Merge pull request #9131 from home-assistant/release-0-52

0.52
This commit is contained in:
Paulus Schoutsen 2017-08-25 22:11:12 -07:00 committed by GitHub
commit 50e5032f86
128 changed files with 3463 additions and 1391 deletions

View File

@ -8,6 +8,9 @@ omit =
homeassistant/helpers/signal.py
# omit pieces of code that rely on external devices being present
homeassistant/components/abode.py
homeassistant/components/*/abode.py
homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py
@ -176,6 +179,9 @@ omit =
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/usps.py
homeassistant/components/*/usps.py
homeassistant/components/velbus.py
homeassistant/components/*/velbus.py
@ -326,6 +332,7 @@ omit =
homeassistant/components/light/yeelightsunflower.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/nello.py
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/sesame.py
@ -383,6 +390,7 @@ omit =
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
homeassistant/components/notify/hipchat.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
@ -391,6 +399,7 @@ omit =
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nfandroidtv.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/prowl.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
homeassistant/components/notify/pushover.py
@ -517,9 +526,9 @@ omit =
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/ups.py
homeassistant/components/sensor/usps.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/waqi.py
homeassistant/components/sensor/worldtidesinfo.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/sensor/zamg.py

3
.gitignore vendored
View File

@ -94,3 +94,6 @@ docs/build
# Windows Explorer
desktop.ini
/home-assistant.pyproj
/home-assistant.sln
/.vs/home-assistant/v14

View File

@ -0,0 +1,75 @@
"""
This component provides basic support for Abode Home Security system.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/abode/
"""
import logging
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import discovery
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_NAME
REQUIREMENTS = ['abodepy==0.7.1']
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by goabode.com"
DOMAIN = 'abode'
DEFAULT_NAME = 'Abode'
DATA_ABODE = 'data_abode'
DEFAULT_ENTITY_NAMESPACE = 'abode'
NOTIFICATION_ID = 'abode_notification'
NOTIFICATION_TITLE = 'Abode Security Setup'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up Abode component."""
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
try:
data = AbodeData(username, password)
hass.data[DATA_ABODE] = data
for component in ['binary_sensor', 'alarm_control_panel']:
discovery.load_platform(hass, component, DOMAIN, {}, config)
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
return True
class AbodeData:
"""Shared Abode data."""
def __init__(self, username, password):
"""Initialize Abode oject."""
import abodepy
self.abode = abodepy.Abode(username, password)
self.devices = self.abode.get_devices()
_LOGGER.debug("Abode Security set up with %s devices",
len(self.devices))

View File

@ -13,7 +13,8 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT)
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
@ -31,6 +32,7 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_DISARM: 'alarm_disarm',
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night',
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
}
@ -81,6 +83,18 @@ def alarm_arm_away(hass, code=None, entity_id=None):
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
@bind_hass
def alarm_arm_night(hass, code=None, entity_id=None):
"""Send the alarm the command for arm night."""
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
@bind_hass
def alarm_trigger(hass, code=None, entity_id=None):
"""Send the alarm the command for trigger."""
@ -187,6 +201,17 @@ class AlarmControlPanel(Entity):
"""
return self.hass.async_add_job(self.alarm_arm_away, code)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
raise NotImplementedError()
def async_alarm_arm_night(self, code=None):
"""Send arm night command.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_arm_night, code)
def alarm_trigger(self, code=None):
"""Send alarm trigger command."""
raise NotImplementedError()

View File

@ -0,0 +1,82 @@
"""
This component provides HA alarm_control_panel support for Abode System.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.abode/
"""
import logging
from homeassistant.components.abode import (DATA_ABODE, DEFAULT_NAME)
from homeassistant.const import (STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.components.alarm_control_panel as alarm
DEPENDENCIES = ['abode']
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:security'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for an Abode device."""
data = hass.data.get(DATA_ABODE)
add_devices([AbodeAlarm(hass, data, data.abode.get_alarm())])
class AbodeAlarm(alarm.AlarmControlPanel):
"""An alarm_control_panel implementation for Abode."""
def __init__(self, hass, data, device):
"""Initialize the alarm control panel."""
super(AbodeAlarm, self).__init__()
self._device = device
self._name = "{0}".format(DEFAULT_NAME)
@property
def should_poll(self):
"""Return the polling state."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return icon."""
return ICON
@property
def state(self):
"""Return the state of the device."""
if self._device.mode == "standby":
state = STATE_ALARM_DISARMED
elif self._device.mode == "away":
state = STATE_ALARM_ARMED_AWAY
elif self._device.mode == "home":
state = STATE_ALARM_ARMED_HOME
else:
state = None
return state
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._device.set_standby()
self.schedule_update_ha_state()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._device.set_home()
self.schedule_update_ha_state()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._device.set_away()
self.schedule_update_ha_state()
def update(self):
"""Update the device state."""
self._device.refresh()

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
REQUIREMENTS = ['pythonegardia==1.0.17']
REQUIREMENTS = ['pythonegardia==1.0.18']
_LOGGER = logging.getLogger(__name__)

View File

@ -12,9 +12,10 @@ import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME,
CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
@ -87,7 +88,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
def state(self):
"""Return the state of the device."""
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_NIGHT) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING
@ -145,6 +147,20 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
if not self._validate_code(code, STATE_ALARM_ARMED_NIGHT):
return
self._state = STATE_ALARM_ARMED_NIGHT
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state

View File

@ -31,6 +31,17 @@ alarm_arm_away:
description: An optional code to arm away the alarm control panel with
example: 1234
alarm_arm_night:
description: Send the alarm the command for arm night
fields:
entity_id:
description: Name of alarm control panel to arm night
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm night the alarm control panel with
example: 1234
alarm_trigger:
description: Send the alarm the command for trigger

View File

@ -16,7 +16,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['simplisafe-python==1.0.4']
REQUIREMENTS = ['simplisafe-python==1.0.5']
_LOGGER = logging.getLogger(__name__)
@ -89,11 +89,11 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
def state(self):
"""Return the state of the device."""
status = self.simplisafe.state()
if status == 'Off':
if status == 'off':
state = STATE_ALARM_DISARMED
elif status == 'Home':
elif status == 'home':
state = STATE_ALARM_ARMED_HOME
elif status == 'Away':
elif status == 'away':
state = STATE_ALARM_ARMED_AWAY
else:
state = STATE_UNKNOWN

View File

@ -13,8 +13,8 @@ import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
CONF_NAME)
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
REQUIREMENTS = ['total_connect_client==0.11']
@ -74,6 +74,12 @@ class TotalConnect(alarm.AlarmControlPanel):
state = STATE_ALARM_ARMED_HOME
elif status == self._client.ARMED_AWAY:
state = STATE_ALARM_ARMED_AWAY
elif status == self._client.ARMED_STAY_NIGHT:
state = STATE_ALARM_ARMED_NIGHT
elif status == self._client.ARMING:
state = STATE_ALARM_ARMING
elif status == self._client.DISARMING:
state = STATE_ALARM_DISARMING
else:
state = STATE_UNKNOWN
@ -90,3 +96,7 @@ class TotalConnect(alarm.AlarmControlPanel):
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away()
def alarm_arm_night(self, code=None):
"""Send arm night command."""
self._client.arm_stay_night()

View File

@ -91,7 +91,7 @@ def request_configuration(hass, config, atv, credentials):
hass.async_add_job(configurator.request_done, instance)
instance = configurator.request_config(
hass, 'Apple TV Authentication', configuration_callback,
'Apple TV Authentication', configuration_callback,
description='Please enter PIN code shown on screen.',
submit_caption='Confirm',
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]

View File

@ -110,7 +110,7 @@ def request_configuration(hass, name, host, serialnumber):
title = '{} ({})'.format(name, host)
request_id = configurator.request_config(
hass, title, configuration_callback,
title, configuration_callback,
description='Functionality: ' + str(AXIS_INCLUDE),
entity_picture="/static/images/logo_axis.png",
link_name='Axis platform documentation',

View File

@ -0,0 +1,81 @@
"""
This component provides HA binary_sensor support for Abode Security System.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.abode/
"""
import logging
from homeassistant.components.abode import (CONF_ATTRIBUTION, DATA_ABODE)
from homeassistant.const import (ATTR_ATTRIBUTION)
from homeassistant.components.binary_sensor import (BinarySensorDevice)
DEPENDENCIES = ['abode']
_LOGGER = logging.getLogger(__name__)
# Sensor types: Name, device_class
SENSOR_TYPES = {
'Door Contact': 'opening',
'Motion Camera': 'motion',
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for an Abode device."""
data = hass.data.get(DATA_ABODE)
sensors = []
for sensor in data.devices:
_LOGGER.debug('Sensor type %s', sensor.type)
if sensor.type in ['Door Contact', 'Motion Camera']:
sensors.append(AbodeBinarySensor(hass, data, sensor))
_LOGGER.debug('Adding %d sensors', len(sensors))
add_devices(sensors)
class AbodeBinarySensor(BinarySensorDevice):
"""A binary sensor implementation for Abode device."""
def __init__(self, hass, data, device):
"""Initialize a sensor for Abode device."""
super(AbodeBinarySensor, self).__init__()
self._device = device
@property
def should_poll(self):
"""Return the polling state."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return "{0} {1}".format(self._device.type, self._device.name)
@property
def is_on(self):
"""Return True if the binary sensor is on."""
if self._device.type == 'Door Contact':
return self._device.status != 'Closed'
elif self._device.type == 'Motion Camera':
return self._device.get_value('motion_event') == '1'
@property
def device_class(self):
"""Return the class of the binary sensor."""
return SENSOR_TYPES.get(self._device.type)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
attrs['device_id'] = self._device.device_id
attrs['battery_low'] = self._device.battery_low
return attrs
def update(self):
"""Update the device state."""
self._device.refresh()

View File

@ -4,62 +4,27 @@ Support for MySensors binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.binary_sensor import (DEVICE_CLASSES,
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN,
BinarySensorDevice)
from homeassistant.const import STATE_ON
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for sensors."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_DOOR: [set_req.V_TRIPPED],
pres.S_MOTION: [set_req.V_TRIPPED],
pres.S_SMOKE: [set_req.V_TRIPPED],
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_SPRINKLER: [set_req.V_TRIPPED],
pres.S_WATER_LEAK: [set_req.V_TRIPPED],
pres.S_SOUND: [set_req.V_TRIPPED],
pres.S_VIBRATION: [set_req.V_TRIPPED],
pres.S_MOISTURE: [set_req.V_TRIPPED],
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsBinarySensor, add_devices))
"""Setup the mysensors platform for binary sensors."""
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsBinarySensor,
add_devices=add_devices)
class MySensorsBinarySensor(
mysensors.MySensorsDeviceEntity, BinarySensorDevice):
mysensors.MySensorsEntity, BinarySensorDevice):
"""Represent the value of a MySensors Binary Sensor child node."""
@property
def is_on(self):
"""Return True if the binary sensor is on."""
if self.value_type in self._values:
return self._values[self.value_type] == STATE_ON
return False
return self._values.get(self.value_type) == STATE_ON
@property
def device_class(self):

View File

@ -6,13 +6,12 @@ https://home-assistant.io/components/binary_sensor.workday/
"""
import asyncio
import logging
import datetime
from datetime import datetime, timedelta
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, WEEKDAYS
import homeassistant.util.dt as dt_util
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.helpers.config_validation as cv
@ -39,11 +38,14 @@ CONF_EXCLUDES = 'excludes'
DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday']
DEFAULT_NAME = 'Workday Sensor'
ALLOWED_DAYS = WEEKDAYS + ['holiday']
CONF_OFFSET = 'days_offset'
DEFAULT_OFFSET = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
vol.Optional(CONF_PROVINCE, default=None): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int),
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):
vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]),
vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES):
@ -60,8 +62,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
province = config.get(CONF_PROVINCE)
workdays = config.get(CONF_WORKDAYS)
excludes = config.get(CONF_EXCLUDES)
days_offset = config.get(CONF_OFFSET)
year = datetime.datetime.now().year
year = (datetime.now() + timedelta(days=days_offset)).year
obj_holidays = getattr(holidays, country)(years=year)
if province:
@ -85,7 +88,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.debug("%s %s", date, name)
add_devices([IsWorkdaySensor(
obj_holidays, workdays, excludes, sensor_name)], True)
obj_holidays, workdays, excludes, days_offset, sensor_name)], True)
def day_to_string(day):
@ -99,12 +102,13 @@ def day_to_string(day):
class IsWorkdaySensor(BinarySensorDevice):
"""Implementation of a Workday sensor."""
def __init__(self, obj_holidays, workdays, excludes, name):
def __init__(self, obj_holidays, workdays, excludes, days_offset, name):
"""Initialize the Workday sensor."""
self._name = name
self._obj_holidays = obj_holidays
self._workdays = workdays
self._excludes = excludes
self._days_offset = days_offset
self._state = None
@property
@ -135,6 +139,16 @@ class IsWorkdaySensor(BinarySensorDevice):
return False
@property
def state_attributes(self):
"""Return the attributes of the entity."""
# return self._attributes
return {
CONF_WORKDAYS: self._workdays,
CONF_EXCLUDES: self._excludes,
CONF_OFFSET: self._days_offset
}
@asyncio.coroutine
def async_update(self):
"""Get date and look whether it is a holiday."""
@ -142,11 +156,12 @@ class IsWorkdaySensor(BinarySensorDevice):
self._state = False
# Get iso day of the week (1 = Monday, 7 = Sunday)
day = datetime.datetime.today().isoweekday() - 1
date = datetime.today() + timedelta(days=self._days_offset)
day = date.isoweekday() - 1
day_of_week = day_to_string(day)
if self.is_include(day_of_week, dt_util.now()):
if self.is_include(day_of_week, date):
self._state = True
if self.is_exclude(day_of_week, dt_util.now()):
if self.is_exclude(day_of_week, date):
self._state = False

View File

@ -0,0 +1,94 @@
"""
Support for a camera made up of usps mail images.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera.usps/
"""
from datetime import timedelta
import logging
from homeassistant.components.camera import Camera
from homeassistant.components.usps import DATA_USPS
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['usps']
SCAN_INTERVAL = timedelta(seconds=10)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up USPS mail camera."""
if discovery_info is None:
return
usps = hass.data[DATA_USPS]
add_devices([USPSCamera(usps)])
class USPSCamera(Camera):
"""Representation of the images available from USPS."""
def __init__(self, usps):
"""Initialize the USPS camera images."""
super().__init__()
self._usps = usps
self._name = self._usps.name
self._session = self._usps.session
self._mail_img = []
self._last_mail = None
self._mail_index = 0
self._mail_count = 0
self._timer = None
def camera_image(self):
"""Update the camera's image if it has changed."""
self._usps.update()
try:
self._mail_count = len(self._usps.mail)
except TypeError:
# No mail
return None
if self._usps.mail != self._last_mail:
# Mail items must have changed
self._mail_img = []
if len(self._usps.mail) >= 1:
self._last_mail = self._usps.mail
for article in self._usps.mail:
_LOGGER.debug("Fetching article image: %s", article)
img = self._session.get(article['image']).content
self._mail_img.append(img)
try:
return self._mail_img[self._mail_index]
except IndexError:
return None
@property
def name(self):
"""Return the name of this camera."""
return '{} mail'.format(self._name)
@property
def model(self):
"""Return date of mail as model."""
try:
return 'Date: {}'.format(self._usps.mail[0]['date'])
except IndexError:
return None
@property
def should_poll(self):
"""Update the mail image index periodically."""
return True
def update(self):
"""Update mail image index."""
if self._mail_index < (self._mail_count - 1):
self._mail_index += 1
else:
self._mail_index = 0

View File

@ -4,15 +4,11 @@ MySensors platform that offers a Climate (MySensors-HVAC) component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_OFF, STATE_AUTO, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
_LOGGER = logging.getLogger(__name__)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
DICT_HA_TO_MYS = {
STATE_AUTO: 'AutoChangeOver',
@ -29,28 +25,12 @@ DICT_MYS_TO_HA = {
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the mysensors climate."""
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
if float(gateway.protocol_version) < 1.5:
continue
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_HVAC: [set_req.V_HVAC_FLOW_STATE],
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsHVAC, add_devices))
"""Setup the mysensors climate."""
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsHVAC, add_devices=add_devices)
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
"""Representation of a MySensors HVAC."""
@property
@ -84,26 +64,28 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
if temp is None:
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return float(temp)
return float(temp) if temp is not None else None
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
return float(self._values.get(set_req.V_HVAC_SETPOINT_COOL))
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
return float(temp) if temp is not None else None
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_COOL in self._values:
return float(self._values.get(set_req.V_HVAC_SETPOINT_HEAT))
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return float(temp) if temp is not None else None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._values.get(self.gateway.const.SetReq.V_HVAC_FLOW_STATE)
return self._values.get(self.value_type)
@property
def operation_list(self):
@ -128,7 +110,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
heat = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
cool = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
updates = ()
updates = []
if temp is not None:
if heat is not None:
# Set HEAT Target temperature
@ -146,7 +128,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
# optimistically assume that device has changed state
self._values[value_type] = value
self.schedule_update_ha_state()
@ -156,54 +138,22 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
# optimistically assume that device has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_HVAC_FLOW_STATE,
self.node_id, self.child_id, self.value_type,
DICT_HA_TO_MYS[operation_mode])
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode
# optimistically assume that device has changed state
self._values[self.value_type] = operation_mode
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
set_req = self.gateway.const.SetReq
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == set_req.V_HVAC_FLOW_STATE:
self._values[value_type] = DICT_MYS_TO_HA[value]
else:
self._values[value_type] = value
def set_humidity(self, humidity):
"""Set new target humidity."""
_LOGGER.error("Service Not Implemented yet")
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_on(self):
"""Turn away mode on."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_off(self):
"""Turn away mode off."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
_LOGGER.error("Service Not Implemented yet")
super().update()
self._values[self.value_type] = DICT_MYS_TO_HA[
self._values[self.value_type]]

View File

@ -14,7 +14,7 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = ('core', 'group', 'hassbian', 'automation')
SECTIONS = ('core', 'group', 'hassbian', 'automation', 'script')
ON_DEMAND = ('zwave')

View File

@ -0,0 +1,19 @@
"""Provide configuration end points for scripts."""
import asyncio
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.script import SCRIPT_ENTRY_SCHEMA, async_reload
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'scripts.yaml'
@asyncio.coroutine
def async_setup(hass):
"""Set up the script config API."""
hass.http.register_view(EditKeyBasedConfigView(
'script', 'config', CONFIG_PATH, cv.slug, SCRIPT_ENTRY_SCHEMA,
post_write_hook=async_reload
))
return True

View File

@ -7,19 +7,21 @@ A callback has to be provided to `request_config` which will be called when
the user has submitted configuration information.
"""
import asyncio
import functools as ft
import logging
from homeassistant.core import callback as async_callback
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.util.async import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__)
_REQUESTS = {}
_KEY_INSTANCE = 'configurator'
DATA_REQUESTS = 'configurator_requests'
ATTR_CONFIGURE_ID = 'configure_id'
ATTR_DESCRIPTION = 'description'
ATTR_DESCRIPTION_IMAGE = 'description_image'
@ -39,63 +41,89 @@ STATE_CONFIGURED = 'configured'
@bind_hass
def request_config(
hass, name, callback, description=None, description_image=None,
@async_callback
def async_request_config(
hass, name, callback=None, description=None, description_image=None,
submit_caption=None, fields=None, link_name=None, link_url=None,
entity_picture=None):
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
"""
instance = run_callback_threadsafe(hass.loop,
_async_get_instance,
hass).result()
instance = hass.data.get(_KEY_INSTANCE)
request_id = instance.request_config(
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
request_id = instance.async_request_config(
name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture)
_REQUESTS[request_id] = instance
if DATA_REQUESTS not in hass.data:
hass.data[DATA_REQUESTS] = {}
hass.data[DATA_REQUESTS][request_id] = instance
return request_id
def notify_errors(request_id, error):
@bind_hass
def request_config(hass, *args, **kwargs):
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
"""
return run_callback_threadsafe(
hass.loop, ft.partial(async_request_config, hass, *args, **kwargs)
).result()
@bind_hass
@async_callback
def async_notify_errors(hass, request_id, error):
"""Add errors to a config request."""
try:
_REQUESTS[request_id].notify_errors(request_id, error)
hass.data[DATA_REQUESTS][request_id].async_notify_errors(
request_id, error)
except KeyError:
# If request_id does not exist
pass
def request_done(request_id):
@bind_hass
def notify_errors(hass, request_id, error):
"""Add errors to a config request."""
return run_callback_threadsafe(
hass.loop, async_notify_errors, hass, request_id, error
).result()
@bind_hass
@async_callback
def async_request_done(hass, request_id):
"""Mark a configuration request as done."""
try:
_REQUESTS.pop(request_id).request_done(request_id)
hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id)
except KeyError:
# If request_id does not exist
pass
@bind_hass
def request_done(hass, request_id):
"""Mark a configuration request as done."""
return run_callback_threadsafe(
hass.loop, async_request_done, hass, request_id
).result()
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the configurator component."""
return True
@async_callback
def _async_get_instance(hass):
"""Get an instance per hass object."""
instance = hass.data.get(_KEY_INSTANCE)
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
return instance
class Configurator(object):
"""The class to keep track of current configuration requests."""
@ -105,14 +133,16 @@ class Configurator(object):
self._cur_id = 0
self._requests = {}
hass.services.async_register(
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call)
def request_config(
@async_callback
def async_request_config(
self, name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture):
"""Set up a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, name, hass=self.hass)
if fields is None:
fields = []
@ -138,11 +168,12 @@ class Configurator(object):
] if value is not None
})
self.hass.states.set(entity_id, STATE_CONFIGURE, data)
self.hass.states.async_set(entity_id, STATE_CONFIGURE, data)
return request_id
def notify_errors(self, request_id, error):
@async_callback
def async_notify_errors(self, request_id, error):
"""Update the state with errors."""
if not self._validate_request_id(request_id):
return
@ -154,9 +185,10 @@ class Configurator(object):
new_data = dict(state.attributes)
new_data[ATTR_ERRORS] = error
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
self.hass.states.async_set(entity_id, STATE_CONFIGURE, new_data)
def request_done(self, request_id):
@async_callback
def async_request_done(self, request_id):
"""Remove the configuration request."""
if not self._validate_request_id(request_id):
return
@ -167,15 +199,16 @@ class Configurator(object):
# the result fo the service call (current design limitation).
# Instead, we will set it to configured to give as feedback but delete
# it shortly after so that it is deleted when the client updates.
self.hass.states.set(entity_id, STATE_CONFIGURED)
self.hass.states.async_set(entity_id, STATE_CONFIGURED)
def deferred_remove(event):
"""Remove the request state."""
self.hass.states.remove(entity_id)
self.hass.states.async_remove(entity_id)
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove)
def handle_service_call(self, call):
@async_callback
def async_handle_service_call(self, call):
"""Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID)
@ -186,8 +219,8 @@ class Configurator(object):
entity_id, fields, callback = self._requests[request_id]
# field validation goes here?
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
if callback:
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self):
"""Generate a unique configurator ID."""

View File

@ -4,42 +4,18 @@ Support for MySensors covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.cover import CoverDevice, ATTR_POSITION, DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for covers."""
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_COVER: [set_req.V_DIMMER, set_req.V_LIGHT],
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_COVER: [set_req.V_PERCENTAGE, set_req.V_STATUS],
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsCover, add_devices))
"""Setup the mysensors platform for covers."""
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsCover, add_devices=add_devices)
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
"""Representation of the value of a MySensors Cover child node."""
@property

View File

@ -19,7 +19,7 @@ from homeassistant.const import (
CONF_FRIENDLY_NAME, CONF_ENTITY_ID,
EVENT_HOMEASSISTANT_START, MATCH_ALL,
CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE,
STATE_OPEN, STATE_CLOSED)
CONF_OPTIMISTIC, STATE_OPEN, STATE_CLOSED)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
@ -39,6 +39,8 @@ CLOSE_ACTION = 'close_cover'
STOP_ACTION = 'stop_cover'
POSITION_ACTION = 'set_cover_position'
TILT_ACTION = 'set_cover_tilt_position'
CONF_TILT_OPTIMISTIC = 'tilt_optimistic'
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
CONF_OPEN_OR_CLOSE = 'open_or_close'
@ -56,6 +58,8 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean,
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
@ -83,11 +87,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
stop_action = device_config.get(STOP_ACTION)
position_action = device_config.get(POSITION_ACTION)
tilt_action = device_config.get(TILT_ACTION)
if position_template is None and state_template is None:
_LOGGER.error('Must specify either %s' or '%s',
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE)
continue
optimistic = device_config.get(CONF_OPTIMISTIC)
tilt_optimistic = device_config.get(CONF_TILT_OPTIMISTIC)
if position_action is None and open_action is None:
_LOGGER.error('Must specify at least one of %s' or '%s',
@ -125,7 +126,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
device, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action, entity_ids
position_action, tilt_action,
optimistic, tilt_optimistic, entity_ids
)
)
if not covers:
@ -142,7 +144,8 @@ class CoverTemplate(CoverDevice):
def __init__(self, hass, device_id, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action, entity_ids):
position_action, tilt_action,
optimistic, tilt_optimistic, entity_ids):
"""Initialize the Template cover."""
self.hass = hass
self.entity_id = async_generate_entity_id(
@ -167,6 +170,9 @@ class CoverTemplate(CoverDevice):
self._tilt_script = None
if tilt_action is not None:
self._tilt_script = Script(hass, tilt_action)
self._optimistic = (optimistic or
(not state_template and not position_template))
self._tilt_optimistic = tilt_optimistic or not tilt_template
self._icon = None
self._position = None
self._tilt_value = None
@ -260,19 +266,23 @@ class CoverTemplate(CoverDevice):
def async_open_cover(self, **kwargs):
"""Move the cover up."""
if self._open_script:
self.hass.async_add_job(self._open_script.async_run())
yield from self._open_script.async_run()
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 100}))
yield from self._position_script.async_run({"position": 100})
if self._optimistic:
self._position = 100
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_close_cover(self, **kwargs):
"""Move the cover down."""
if self._close_script:
self.hass.async_add_job(self._close_script.async_run())
yield from self._close_script.async_run()
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 0}))
yield from self._position_script.async_run({"position": 0})
if self._optimistic:
self._position = 0
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
@ -284,29 +294,35 @@ class CoverTemplate(CoverDevice):
def async_set_cover_position(self, **kwargs):
"""Set cover position."""
self._position = kwargs[ATTR_POSITION]
self.hass.async_add_job(self._position_script.async_run(
{"position": self._position}))
yield from self._position_script.async_run(
{"position": self._position})
if self._optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_open_cover_tilt(self, **kwargs):
"""Tilt the cover open."""
self._tilt_value = 100
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
if self._tilt_optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_close_cover_tilt(self, **kwargs):
"""Tilt the cover closed."""
self._tilt_value = 0
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
yield from self._tilt_script.async_run(
{"tilt": self._tilt_value})
if self._tilt_optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
self._tilt_value = kwargs[ATTR_TILT_POSITION]
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
if self._tilt_optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_update(self):

View File

@ -6,28 +6,32 @@ https://home-assistant.io/components/device_tracker.automatic/
"""
import asyncio
from datetime import timedelta
import json
import logging
import os
from aiohttp import web
import voluptuous as vol
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC,
ATTR_GPS, ATTR_GPS_ACCURACY)
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_START)
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
REQUIREMENTS = ['aioautomatic==0.4.0']
REQUIREMENTS = ['aioautomatic==0.6.0']
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id'
CONF_SECRET = 'secret'
CONF_DEVICES = 'devices'
CONF_CURRENT_LOCATION = 'current_location'
DEFAULT_TIMEOUT = 5
@ -38,38 +42,74 @@ ATTR_FUEL_LEVEL = 'fuel_level'
EVENT_AUTOMATIC_UPDATE = 'automatic_update'
AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json'
DATA_CONFIGURING = 'automatic_configurator_clients'
DATA_REFRESH_TOKEN = 'refresh_token'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
vol.Optional(CONF_DEVICES, default=None): vol.All(
cv.ensure_list, [cv.string])
})
def _get_refresh_token_from_file(hass, filename):
"""Attempt to load session data from file."""
path = hass.config.path(filename)
if not os.path.isfile(path):
return None
try:
with open(path) as data_file:
data = json.load(data_file)
if data is None:
return None
return data.get(DATA_REFRESH_TOKEN)
except ValueError:
return None
def _write_refresh_token_to_file(hass, filename, refresh_token):
"""Attempt to store session data to file."""
path = hass.config.path(filename)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w+') as data_file:
json.dump({
DATA_REFRESH_TOKEN: refresh_token
}, data_file)
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Validate the configuration and return an Automatic scanner."""
import aioautomatic
hass.http.register_view(AutomaticAuthCallbackView())
scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE
client = aioautomatic.Client(
client_id=config[CONF_CLIENT_ID],
client_secret=config[CONF_SECRET],
client_session=async_get_clientsession(hass),
request_kwargs={'timeout': DEFAULT_TIMEOUT})
try:
try:
session = yield from client.create_session_from_password(
FULL_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
except aioautomatic.exceptions.ForbiddenError as exc:
if not str(exc).startswith("invalid_scope"):
raise exc
_LOGGER.info("Client not authorized for current_location scope. "
"location:updated events will not be received.")
session = yield from client.create_session_from_password(
DEFAULT_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID])
refresh_token = yield from hass.async_add_job(
_get_refresh_token_from_file, hass, filename)
@asyncio.coroutine
def initialize_data(session):
"""Initialize the AutomaticData object from the created session."""
hass.async_add_job(
_write_refresh_token_to_file, hass, filename,
session.refresh_token)
data = AutomaticData(
hass, client, session, config[CONF_DEVICES], async_see)
@ -77,26 +117,86 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
vehicles = yield from session.get_vehicles()
for vehicle in vehicles:
hass.async_add_job(data.load_vehicle(vehicle))
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
return False
@callback
def ws_connect(event):
"""Open the websocket connection."""
hass.async_add_job(data.ws_connect())
# Create a task instead of adding a tracking job, since this task will
# run until the websocket connection is closed.
hass.loop.create_task(data.ws_connect())
@callback
def ws_close(event):
"""Close the websocket connection."""
hass.async_add_job(data.ws_close())
if refresh_token is not None:
try:
session = yield from client.create_session_from_refresh_token(
refresh_token)
yield from initialize_data(session)
return True
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, ws_connect)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, ws_close)
configurator = hass.components.configurator
request_id = configurator.async_request_config(
"Automatic", description=(
"Authorization required for Automatic device tracker."),
link_name="Click here to authorize Home Assistant.",
link_url=client.generate_oauth_url(scope),
entity_picture="/static/images/logo_automatic.png",
)
@asyncio.coroutine
def initialize_callback(code, state):
"""Callback after OAuth2 response is returned."""
try:
session = yield from client.create_session_from_oauth_code(
code, state)
yield from initialize_data(session)
configurator.async_request_done(request_id)
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
configurator.async_notify_errors(request_id, str(err))
return False
if DATA_CONFIGURING not in hass.data:
hass.data[DATA_CONFIGURING] = {}
hass.data[DATA_CONFIGURING][client.state] = initialize_callback
return True
class AutomaticAuthCallbackView(HomeAssistantView):
"""Handle OAuth finish callback requests."""
requires_auth = False
url = '/api/automatic/callback'
name = 'api:automatic:callback'
@callback
def get(self, request): # pylint: disable=no-self-use
"""Finish OAuth callback request."""
hass = request.app['hass']
params = request.query
response = web.HTTPFound('/states')
if 'state' not in params or 'code' not in params:
if 'error' in params:
_LOGGER.error(
"Error authorizing Automatic: %s", params['error'])
return response
else:
_LOGGER.error(
"Error authorizing Automatic. Invalid response returned.")
return response
if DATA_CONFIGURING not in hass.data or \
params['state'] not in hass.data[DATA_CONFIGURING]:
_LOGGER.error("Automatic configuration request not found.")
return response
code = params['code']
state = params['state']
initialize_callback = hass.data[DATA_CONFIGURING][state]
hass.async_add_job(initialize_callback(code, state))
return response
class AutomaticData(object):
"""A class representing an Automatic cloud service connection."""
@ -115,6 +215,8 @@ class AutomaticData(object):
lambda name, event: self.hass.async_add_job(
self.handle_event(name, event)))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close())
@asyncio.coroutine
def handle_event(self, name, event):
"""Coroutine to update state for a realtime event."""

View File

@ -19,7 +19,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util import slugify
import homeassistant.util.dt as dt_util
from homeassistant.util.location import distance
from homeassistant.loader import get_component
_LOGGER = logging.getLogger(__name__)
@ -209,7 +208,7 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
configurator = self.hass.components.configurator
configurator.request_done(request_id)
# Trigger the next step immediately
@ -217,7 +216,7 @@ class Icloud(DeviceScanner):
def icloud_need_trusted_device(self):
"""We need a trusted device."""
configurator = get_component('configurator')
configurator = self.hass.components.configurator
if self.accountname in _CONFIGURING:
return
@ -229,7 +228,7 @@ class Icloud(DeviceScanner):
devicesstring += "{}: {};".format(i, devicename)
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
'iCloud {}'.format(self.accountname),
self.icloud_trusted_device_callback,
description=(
'Please choose your trusted device by entering'
@ -259,17 +258,17 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
configurator = self.hass.components.configurator
configurator.request_done(request_id)
def icloud_need_verification_code(self):
"""Return the verification code."""
configurator = get_component('configurator')
configurator = self.hass.components.configurator
if self.accountname in _CONFIGURING:
return
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
'iCloud {}'.format(self.accountname),
self.icloud_verification_callback,
description=('Please enter the validation code:'),
entity_picture="/static/images/config_icloud.png",

View File

@ -4,61 +4,51 @@ Support for tracking MySensors devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.helpers.dispatcher import dispatcher_connect
from homeassistant.util import slugify
DEPENDENCIES = ['mysensors']
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the MySensors tracker."""
def mysensors_callback(gateway, msg):
"""Set up callback for mysensors platform."""
node = gateway.sensors[msg.node_id]
if node.sketch_name is None:
_LOGGER.debug("No sketch_name: node %s", msg.node_id)
return
"""Set up the MySensors device scanner."""
new_devices = mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsDeviceScanner,
device_args=(see, ))
if not new_devices:
return False
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
child = node.children.get(msg.child_id)
if child is None:
return
position = child.values.get(set_req.V_POSITION)
if child.type != pres.S_GPS or position is None:
return
try:
latitude, longitude, _ = position.split(',')
except ValueError:
_LOGGER.error("Payload for V_POSITION %s is not of format "
"latitude, longitude, altitude", position)
return
name = '{} {} {}'.format(
node.sketch_name, msg.node_id, child.id)
attr = {
mysensors.ATTR_CHILD_ID: child.id,
mysensors.ATTR_DESCRIPTION: child.description,
mysensors.ATTR_DEVICE: gateway.device,
mysensors.ATTR_NODE_ID: msg.node_id,
}
see(
dev_id=slugify(name),
host_name=name,
gps=(latitude, longitude),
battery=node.battery_level,
attributes=attr
)
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
for gateway in gateways:
if float(gateway.protocol_version) < 2.0:
continue
gateway.platform_callbacks.append(mysensors_callback)
for device in new_devices:
dev_id = (
id(device.gateway), device.node_id, device.child_id,
device.value_type)
dispatcher_connect(
hass, mysensors.SIGNAL_CALLBACK.format(*dev_id),
device.update_callback)
return True
class MySensorsDeviceScanner(mysensors.MySensorsDevice):
"""Represent a MySensors scanner."""
def __init__(self, see, *args):
"""Set up instance."""
super().__init__(*args)
self.see = see
def update_callback(self):
"""Update the device."""
self.update()
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
position = child.values[self.value_type]
latitude, longitude, _ = position.split(',')
self.see(
dev_id=slugify(self.name),
host_name=self.name,
gps=(latitude, longitude),
battery=node.battery_level,
attributes=self.device_state_attributes
)

View File

@ -13,10 +13,9 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.loader import get_component
from homeassistant.util import Throttle
REQUIREMENTS = ['python-ecobee-api==0.0.7']
REQUIREMENTS = ['python-ecobee-api==0.0.8']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@ -41,7 +40,7 @@ CONFIG_SCHEMA = vol.Schema({
def request_configuration(network, hass, config):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if 'ecobee' in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['ecobee'], "Failed to register, please try again.")
@ -56,7 +55,7 @@ def request_configuration(network, hass, config):
setup_ecobee(hass, network, config)
_CONFIGURING['ecobee'] = configurator.request_config(
hass, "Ecobee", ecobee_configuration_callback,
"Ecobee", ecobee_configuration_callback,
description=(
'Please authorize this app at https://www.ecobee.com/consumer'
'portal/index.html with pin code: ' + network.pin),
@ -73,7 +72,7 @@ def setup_ecobee(hass, network, config):
return
if 'ecobee' in _CONFIGURING:
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('ecobee'))
hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP)

View File

@ -2,7 +2,6 @@
import threading
import socket
import logging
import os
import select
from aiohttp import web
@ -86,18 +85,6 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
advertise_ip, advertise_port).replace("\n", "\r\n") \
.encode('utf-8')
# Set up a pipe for signaling to the receiver that it's time to
# shutdown. Essentially, we place the SSDP socket into nonblocking
# mode and use select() to wait for data to arrive on either the SSDP
# socket or the pipe. If data arrives on either one, select() returns
# and tells us which filenos have data ready to read.
#
# When we want to stop the responder, we write data to the pipe, which
# causes the select() to return and indicate that said pipe has data
# ready to be read, which indicates to us that the responder needs to
# be shutdown.
self._interrupted_read_pipe, self._interrupted_write_pipe = os.pipe()
def run(self):
"""Run the server."""
# Listen for UDP port 1900 packets sent to SSDP multicast address
@ -119,7 +106,7 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
socket.inet_aton(self.host_ip_addr))
if self.upnp_bind_multicast:
ssdp_socket.bind(("239.255.255.250", 1900))
ssdp_socket.bind(("", 1900))
else:
ssdp_socket.bind((self.host_ip_addr, 1900))
@ -130,16 +117,13 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
try:
read, _, _ = select.select(
[self._interrupted_read_pipe, ssdp_socket], [],
[ssdp_socket])
[ssdp_socket], [],
[ssdp_socket], 2)
if self._interrupted_read_pipe in read:
# Implies self._interrupted is True
clean_socket_close(ssdp_socket)
return
elif ssdp_socket in read:
if ssdp_socket in read:
data, addr = ssdp_socket.recvfrom(1024)
else:
# most likely the timeout, so check for interupt
continue
except socket.error as ex:
if self._interrupted:
@ -148,6 +132,9 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
_LOGGER.error("UPNP Responder socket exception occured: %s",
ex.__str__)
# without the following continue, a second exception occurs
# because the data object has not been initialized
continue
if "M-SEARCH" in data.decode('utf-8'):
# SSDP M-SEARCH method received, respond to it with our info
@ -161,7 +148,6 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
"""Stop the server."""
# Request for server
self._interrupted = True
os.write(self._interrupted_write_pipe, bytes([0]))
self.join()

View File

@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
REQUIREMENTS = ['pyenvisalink==2.1']
REQUIREMENTS = ['pyenvisalink==2.2']
_LOGGER = logging.getLogger(__name__)
@ -74,9 +74,9 @@ CONFIG_SCHEMA = vol.Schema({
vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(
CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL): vol.Coerce(int),
}),
}, extra=vol.ALLOW_EXTRA)

View File

@ -13,7 +13,6 @@ from homeassistant.components.fan import (
ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED, FanEntity)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component
import homeassistant.util as util
_CONFIGURING = {}
@ -57,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
@ -72,7 +71,7 @@ def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id,
'Insteon ' + model + ' addr: ' + device_id,
insteon_fan_config_callback,
description=('Enter a name for ' + model + ' Fan addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
@ -85,7 +84,7 @@ def setup_fan(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the fan."""
if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Device configuration done!")

View File

@ -16,6 +16,11 @@ from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
# Define term used for medium speed. This must be set as the fan component uses
# 'medium' which the ISY does not understand
ISY_SPEED_MEDIUM = 'med'
VALUE_TO_STATE = {
0: SPEED_OFF,
63: SPEED_LOW,
@ -29,7 +34,7 @@ STATE_TO_VALUE = {}
for key in VALUE_TO_STATE:
STATE_TO_VALUE[VALUE_TO_STATE[key]] = key
STATES = [SPEED_OFF, SPEED_LOW, 'med', SPEED_HIGH]
STATES = [SPEED_OFF, SPEED_LOW, ISY_SPEED_MEDIUM, SPEED_HIGH]
# pylint: disable=unused-argument
@ -93,6 +98,11 @@ class ISYFanDevice(isy.ISYDevice, FanEntity):
else:
self.speed = self.state
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
class ISYFanProgram(ISYFanDevice):
"""Representation of an ISY994 fan program."""

View File

@ -19,7 +19,7 @@ from homeassistant.helpers.dispatcher import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['ha-ffmpeg==1.5']
REQUIREMENTS = ['ha-ffmpeg==1.7']
DOMAIN = 'ffmpeg'

View File

@ -3,10 +3,10 @@
FINGERPRINTS = {
"compatibility.js": "1686167ff210e001f063f5c606b2e74b",
"core.js": "2a7d01e45187c7d4635da05065b5e54e",
"frontend.html": "5a2a3d6181cc820f5b3e94d1a50def74",
"frontend.html": "6c8192a4393c9e83516dc8177b75c23d",
"mdi.html": "e91f61a039ed0a9936e7ee5360da3870",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-config.html": "ec48185c79000d0cfe5bbf38c7974944",
"panels/ha-panel-config.html": "bd20a3b11b46522e3c705a0b6a72b9dc",
"panels/ha-panel-dev-event.html": "d409e7ab537d9fe629126d122345279c",
"panels/ha-panel-dev-info.html": "b0e55eb657fd75f21aba2426ac0cedc0",
"panels/ha-panel-dev-mqtt.html": "94b222b013a98583842de3e72d5888c6",

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 34e45427ea2ee4f3d1a439db9fb5b8e3368f40d9
Subproject commit 07d5d6e8a9205f77f83f68615695bcbc73cf83e3

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

View File

@ -37,7 +37,7 @@
/* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren, quotes, comma-spacing */
'use strict';
var precacheConfig = [["/","07ae53d16e9e97de8c721f5032cf79bf"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-f47b6910d8e4880e22cc508ca452f9b6.html","9aa0675e01373c6bc2737438bb84a9ec"],["/frontend/panels/map-c2544fff3eedb487d44105cf94b335ec.html","113c5bf9a68a74c62e50cd354034e78b"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-5a2a3d6181cc820f5b3e94d1a50def74.html","6cd425233aeb180178dccae238533d65"],["/static/mdi-e91f61a039ed0a9936e7ee5360da3870.html","5e587bc82719b740a4f0798722a83aee"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]];
var precacheConfig = [["/","535d629ec4d3936dba0ca4ca84dabeb2"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-f47b6910d8e4880e22cc508ca452f9b6.html","9aa0675e01373c6bc2737438bb84a9ec"],["/frontend/panels/map-c2544fff3eedb487d44105cf94b335ec.html","113c5bf9a68a74c62e50cd354034e78b"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-6c8192a4393c9e83516dc8177b75c23d.html","56d5bfe9e11a8b81a686f20aeae3c359"],["/static/mdi-e91f61a039ed0a9936e7ee5360da3870.html","5e587bc82719b740a4f0798722a83aee"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]];
var cacheName = 'sw-precache-v3--' + (self.registration ? self.registration.scope : '');

View File

@ -45,13 +45,12 @@ def setup(hass, config):
try:
sock.connect((host, port))
sock.shutdown(2)
_LOGGER.debug('Connection to Graphite possible')
_LOGGER.debug("Connection to Graphite possible")
except socket.error:
_LOGGER.error('Not able to connect to Graphite')
_LOGGER.error("Not able to connect to Graphite")
return False
GraphiteFeeder(hass, host, port, prefix)
return True
@ -143,15 +142,15 @@ class GraphiteFeeder(threading.Thread):
_LOGGER.debug("Processing STATE_CHANGED event for %s",
event.data['entity_id'])
try:
self._report_attributes(event.data['entity_id'],
event.data['new_state'])
self._report_attributes(
event.data['entity_id'], event.data['new_state'])
# pylint: disable=broad-except
except Exception:
# Catch this so we can avoid the thread dying and
# make it visible.
_LOGGER.exception("Failed to process STATE_CHANGED event")
else:
_LOGGER.warning("Processing unexpected event type %s",
event.event_type)
_LOGGER.warning(
"Processing unexpected event type %s", event.event_type)
self._queue.task_done()

View File

@ -15,7 +15,7 @@ from homeassistant.components.image_processing import (
from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceEntity)
REQUIREMENTS = ['face_recognition==0.2.0']
REQUIREMENTS = ['face_recognition==0.2.2']
_LOGGER = logging.getLogger(__name__)

View File

@ -16,7 +16,7 @@ from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceEntity)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['face_recognition==0.2.0']
REQUIREMENTS = ['face_recognition==0.2.2']
_LOGGER = logging.getLogger(__name__)

View File

@ -79,8 +79,12 @@ def async_setup(hass, config):
#
# Override the device default capabilities for a specific address
#
plm.protocol.devices.add_override(
device['address'], 'capabilities', [device['platform']])
if isinstance(device['platform'], list):
plm.protocol.devices.add_override(
device['address'], 'capabilities', device['platform'])
else:
plm.protocol.devices.add_override(
device['address'], 'capabilities', [device['platform']])
hass.data['insteon_plm'] = plm

View File

@ -116,8 +116,8 @@ class DecoraWifiLight(Light):
attribs = {'power': 'ON'}
if ATTR_BRIGHTNESS in kwargs:
min_level = self._switch.get('minLevel', 0)
max_level = self._switch.get('maxLevel', 100)
min_level = self._switch.data.get('minLevel', 0)
max_level = self._switch.data.get('maxLevel', 100)
brightness = int(kwargs[ATTR_BRIGHTNESS] * max_level / 255)
brightness = max(brightness, min_level)
attribs['brightness'] = brightness

View File

@ -122,7 +122,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if ipaddr in light_ips:
continue
device['name'] = '{} {}'.format(device['id'], ipaddr)
device[ATTR_MODE] = 'rgbw'
device[ATTR_MODE] = MODE_RGBW
device[CONF_PROTOCOL] = None
light = FluxLight(device)
lights.append(light)
@ -216,9 +216,9 @@ class FluxLight(Light):
elif rgb is not None:
self._bulb.setRgb(*tuple(rgb))
elif brightness is not None:
if self._mode == 'rgbw':
if self._mode == MODE_RGBW:
self._bulb.setWarmWhite255(brightness)
elif self._mode == 'rgb':
elif self._mode == MODE_RGB:
(red, green, blue) = self._bulb.getRgb()
self._bulb.setRgb(red, green, blue, brightness=brightness)
elif effect == EFFECT_RANDOM:

View File

@ -23,7 +23,6 @@ from homeassistant.components.light import (
SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME)
from homeassistant.loader import get_component
from homeassistant.components.emulated_hue import ATTR_EMULATED_HUE
import homeassistant.helpers.config_validation as cv
@ -164,9 +163,7 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
lights = {}
@ -268,7 +265,7 @@ def request_configuration(host, hass, add_devices, filename,
allow_unreachable, allow_in_emulated_hue,
allow_hue_groups):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
@ -284,7 +281,7 @@ def request_configuration(host, hass, add_devices, filename,
allow_in_emulated_hue, allow_hue_groups)
_CONFIGURING[host] = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
"Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips Hue "
"with Home Assistant."),
entity_picture="/static/images/logo_philips_hue.png",
@ -384,7 +381,6 @@ class HueLight(Light):
hue, sat = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR])
command['hue'] = hue
command['sat'] = sat
command['bri'] = self.info['bri']
else:
command['xy'] = kwargs[ATTR_XY_COLOR]
elif ATTR_RGB_COLOR in kwargs:
@ -399,14 +395,13 @@ class HueLight(Light):
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
command['xy'] = xyb[0], xyb[1]
command['bri'] = xyb[2]
elif ATTR_COLOR_TEMP in kwargs:
temp = kwargs[ATTR_COLOR_TEMP]
command['ct'] = max(self.min_mireds, min(temp, self.max_mireds))
if ATTR_BRIGHTNESS in kwargs:
command['bri'] = kwargs[ATTR_BRIGHTNESS]
if ATTR_COLOR_TEMP in kwargs:
temp = kwargs[ATTR_COLOR_TEMP]
command['ct'] = max(self.min_mireds, min(temp, self.max_mireds))
flash = kwargs.get(ATTR_FLASH)
if flash == FLASH_LONG:
@ -425,9 +420,9 @@ class HueLight(Light):
elif effect == EFFECT_RANDOM:
command['hue'] = random.randrange(0, 65535)
command['sat'] = random.randrange(150, 254)
elif self.bridge_type == 'hue':
if self.info.get('manufacturername') != "OSRAM":
command['effect'] = 'none'
elif (self.bridge_type == 'hue' and
self.info.get('manufacturername') == 'Philips'):
command['effect'] = 'none'
self._command_func(self.light_id, command)

View File

@ -11,7 +11,6 @@ from datetime import timedelta
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
from homeassistant.loader import get_component
import homeassistant.util as util
_CONFIGURING = {}
@ -54,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
@ -69,7 +68,7 @@ def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id,
'Insteon ' + model + ' addr: ' + device_id,
insteon_light_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
@ -82,7 +81,7 @@ def setup_light(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the light."""
if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.debug("Device configuration done")

View File

@ -325,29 +325,33 @@ class LIFXManager(object):
entity = self.entities[device.mac_addr]
entity.registered = True
_LOGGER.debug("%s register AGAIN", entity.who)
yield from entity.async_update()
yield from entity.async_update_ha_state()
yield from entity.update_hass()
else:
_LOGGER.debug("%s register NEW", device.ip_addr)
device.timeout = MESSAGE_TIMEOUT
device.retry_count = MESSAGE_RETRIES
device.unregister_timeout = UNAVAILABLE_GRACE
# Read initial state
ack = AwaitAioLIFX().wait
yield from ack(device.get_version)
yield from ack(device.get_color)
version_resp = yield from ack(device.get_version)
if version_resp:
color_resp = yield from ack(device.get_color)
if lifxwhite(device):
entity = LIFXWhite(device, self.effects_conductor)
elif lifxmultizone(device):
yield from ack(partial(device.get_color_zones, start_index=0))
entity = LIFXStrip(device, self.effects_conductor)
if version_resp is None or color_resp is None:
_LOGGER.error("Failed to initialize %s", device.ip_addr)
else:
entity = LIFXColor(device, self.effects_conductor)
device.timeout = MESSAGE_TIMEOUT
device.retry_count = MESSAGE_RETRIES
device.unregister_timeout = UNAVAILABLE_GRACE
_LOGGER.debug("%s register READY", entity.who)
self.entities[device.mac_addr] = entity
self.async_add_devices([entity])
if lifxwhite(device):
entity = LIFXWhite(device, self.effects_conductor)
elif lifxmultizone(device):
entity = LIFXStrip(device, self.effects_conductor)
else:
entity = LIFXColor(device, self.effects_conductor)
_LOGGER.debug("%s register READY", entity.who)
self.entities[device.mac_addr] = entity
self.async_add_devices([entity], True)
@callback
def unregister(self, device):
@ -674,9 +678,14 @@ class LIFXStrip(LIFXColor):
@asyncio.coroutine
def update_color_zones(self):
"""Get updated color information for each zone."""
ack = AwaitAioLIFX().wait
bulb = self.device
# Each get_color_zones returns the next 8 zones
for zone in range(0, len(bulb.color_zones), 8):
yield from ack(partial(bulb.get_color_zones, start_index=zone))
zone = 0
top = 1
while self.available and zone < top:
# Each get_color_zones can update 8 zones at once
resp = yield from AwaitAioLIFX().wait(partial(
self.device.get_color_zones,
start_index=zone,
end_index=zone+7))
if resp:
zone += 8
top = resp.count

View File

@ -4,64 +4,35 @@ Support for MySensors lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_WHITE_VALUE,
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_WHITE_VALUE, DOMAIN,
SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, SUPPORT_WHITE_VALUE, Light)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.util.color import rgb_hex_to_rgb_list
_LOGGER = logging.getLogger(__name__)
ATTR_VALUE = 'value'
ATTR_VALUE_TYPE = 'value_type'
SUPPORT_MYSENSORS = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR |
SUPPORT_WHITE_VALUE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for lights."""
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_DIMMER: [set_req.V_DIMMER],
}
device_class_map = {
pres.S_DIMMER: MySensorsLightDimmer,
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_RGB_LIGHT: [set_req.V_RGB],
pres.S_RGBW_LIGHT: [set_req.V_RGBW],
})
map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE)
device_class_map.update({
pres.S_RGB_LIGHT: MySensorsLightRGB,
pres.S_RGBW_LIGHT: MySensorsLightRGBW,
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, device_class_map, add_devices))
"""Setup the mysensors platform for lights."""
device_class_map = {
'S_DIMMER': MySensorsLightDimmer,
'S_RGB_LIGHT': MySensorsLightRGB,
'S_RGBW_LIGHT': MySensorsLightRGBW,
}
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, device_class_map,
add_devices=add_devices)
class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
class MySensorsLight(mysensors.MySensorsEntity, Light):
"""Representation of a MySensors Light child node."""
def __init__(self, *args):
"""Initialize a MySensors Light."""
mysensors.MySensorsDeviceEntity.__init__(self, *args)
super().__init__(*args)
self._state = None
self._brightness = None
self._rgb = None
@ -101,7 +72,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
"""Turn on light child device."""
set_req = self.gateway.const.SetReq
if self._state or set_req.V_LIGHT not in self._values:
if self._state:
return
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_LIGHT, 1)
@ -110,7 +81,6 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
# optimistically assume that light has changed state
self._state = True
self._values[set_req.V_LIGHT] = STATE_ON
self.schedule_update_ha_state()
def _turn_on_dimmer(self, **kwargs):
"""Turn on dimmer child device."""
@ -130,7 +100,6 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
# optimistically assume that light has changed state
self._brightness = brightness
self._values[set_req.V_DIMMER] = percent
self.schedule_update_ha_state()
def _turn_on_rgb_and_w(self, hex_template, **kwargs):
"""Turn on RGB or RGBW child device."""
@ -144,16 +113,11 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
return
if new_rgb is not None:
rgb = list(new_rgb)
if rgb is None:
return
if hex_template == '%02x%02x%02x%02x':
if new_white is not None:
rgb.append(new_white)
elif white is not None:
rgb.append(white)
else:
_LOGGER.error("White value is not updated for RGBW light")
return
rgb.append(white)
hex_color = hex_template % tuple(rgb)
if len(rgb) > 3:
white = rgb.pop()
@ -164,104 +128,40 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
# optimistically assume that light has changed state
self._rgb = rgb
self._white = white
if hex_color:
self._values[self.value_type] = hex_color
self.schedule_update_ha_state()
self._values[self.value_type] = hex_color
def _turn_off_light(self, value_type=None, value=None):
"""Turn off light child device."""
set_req = self.gateway.const.SetReq
value_type = (
set_req.V_LIGHT
if set_req.V_LIGHT in self._values else value_type)
value = 0 if set_req.V_LIGHT in self._values else value
return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value}
def _turn_off_dimmer(self, value_type=None, value=None):
"""Turn off dimmer child device."""
set_req = self.gateway.const.SetReq
value_type = (
set_req.V_DIMMER
if set_req.V_DIMMER in self._values else value_type)
value = 0 if set_req.V_DIMMER in self._values else value
return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value}
def _turn_off_rgb_or_w(self, value_type=None, value=None):
"""Turn off RGB or RGBW child device."""
if float(self.gateway.protocol_version) >= 1.5:
set_req = self.gateway.const.SetReq
if self.value_type == set_req.V_RGB:
value = '000000'
elif self.value_type == set_req.V_RGBW:
value = '00000000'
return {ATTR_VALUE_TYPE: self.value_type, ATTR_VALUE: value}
def _turn_off_main(self, value_type=None, value=None):
def turn_off(self):
"""Turn the device off."""
set_req = self.gateway.const.SetReq
if value_type is None or value is None:
_LOGGER.warning(
"%s: value_type %s, value = %s, None is not valid argument "
"when setting child value", self._name, value_type, value)
return
value_type = self.gateway.const.SetReq.V_LIGHT
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
self.node_id, self.child_id, value_type, 0)
if self.gateway.optimistic:
# optimistically assume that light has changed state
self._state = False
self._values[value_type] = (
STATE_OFF if set_req.V_LIGHT in self._values else value)
self._values[value_type] = STATE_OFF
self.schedule_update_ha_state()
def _update_light(self):
"""Update the controller with values from light child."""
value_type = self.gateway.const.SetReq.V_LIGHT
if value_type in self._values:
self._values[value_type] = (
STATE_ON if int(self._values[value_type]) == 1 else STATE_OFF)
self._state = self._values[value_type] == STATE_ON
self._state = self._values[value_type] == STATE_ON
def _update_dimmer(self):
"""Update the controller with values from dimmer child."""
set_req = self.gateway.const.SetReq
value_type = set_req.V_DIMMER
value_type = self.gateway.const.SetReq.V_DIMMER
if value_type in self._values:
self._brightness = round(255 * int(self._values[value_type]) / 100)
if self._brightness == 0:
self._state = False
if set_req.V_LIGHT not in self._values:
self._state = self._brightness > 0
def _update_rgb_or_w(self):
"""Update the controller with values from RGB or RGBW child."""
set_req = self.gateway.const.SetReq
value = self._values[self.value_type]
if len(value) != 6 and len(value) != 8:
_LOGGER.error(
"Wrong value %s for %s", value, set_req(self.value_type).name)
return
color_list = rgb_hex_to_rgb_list(value)
if set_req.V_LIGHT not in self._values and \
set_req.V_DIMMER not in self._values:
self._state = max(color_list) > 0
if len(color_list) > 3:
if set_req.V_RGBW != self.value_type:
_LOGGER.error(
"Wrong value %s for %s",
value, set_req(self.value_type).name)
return
self._white = color_list.pop()
self._rgb = color_list
def _update_main(self):
"""Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
self._values[value_type] = value
class MySensorsLightDimmer(MySensorsLight):
"""Dimmer child class to MySensorsLight."""
@ -270,18 +170,12 @@ class MySensorsLightDimmer(MySensorsLight):
"""Turn the device on."""
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_dimmer()
ret = self._turn_off_light(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
self._turn_off_main(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
if self.gateway.optimistic:
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
super().update()
self._update_light()
self._update_dimmer()
@ -294,20 +188,12 @@ class MySensorsLightRGB(MySensorsLight):
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
self._turn_on_rgb_and_w('%02x%02x%02x', **kwargs)
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_rgb_or_w()
ret = self._turn_off_dimmer(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
ret = self._turn_off_light(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
self._turn_off_main(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
if self.gateway.optimistic:
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
super().update()
self._update_light()
self._update_dimmer()
self._update_rgb_or_w()
@ -316,8 +202,12 @@ class MySensorsLightRGB(MySensorsLight):
class MySensorsLightRGBW(MySensorsLightRGB):
"""RGBW child class to MySensorsLightRGB."""
# pylint: disable=too-many-ancestors
def turn_on(self, **kwargs):
"""Turn the device on."""
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
self._turn_on_rgb_and_w('%02x%02x%02x%02x', **kwargs)
if self.gateway.optimistic:
self.schedule_update_ha_state()

View File

@ -14,7 +14,7 @@ from homeassistant.components.light import (
SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pwmled==1.1.1']
REQUIREMENTS = ['pwmled==1.2.1']
_LOGGER = logging.getLogger(__name__)

View File

@ -5,14 +5,17 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.tplink/
"""
import logging
import colorsys
from homeassistant.const import (CONF_HOST, CONF_NAME)
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_KELVIN,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP)
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_RGB_COLOR,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR)
from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
from homeassistant.util.color import \
color_temperature_kelvin_to_mired as kelvin_to_mired
from homeassistant.util.color import (
color_temperature_kelvin_to_mired as kelvin_to_mired)
from typing import Tuple
REQUIREMENTS = ['pyHS100==0.2.4.2']
@ -39,10 +42,26 @@ def brightness_from_percentage(percent):
return (percent*255.0)/100.0
# Travis-CI runs too old astroid https://github.com/PyCQA/pylint/issues/1212
# pylint: disable=invalid-sequence-index
def rgb_to_hsv(rgb: Tuple[float, float, float]) -> Tuple[int, int, int]:
"""Convert RGB tuple (values 0-255) to HSV (degrees, %, %)."""
hue, sat, value = colorsys.rgb_to_hsv(rgb[0]/255, rgb[1]/255, rgb[2]/255)
return int(hue * 360), int(sat * 100), int(value * 100)
# Travis-CI runs too old astroid https://github.com/PyCQA/pylint/issues/1212
# pylint: disable=invalid-sequence-index
def hsv_to_rgb(hsv: Tuple[float, float, float]) -> Tuple[int, int, int]:
"""Convert HSV tuple (degrees, %, %) to RGB (values 0-255)."""
red, green, blue = colorsys.hsv_to_rgb(hsv[0]/360, hsv[1]/100, hsv[2]/100)
return int(red * 255), int(green * 255), int(blue * 255)
class TPLinkSmartBulb(Light):
"""Representation of a TPLink Smart Bulb."""
def __init__(self, smartbulb, name):
def __init__(self, smartbulb: 'SmartBulb', name):
"""Initialize the bulb."""
self.smartbulb = smartbulb
@ -55,6 +74,7 @@ class TPLinkSmartBulb(Light):
self._state = None
self._color_temp = None
self._brightness = None
self._rgb = None
_LOGGER.debug("Setting up TP-Link Smart Bulb")
@property
@ -64,6 +84,8 @@ class TPLinkSmartBulb(Light):
def turn_on(self, **kwargs):
"""Turn the light on."""
self.smartbulb.state = self.smartbulb.BULB_STATE_ON
if ATTR_COLOR_TEMP in kwargs:
self.smartbulb.color_temp = \
mired_to_kelvin(kwargs[ATTR_COLOR_TEMP])
@ -72,7 +94,9 @@ class TPLinkSmartBulb(Light):
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)
self.smartbulb.brightness = brightness_to_percentage(brightness)
self.smartbulb.state = self.smartbulb.BULB_STATE_ON
if ATTR_RGB_COLOR in kwargs:
rgb = kwargs.get(ATTR_RGB_COLOR)
self.smartbulb.hsv = rgb_to_hsv(rgb)
def turn_off(self):
"""Turn the light off."""
@ -88,6 +112,11 @@ class TPLinkSmartBulb(Light):
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def rgb_color(self):
"""Return the color in RGB."""
return self._rgb
@property
def is_on(self):
"""True if device is on."""
@ -106,10 +135,14 @@ class TPLinkSmartBulb(Light):
self.smartbulb.color_temp != 0):
self._color_temp = kelvin_to_mired(
self.smartbulb.color_temp)
self._rgb = hsv_to_rgb(self.smartbulb.hsv)
except (SmartPlugException, OSError) as ex:
_LOGGER.warning('Could not read state for %s: %s', self.name, ex)
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_TPLINK
supported_features = SUPPORT_TPLINK
if self.smartbulb.is_color:
supported_features += SUPPORT_RGB_COLOR
return supported_features

View File

@ -6,6 +6,7 @@ https://home-assistant.io/components/light.yeelight/
"""
import logging
import colorsys
from typing import Tuple
import voluptuous as vol
@ -89,6 +90,14 @@ YEELIGHT_EFFECT_LIST = [
EFFECT_STOP]
# Travis-CI runs too old astroid https://github.com/PyCQA/pylint/issues/1212
# pylint: disable=invalid-sequence-index
def hsv_to_rgb(hsv: Tuple[float, float, float]) -> Tuple[int, int, int]:
"""Convert HSV tuple (degrees, %, %) to RGB (values 0-255)."""
red, green, blue = colorsys.hsv_to_rgb(hsv[0]/360, hsv[1]/100, hsv[2]/100)
return int(red * 255), int(green * 255), int(blue * 255)
def _cmd(func):
"""Define a wrapper to catch exceptions from the bulb."""
def _wrap(self, *args, **kwargs):
@ -192,10 +201,10 @@ class YeelightLight(Light):
if color_mode == 2: # color temperature
return color_temperature_to_rgb(self.color_temp)
if color_mode == 3: # hsv
hue = self._properties.get('hue')
sat = self._properties.get('sat')
val = self._properties.get('bright')
return colorsys.hsv_to_rgb(hue, sat, val)
hue = int(self._properties.get('hue'))
sat = int(self._properties.get('sat'))
val = int(self._properties.get('bright'))
return hsv_to_rgb((hue, sat, val))
rgb = int(rgb)
blue = rgb & 0xff
@ -214,7 +223,7 @@ class YeelightLight(Light):
return self._bulb.last_properties
@property
def _bulb(self) -> object:
def _bulb(self) -> 'yeelight.Bulb':
import yeelight
if self._bulb_device is None:
try:

View File

@ -0,0 +1,99 @@
"""
Nello.io lock platform.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/lock.nello/
"""
from itertools import filterfalse
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.lock import (LockDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME)
REQUIREMENTS = ['pynello==1.5']
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
ATTR_LOCATION_ID = 'location_id'
EVENT_DOOR_BELL = 'nello_bell_ring'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nello lock platform."""
from pynello import Nello
nello = Nello(config.get(CONF_USERNAME), config.get(CONF_PASSWORD))
add_devices([NelloLock(lock) for lock in nello.locations], True)
class NelloLock(LockDevice):
"""Representation of a Nello lock."""
def __init__(self, nello_lock):
"""Initialize the lock."""
self._nello_lock = nello_lock
self._device_attrs = None
self._activity = None
self._name = None
@property
def name(self):
"""Return the name of the lock."""
return self._name
@property
def is_locked(self):
"""Return true if lock is locked."""
return True
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return self._device_attrs
def update(self):
"""Update the nello lock properties."""
self._nello_lock.update()
# Location identifiers
location_id = self._nello_lock.location_id
short_id = self._nello_lock.short_id
address = self._nello_lock.address
self._name = 'Nello {}'.format(short_id)
self._device_attrs = {
ATTR_ADDRESS: address,
ATTR_LOCATION_ID: location_id
}
# Process recent activity
activity = self._nello_lock.activity
if self._activity:
# Filter out old events
new_activity = list(
filterfalse(lambda x: x in self._activity, activity))
if new_activity:
for act in new_activity:
activity_type = act.get('type')
if activity_type == 'bell.ring.denied':
event_data = {
'address': address,
'date': act.get('date'),
'description': act.get('description'),
'location_id': location_id,
'short_id': short_id
}
self.hass.bus.fire(EVENT_DOOR_BELL, event_data)
# Save the activity history so that we don't trigger an event twice
self._activity = activity
def unlock(self, **kwargs):
"""Unlock the device."""
if not self._nello_lock.open_door():
_LOGGER.error("Failed to unlock")

View File

@ -15,7 +15,7 @@ from homeassistant.components.media_player import (
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['youtube_dl==2017.8.6']
REQUIREMENTS = ['youtube_dl==2017.8.18']
_LOGGER = logging.getLogger(__name__)
@ -42,7 +42,7 @@ def setup(hass, config):
'media_player', 'services.yaml'))
def play_media(call):
"""Get stream URL and send it to the media_player.play_media."""
"""Get stream URL and send it to the play_media service."""
MediaExtractor(hass, config[DOMAIN], call.data).extract_and_send()
hass.services.register(DOMAIN,
@ -66,7 +66,7 @@ class MEQueryException(Exception):
pass
class MediaExtractor:
class MediaExtractor(object):
"""Class which encapsulates all extraction logic."""
def __init__(self, hass, component_config, call_data):
@ -107,15 +107,14 @@ class MediaExtractor:
ydl = YoutubeDL({'quiet': True, 'logger': _LOGGER})
try:
all_media = ydl.extract_info(self.get_media_url(),
process=False)
all_media = ydl.extract_info(self.get_media_url(), process=False)
except DownloadError:
# This exception will be logged by youtube-dl itself
raise MEDownloadException()
if 'entries' in all_media:
_LOGGER.warning("Playlists are not supported, "
"looking for the first video")
_LOGGER.warning(
"Playlists are not supported, looking for the first video")
entries = list(all_media['entries'])
if len(entries) > 0:
selected_media = entries[0]
@ -126,14 +125,14 @@ class MediaExtractor:
selected_media = all_media
def stream_selector(query):
"""Find stream url that matches query."""
"""Find stream URL that matches query."""
try:
ydl.params['format'] = query
requested_stream = ydl.process_ie_result(selected_media,
download=False)
requested_stream = ydl.process_ie_result(
selected_media, download=False)
except (ExtractorError, DownloadError):
_LOGGER.error("Could not extract stream for the query: %s",
query)
_LOGGER.error(
"Could not extract stream for the query: %s", query)
raise MEQueryException()
return requested_stream['url']
@ -141,7 +140,7 @@ class MediaExtractor:
return stream_selector
def call_media_player_service(self, stream_selector, entity_id):
"""Call media_player.play_media service."""
"""Call Media player play_media service."""
stream_query = self.get_stream_query_for_entity(entity_id)
try:
@ -164,8 +163,8 @@ class MediaExtractor:
def get_stream_query_for_entity(self, entity_id):
"""Get stream format query for entity."""
default_stream_query = self.config.get(CONF_DEFAULT_STREAM_QUERY,
DEFAULT_STREAM_QUERY)
default_stream_query = self.config.get(
CONF_DEFAULT_STREAM_QUERY, DEFAULT_STREAM_QUERY)
if entity_id:
media_content_type = self.call_data.get(ATTR_MEDIA_CONTENT_TYPE)

View File

@ -11,7 +11,6 @@ import re
import voluptuous as vol
from homeassistant.loader import get_component
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
@ -132,7 +131,7 @@ def setup_bravia(config, pin, hass, add_devices):
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Discovery configuration done")
@ -150,7 +149,7 @@ def request_configuration(config, hass, add_devices):
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
@ -171,7 +170,7 @@ def request_configuration(config, hass, add_devices):
request_configuration(config, hass, add_devices)
_CONFIGURING[host] = configurator.request_config(
hass, name, bravia_configuration_callback,
name, bravia_configuration_callback,
description='Enter the Pin shown on your Sony Bravia TV.' +
'If no Pin is shown, enter 0000 to let TV show you a Pin.',
description_image="/static/images/smart-tv.png",

View File

@ -18,7 +18,6 @@ from homeassistant.components.media_player import (
MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF, CONF_HOST, CONF_PORT, CONF_NAME)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['websocket-client==0.37.0']
@ -48,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def request_configuration(hass, config, url, add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if 'gpmdp' in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['gpmdp'], "Failed to register, please try again.")
@ -96,7 +95,7 @@ def request_configuration(hass, config, url, add_devices_callback):
break
_CONFIGURING['gpmdp'] = configurator.request_config(
hass, DEFAULT_NAME, gpmdp_configuration_callback,
DEFAULT_NAME, gpmdp_configuration_callback,
description=(
'Enter the pin that is displayed in the '
'Google Play Music Desktop Player.'),
@ -117,7 +116,7 @@ def setup_gpmdp(hass, config, code, add_devices):
return
if 'gpmdp' in _CONFIGURING:
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('gpmdp'))
add_devices([GPMDP(name, url, code)], True)

View File

@ -79,14 +79,15 @@ class MpdDevice(MediaPlayerDevice):
self._client = mpd.MPDClient()
self._client.timeout = 5
self._client.idletimeout = None
if password is not None:
self._client.password(password)
def _connect(self):
"""Connect to MPD."""
import mpd
try:
self._client.connect(self.server, self.port)
if self.password is not None:
self._client.password(self.password)
except mpd.ConnectionError:
return

View File

@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
from homeassistant.const import (STATE_OFF, STATE_ON, CONF_HOST, CONF_NAME)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['onkyo-eiscp==1.1']
REQUIREMENTS = ['onkyo-eiscp==1.2.4']
_LOGGER = logging.getLogger(__name__)

View File

@ -23,7 +23,6 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.loader import get_component
REQUIREMENTS = ['plexapi==2.0.2']
@ -143,7 +142,7 @@ def setup_plexserver(
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Discovery configuration done")
@ -236,7 +235,7 @@ def setup_plexserver(
def request_configuration(host, hass, config, add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(_CONFIGURING[host],
@ -254,7 +253,6 @@ def request_configuration(host, hass, config, add_devices_callback):
)
_CONFIGURING[host] = configurator.request_config(
hass,
'Plex Media Server',
plex_configuration_callback,
description=('Enter the X-Plex-Token'),

View File

@ -20,7 +20,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['snapcast==2.0.6']
REQUIREMENTS = ['snapcast==2.0.7']
_LOGGER = logging.getLogger(__name__)

View File

@ -10,7 +10,6 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.loader import get_component
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET,
@ -62,9 +61,9 @@ SCAN_INTERVAL = timedelta(seconds=30)
def request_configuration(hass, config, add_devices, oauth):
"""Request Spotify authorization."""
configurator = get_component('configurator')
configurator = hass.components.configurator
hass.data[DOMAIN] = configurator.request_config(
hass, DEFAULT_NAME, lambda _: None,
DEFAULT_NAME, lambda _: None,
link_name=CONFIGURATOR_LINK_NAME,
link_url=oauth.get_authorize_url(),
description=CONFIGURATOR_DESCRIPTION,
@ -88,7 +87,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
request_configuration(hass, config, add_devices, oauth)
return
if hass.data.get(DOMAIN):
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(hass.data.get(DOMAIN))
del hass.data[DOMAIN]
player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME),
@ -186,7 +185,8 @@ class SpotifyMediaPlayer(MediaPlayerDevice):
self._artist = ', '.join([artist.get('name')
for artist in item.get('artists')])
self._uri = current.get('uri')
self._image_url = item.get('album').get('images')[0].get('url')
images = item.get('album').get('images')
self._image_url = images[0].get('url') if images else None
# Playing state
self._state = STATE_PAUSED
if current.get('is_playing'):

View File

@ -95,7 +95,8 @@ class LogitechMediaServer(object):
"""Create a list of devices connected to LMS."""
result = []
data = yield from self.async_query('players', 'status')
if data is False:
return result
for players in data.get('players_loop', []):
player = SqueezeBoxDevice(
self, players['playerid'], players['name'])

View File

@ -19,10 +19,9 @@ from homeassistant.components.media_player import (
SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL,
MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, STATE_OFF,
CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, CONF_TIMEOUT, STATE_OFF,
STATE_PLAYING, STATE_PAUSED,
STATE_UNKNOWN, CONF_NAME, CONF_FILENAME)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pylgtv==0.1.7',
@ -56,7 +55,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_MAC): cv.string,
vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA,
vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string
vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string,
vol.Optional(CONF_TIMEOUT, default=10): cv.positive_int,
})
@ -79,17 +79,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
mac = config.get(CONF_MAC)
name = config.get(CONF_NAME)
customize = config.get(CONF_CUSTOMIZE)
timeout = config.get(CONF_TIMEOUT)
config = hass.config.path(config.get(CONF_FILENAME))
setup_tv(host, mac, name, customize, config, hass, add_devices)
setup_tv(host, mac, name, customize, config, timeout, hass, add_devices)
def setup_tv(host, mac, name, customize, config, hass, add_devices):
def setup_tv(host, mac, name, customize, config, timeout, hass, add_devices):
"""Set up a LG WebOS TV based on host parameter."""
from pylgtv import WebOsClient
from pylgtv import PyLGTVPairException
from websockets.exceptions import ConnectionClosed
client = WebOsClient(host, config)
client = WebOsClient(host, config, timeout)
if not client.is_registered():
if host in _CONFIGURING:
@ -100,30 +101,30 @@ def setup_tv(host, mac, name, customize, config, hass, add_devices):
_LOGGER.warning(
"Connected to LG webOS TV %s but not paired", host)
return
except (OSError, ConnectionClosed, TypeError,
asyncio.TimeoutError):
except (OSError, ConnectionClosed, asyncio.TimeoutError):
_LOGGER.error("Unable to connect to host %s", host)
return
else:
# Not registered, request configuration.
_LOGGER.warning("LG webOS TV %s needs to be paired", host)
request_configuration(
host, mac, name, customize, config, hass, add_devices)
host, mac, name, customize, config, timeout, hass, add_devices)
return
# If we came here and configuring this host, mark as done.
if client.is_registered() and host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
add_devices([LgWebOSDevice(host, mac, name, customize, config)], True)
add_devices([LgWebOSDevice(host, mac, name, customize, config, timeout)],
True)
def request_configuration(
host, mac, name, customize, config, hass, add_devices):
host, mac, name, customize, config, timeout, hass, add_devices):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
@ -133,11 +134,12 @@ def request_configuration(
# pylint: disable=unused-argument
def lgtv_configuration_callback(data):
"""Handle configuration changes."""
setup_tv(host, mac, name, customize, config, hass, add_devices)
"""The actions to do when our configuration callback is called."""
setup_tv(host, mac, name, customize, config, timeout, hass,
add_devices)
_CONFIGURING[host] = configurator.request_config(
hass, name, lgtv_configuration_callback,
name, lgtv_configuration_callback,
description='Click start and accept the pairing request on your TV.',
description_image='/static/images/config_webos.png',
submit_caption='Start pairing request'
@ -147,11 +149,11 @@ def request_configuration(
class LgWebOSDevice(MediaPlayerDevice):
"""Representation of a LG WebOS TV."""
def __init__(self, host, mac, name, customize, config):
def __init__(self, host, mac, name, customize, config, timeout):
"""Initialize the webos device."""
from pylgtv import WebOsClient
from wakeonlan import wol
self._client = WebOsClient(host, config)
self._client = WebOsClient(host, config, timeout)
self._wol = wol
self._mac = mac
self._customize = customize

View File

@ -4,30 +4,37 @@ Connect to a MySensors gateway via pymysensors API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/
"""
import asyncio
from collections import defaultdict
import logging
import os
import socket
import sys
from timeit import default_timer as timer
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.setup import setup_component
from homeassistant.components.mqtt import (
valid_publish_topic, valid_subscribe_topic)
from homeassistant.const import (
ATTR_BATTERY_LEVEL, CONF_NAME, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
from homeassistant.setup import setup_component
REQUIREMENTS = ['pymysensors==0.10.0']
REQUIREMENTS = ['pymysensors==0.11.0']
_LOGGER = logging.getLogger(__name__)
ATTR_CHILD_ID = 'child_id'
ATTR_DESCRIPTION = 'description'
ATTR_DEVICE = 'device'
ATTR_DEVICES = 'devices'
ATTR_NODE_ID = 'node_id'
CONF_BAUD_RATE = 'baud_rate'
@ -44,11 +51,16 @@ CONF_VERSION = 'version'
DEFAULT_BAUD_RATE = 115200
DEFAULT_TCP_PORT = 5003
DEFAULT_VERSION = 1.4
DEFAULT_VERSION = '1.4'
DOMAIN = 'mysensors'
MQTT_COMPONENT = 'mqtt'
MYSENSORS_GATEWAYS = 'mysensors_gateways'
MYSENSORS_PLATFORM_DEVICES = 'mysensors_devices_{}'
PLATFORM = 'platform'
SCHEMA = 'schema'
SIGNAL_CALLBACK = 'mysensors_callback_{}_{}_{}_{}'
TYPE = 'type'
def is_socket_address(value):
@ -144,11 +156,127 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
vol.Optional(CONF_PERSISTENCE, default=True): cv.boolean,
vol.Optional(CONF_RETAIN, default=True): cv.boolean,
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.Coerce(float),
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string,
}))
}, extra=vol.ALLOW_EXTRA)
# mysensors const schemas
BINARY_SENSOR_SCHEMA = {PLATFORM: 'binary_sensor', TYPE: 'V_TRIPPED'}
CLIMATE_SCHEMA = {PLATFORM: 'climate', TYPE: 'V_HVAC_FLOW_STATE'}
LIGHT_DIMMER_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_DIMMER',
SCHEMA: {'V_DIMMER': cv.string, 'V_LIGHT': cv.string}}
LIGHT_PERCENTAGE_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_PERCENTAGE',
SCHEMA: {'V_PERCENTAGE': cv.string, 'V_STATUS': cv.string}}
LIGHT_RGB_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_RGB', SCHEMA: {
'V_RGB': cv.string, 'V_STATUS': cv.string}}
LIGHT_RGBW_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_RGBW', SCHEMA: {
'V_RGBW': cv.string, 'V_STATUS': cv.string}}
NOTIFY_SCHEMA = {PLATFORM: 'notify', TYPE: 'V_TEXT'}
DEVICE_TRACKER_SCHEMA = {PLATFORM: 'device_tracker', TYPE: 'V_POSITION'}
DUST_SCHEMA = [
{PLATFORM: 'sensor', TYPE: 'V_DUST_LEVEL'},
{PLATFORM: 'sensor', TYPE: 'V_LEVEL'}]
SWITCH_LIGHT_SCHEMA = {PLATFORM: 'switch', TYPE: 'V_LIGHT'}
SWITCH_STATUS_SCHEMA = {PLATFORM: 'switch', TYPE: 'V_STATUS'}
MYSENSORS_CONST_SCHEMA = {
'S_DOOR': [BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_MOTION': [BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_SMOKE': [BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_SPRINKLER': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_STATUS'}],
'S_WATER_LEAK': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_SOUND': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_LEVEL'},
{PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_VIBRATION': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_LEVEL'},
{PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_MOISTURE': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_LEVEL'},
{PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_HVAC': [CLIMATE_SCHEMA],
'S_COVER': [
{PLATFORM: 'cover', TYPE: 'V_DIMMER'},
{PLATFORM: 'cover', TYPE: 'V_PERCENTAGE'},
{PLATFORM: 'cover', TYPE: 'V_LIGHT'},
{PLATFORM: 'cover', TYPE: 'V_STATUS'}],
'S_DIMMER': [LIGHT_DIMMER_SCHEMA, LIGHT_PERCENTAGE_SCHEMA],
'S_RGB_LIGHT': [LIGHT_RGB_SCHEMA],
'S_RGBW_LIGHT': [LIGHT_RGBW_SCHEMA],
'S_INFO': [NOTIFY_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_TEXT'}],
'S_GPS': [
DEVICE_TRACKER_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_POSITION'}],
'S_TEMP': [{PLATFORM: 'sensor', TYPE: 'V_TEMP'}],
'S_HUM': [{PLATFORM: 'sensor', TYPE: 'V_HUM'}],
'S_BARO': [
{PLATFORM: 'sensor', TYPE: 'V_PRESSURE'},
{PLATFORM: 'sensor', TYPE: 'V_FORECAST'}],
'S_WIND': [
{PLATFORM: 'sensor', TYPE: 'V_WIND'},
{PLATFORM: 'sensor', TYPE: 'V_GUST'},
{PLATFORM: 'sensor', TYPE: 'V_DIRECTION'}],
'S_RAIN': [
{PLATFORM: 'sensor', TYPE: 'V_RAIN'},
{PLATFORM: 'sensor', TYPE: 'V_RAINRATE'}],
'S_UV': [{PLATFORM: 'sensor', TYPE: 'V_UV'}],
'S_WEIGHT': [
{PLATFORM: 'sensor', TYPE: 'V_WEIGHT'},
{PLATFORM: 'sensor', TYPE: 'V_IMPEDANCE'}],
'S_POWER': [
{PLATFORM: 'sensor', TYPE: 'V_WATT'},
{PLATFORM: 'sensor', TYPE: 'V_KWH'},
{PLATFORM: 'sensor', TYPE: 'V_VAR'},
{PLATFORM: 'sensor', TYPE: 'V_VA'},
{PLATFORM: 'sensor', TYPE: 'V_POWER_FACTOR'}],
'S_DISTANCE': [{PLATFORM: 'sensor', TYPE: 'V_DISTANCE'}],
'S_LIGHT_LEVEL': [
{PLATFORM: 'sensor', TYPE: 'V_LIGHT_LEVEL'},
{PLATFORM: 'sensor', TYPE: 'V_LEVEL'}],
'S_IR': [
{PLATFORM: 'sensor', TYPE: 'V_IR_RECEIVE'},
{PLATFORM: 'switch', TYPE: 'V_IR_SEND',
SCHEMA: {'V_IR_SEND': cv.string, 'V_LIGHT': cv.string}}],
'S_WATER': [
{PLATFORM: 'sensor', TYPE: 'V_FLOW'},
{PLATFORM: 'sensor', TYPE: 'V_VOLUME'}],
'S_CUSTOM': [
{PLATFORM: 'sensor', TYPE: 'V_VAR1'},
{PLATFORM: 'sensor', TYPE: 'V_VAR2'},
{PLATFORM: 'sensor', TYPE: 'V_VAR3'},
{PLATFORM: 'sensor', TYPE: 'V_VAR4'},
{PLATFORM: 'sensor', TYPE: 'V_VAR5'},
{PLATFORM: 'sensor', TYPE: 'V_CUSTOM'}],
'S_SCENE_CONTROLLER': [
{PLATFORM: 'sensor', TYPE: 'V_SCENE_ON'},
{PLATFORM: 'sensor', TYPE: 'V_SCENE_OFF'}],
'S_COLOR_SENSOR': [{PLATFORM: 'sensor', TYPE: 'V_RGB'}],
'S_MULTIMETER': [
{PLATFORM: 'sensor', TYPE: 'V_VOLTAGE'},
{PLATFORM: 'sensor', TYPE: 'V_CURRENT'},
{PLATFORM: 'sensor', TYPE: 'V_IMPEDANCE'}],
'S_GAS': [
{PLATFORM: 'sensor', TYPE: 'V_FLOW'},
{PLATFORM: 'sensor', TYPE: 'V_VOLUME'}],
'S_WATER_QUALITY': [
{PLATFORM: 'sensor', TYPE: 'V_TEMP'},
{PLATFORM: 'sensor', TYPE: 'V_PH'},
{PLATFORM: 'sensor', TYPE: 'V_ORP'},
{PLATFORM: 'sensor', TYPE: 'V_EC'},
{PLATFORM: 'switch', TYPE: 'V_STATUS'}],
'S_AIR_QUALITY': DUST_SCHEMA,
'S_DUST': DUST_SCHEMA,
'S_LIGHT': [SWITCH_LIGHT_SCHEMA],
'S_BINARY': [SWITCH_STATUS_SCHEMA],
'S_LOCK': [{PLATFORM: 'switch', TYPE: 'V_LOCK_STATUS'}],
}
def setup(hass, config):
"""Set up the MySensors component."""
import mysensors.mysensors as mysensors
@ -197,20 +325,14 @@ def setup(hass, config):
# invalid ip address
return
gateway.metric = hass.config.units.is_metric
optimistic = config[DOMAIN].get(CONF_OPTIMISTIC)
gateway = GatewayWrapper(gateway, optimistic, device)
# pylint: disable=attribute-defined-outside-init
gateway.event_callback = gateway.callback_factory()
gateway.optimistic = config[DOMAIN].get(CONF_OPTIMISTIC)
gateway.device = device
gateway.event_callback = gw_callback_factory(hass)
def gw_start(event):
"""Trigger to start of the gateway and any persistence."""
if persistence:
for node_id in gateway.sensors:
node = gateway.sensors[node_id]
for child_id in node.children:
msg = mysensors.Message().modify(
node_id=node_id, child_id=child_id)
gateway.event_callback(msg)
discover_persistent_devices(hass, gateway)
gateway.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: gateway.stop())
@ -219,15 +341,8 @@ def setup(hass, config):
return gateway
gateways = hass.data.get(MYSENSORS_GATEWAYS)
if gateways is not None:
_LOGGER.error(
"%s already exists in %s, will not setup %s component",
MYSENSORS_GATEWAYS, hass.data, DOMAIN)
return False
# Setup all devices from config
gateways = []
gateways = {}
conf_gateways = config[DOMAIN][CONF_GATEWAYS]
for index, gway in enumerate(conf_gateways):
@ -243,7 +358,7 @@ def setup(hass, config):
device, persistence_file, baud_rate, tcp_port, in_prefix,
out_prefix)
if ready_gateway is not None:
gateways.append(ready_gateway)
gateways[id(ready_gateway)] = ready_gateway
if not gateways:
_LOGGER.error(
@ -252,115 +367,187 @@ def setup(hass, config):
hass.data[MYSENSORS_GATEWAYS] = gateways
for component in ['sensor', 'switch', 'light', 'binary_sensor', 'climate',
'cover']:
discovery.load_platform(hass, component, DOMAIN, {}, config)
discovery.load_platform(
hass, 'device_tracker', DOMAIN, {}, config)
discovery.load_platform(
hass, 'notify', DOMAIN, {CONF_NAME: DOMAIN}, config)
return True
def pf_callback_factory(map_sv_types, devices, entity_class, add_devices=None):
"""Return a new callback for the platform."""
def mysensors_callback(gateway, msg):
"""Run when a message from the gateway arrives."""
if gateway.sensors[msg.node_id].sketch_name is None:
_LOGGER.debug("No sketch_name: node %s", msg.node_id)
return
child = gateway.sensors[msg.node_id].children.get(msg.child_id)
def validate_child(gateway, node_id, child):
"""Validate that a child has the correct values according to schema.
Return a dict of platform with a list of device ids for validated devices.
"""
validated = defaultdict(list)
if not child.values:
_LOGGER.debug(
"No child values for node %s child %s", node_id, child.id)
return validated
if gateway.sensors[node_id].sketch_name is None:
_LOGGER.debug("Node %s is missing sketch name", node_id)
return validated
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
s_name = next(
(member.name for member in pres if member.value == child.type), None)
if s_name not in MYSENSORS_CONST_SCHEMA:
_LOGGER.warning("Child type %s is not supported", s_name)
return validated
child_schemas = MYSENSORS_CONST_SCHEMA[s_name]
def msg(name):
"""Return a message for an invalid schema."""
return "{} requires value_type {}".format(
pres(child.type).name, set_req[name].name)
for schema in child_schemas:
platform = schema[PLATFORM]
v_name = schema[TYPE]
value_type = next(
(member.value for member in set_req if member.name == v_name),
None)
if value_type is None:
continue
_child_schema = child.get_schema(gateway.protocol_version)
vol_schema = _child_schema.extend(
{vol.Required(set_req[key].value, msg=msg(key)):
_child_schema.schema.get(set_req[key].value, val)
for key, val in schema.get(SCHEMA, {v_name: cv.string}).items()},
extra=vol.ALLOW_EXTRA)
try:
vol_schema(child.values)
except vol.Invalid as exc:
level = (logging.WARNING if value_type in child.values
else logging.DEBUG)
_LOGGER.log(
level,
"Invalid values: %s: %s platform: node %s child %s: %s",
child.values, platform, node_id, child.id, exc)
continue
dev_id = id(gateway), node_id, child.id, value_type
validated[platform].append(dev_id)
return validated
def discover_mysensors_platform(hass, platform, new_devices):
"""Discover a mysensors platform."""
discovery.load_platform(
hass, platform, DOMAIN, {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN})
def discover_persistent_devices(hass, gateway):
"""Discover platforms for devices loaded via persistence file."""
new_devices = defaultdict(list)
for node_id in gateway.sensors:
node = gateway.sensors[node_id]
for child in node.children.values():
validated = validate_child(gateway, node_id, child)
for platform, dev_ids in validated.items():
new_devices[platform].extend(dev_ids)
for platform, dev_ids in new_devices.items():
discover_mysensors_platform(hass, platform, dev_ids)
def get_mysensors_devices(hass, domain):
"""Return mysensors devices for a platform."""
if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data:
hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] = {}
return hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)]
def gw_callback_factory(hass):
"""Return a new callback for the gateway."""
def mysensors_callback(msg):
"""Default callback for a mysensors gateway."""
start = timer()
_LOGGER.debug(
"Node update: node %s child %s", msg.node_id, msg.child_id)
child = msg.gateway.sensors[msg.node_id].children.get(msg.child_id)
if child is None:
_LOGGER.debug(
"Not a child update for node %s", msg.node_id)
return
for value_type in child.values:
key = msg.node_id, child.id, value_type
if child.type not in map_sv_types or \
value_type not in map_sv_types[child.type]:
continue
if key in devices:
if add_devices:
devices[key].schedule_update_ha_state(True)
else:
devices[key].update()
continue
name = '{} {} {}'.format(
gateway.sensors[msg.node_id].sketch_name, msg.node_id,
child.id)
if isinstance(entity_class, dict):
device_class = entity_class[child.type]
else:
device_class = entity_class
devices[key] = device_class(
gateway, msg.node_id, child.id, name, value_type)
if add_devices:
_LOGGER.info("Adding new devices: %s", [devices[key]])
add_devices([devices[key]], True)
else:
devices[key].update()
signals = []
# Update all platforms for the device via dispatcher.
# Add/update entity if schema validates to true.
validated = validate_child(msg.gateway, msg.node_id, child)
for platform, dev_ids in validated.items():
devices = get_mysensors_devices(hass, platform)
for idx, dev_id in enumerate(list(dev_ids)):
if dev_id in devices:
dev_ids.pop(idx)
signals.append(SIGNAL_CALLBACK.format(*dev_id))
if dev_ids:
discover_mysensors_platform(hass, platform, dev_ids)
for signal in set(signals):
# Only one signal per device is needed.
# A device can have multiple platforms, ie multiple schemas.
# FOR LATER: Add timer to not signal if another update comes in.
dispatcher_send(hass, signal)
end = timer()
if end - start > 0.1:
_LOGGER.debug(
"Callback for node %s child %s took %.3f seconds",
msg.node_id, msg.child_id, end - start)
return mysensors_callback
class GatewayWrapper(object):
"""Gateway wrapper class."""
def __init__(self, gateway, optimistic, device):
"""Set up the class attributes on instantiation.
Args:
gateway (mysensors.SerialGateway): Gateway to wrap.
optimistic (bool): Send values to actuators without feedback state.
device (str): Path to serial port, ip adress or mqtt.
Attributes:
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
platform_callbacks (list): Callback functions, one per platform.
optimistic (bool): Send values to actuators without feedback state.
device (str): Device configured as gateway.
__initialised (bool): True if GatewayWrapper is initialised.
"""
self._wrapped_gateway = gateway
self.platform_callbacks = []
self.optimistic = optimistic
self.device = device
self.__initialised = True
def __getattr__(self, name):
"""See if this object has attribute name."""
# Do not use hasattr, it goes into infinite recurrsion
if name in self.__dict__:
# This object has the attribute.
return getattr(self, name)
# The wrapped object has the attribute.
return getattr(self._wrapped_gateway, name)
def __setattr__(self, name, value):
"""See if this object has attribute name then set to value."""
if '_GatewayWrapper__initialised' not in self.__dict__:
return object.__setattr__(self, name, value)
elif name in self.__dict__:
object.__setattr__(self, name, value)
else:
object.__setattr__(self._wrapped_gateway, name, value)
def callback_factory(self):
"""Return a new callback function."""
def node_update(msg):
"""Handle node updates from the MySensors gateway."""
_LOGGER.debug(
"Update: node %s, child %s sub_type %s",
msg.node_id, msg.child_id, msg.sub_type)
for callback in self.platform_callbacks:
callback(self, msg)
return node_update
def get_mysensors_name(gateway, node_id, child_id):
"""Return a name for a node child."""
return '{} {} {}'.format(
gateway.sensors[node_id].sketch_name, node_id, child_id)
class MySensorsDeviceEntity(object):
"""Representation of a MySensors entity."""
def get_mysensors_gateway(hass, gateway_id):
"""Return gateway."""
if MYSENSORS_GATEWAYS not in hass.data:
hass.data[MYSENSORS_GATEWAYS] = {}
gateways = hass.data.get(MYSENSORS_GATEWAYS)
return gateways.get(gateway_id)
def setup_mysensors_platform(
hass, domain, discovery_info, device_class, device_args=None,
add_devices=None):
"""Set up a mysensors platform."""
# Only act if called via mysensors by discovery event.
# Otherwise gateway is not setup.
if not discovery_info:
return
if device_args is None:
device_args = ()
new_devices = []
new_dev_ids = discovery_info[ATTR_DEVICES]
for dev_id in new_dev_ids:
devices = get_mysensors_devices(hass, domain)
if dev_id in devices:
continue
gateway_id, node_id, child_id, value_type = dev_id
gateway = get_mysensors_gateway(hass, gateway_id)
if not gateway:
continue
device_class_copy = device_class
if isinstance(device_class, dict):
child = gateway.sensors[node_id].children[child_id]
s_type = gateway.const.Presentation(child.type).name
device_class_copy = device_class[s_type]
name = get_mysensors_name(gateway, node_id, child_id)
# python 3.4 cannot unpack inside tuple, but combining tuples works
args_copy = device_args + (
gateway, node_id, child_id, name, value_type)
devices[dev_id] = device_class_copy(*args_copy)
new_devices.append(devices[dev_id])
if new_devices:
_LOGGER.info("Adding new devices: %s", new_devices)
if add_devices is not None:
add_devices(new_devices, True)
return new_devices
class MySensorsDevice(object):
"""Representation of a MySensors device."""
def __init__(self, gateway, node_id, child_id, name, value_type):
"""Set up the MySensors device."""
@ -373,11 +560,6 @@ class MySensorsDeviceEntity(object):
self.child_type = child.type
self._values = {}
@property
def should_poll(self):
"""Mysensor gateway pushes its state to HA."""
return False
@property
def name(self):
"""Return the name of this entity."""
@ -399,18 +581,9 @@ class MySensorsDeviceEntity(object):
set_req = self.gateway.const.SetReq
for value_type, value in self._values.items():
try:
attr[set_req(value_type).name] = value
except ValueError:
_LOGGER.error("Value_type %s is not valid for mysensors "
"version %s", value_type,
self.gateway.protocol_version)
return attr
attr[set_req(value_type).name] = value
@property
def available(self):
"""Return true if entity is available."""
return self.value_type in self._values
return attr
def update(self):
"""Update the controller with the latest value from a sensor."""
@ -419,7 +592,8 @@ class MySensorsDeviceEntity(object):
set_req = self.gateway.const.SetReq
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
"Entity update: %s: value_type %s, value = %s",
self._name, value_type, value)
if value_type in (set_req.V_ARMED, set_req.V_LIGHT,
set_req.V_LOCK_STATUS, set_req.V_TRIPPED):
self._values[value_type] = (
@ -428,3 +602,29 @@ class MySensorsDeviceEntity(object):
self._values[value_type] = int(value)
else:
self._values[value_type] = value
class MySensorsEntity(MySensorsDevice, Entity):
"""Representation of a MySensors entity."""
@property
def should_poll(self):
"""Mysensor gateway pushes its state to HA."""
return False
@property
def available(self):
"""Return true if entity is available."""
return self.value_type in self._values
def _async_update_callback(self):
"""Update the entity."""
self.hass.async_add_job(self.async_update_ha_state(True))
@asyncio.coroutine
def async_added_to_hass(self):
"""Register update callback."""
dev_id = id(self.gateway), self.node_id, self.child_id, self.value_type
async_dispatcher_connect(
self.hass, SIGNAL_CALLBACK.format(*dev_id),
self._async_update_callback)

View File

@ -14,7 +14,6 @@ from homeassistant.helpers import discovery
from homeassistant.const import (
CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS,
CONF_MONITORED_CONDITIONS)
from homeassistant.loader import get_component
REQUIREMENTS = ['python-nest==3.1.0']
@ -54,7 +53,7 @@ CONFIG_SCHEMA = vol.Schema({
def request_configuration(nest, hass, config):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if 'nest' in _CONFIGURING:
_LOGGER.debug("configurator failed")
configurator.notify_errors(
@ -68,7 +67,7 @@ def request_configuration(nest, hass, config):
setup_nest(hass, nest, config, pin=pin)
_CONFIGURING['nest'] = configurator.request_config(
hass, "Nest", nest_configuration_callback,
"Nest", nest_configuration_callback,
description=('To configure Nest, click Request Authorization below, '
'log into your Nest account, '
'and then enter the resulting PIN'),
@ -92,7 +91,7 @@ def setup_nest(hass, nest, config, pin=None):
if 'nest' in _CONFIGURING:
_LOGGER.debug("configuration done")
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('nest'))
_LOGGER.debug("proceeding with setup")

View File

@ -4,16 +4,18 @@ Discord platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.discord/
"""
import logging
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
PLATFORM_SCHEMA, BaseNotificationService, ATTR_TARGET)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['discord.py==0.16.8']
REQUIREMENTS = ['discord.py==0.16.10']
CONF_TOKEN = 'token'
@ -42,13 +44,22 @@ class DiscordNotificationService(BaseNotificationService):
import discord
discord_bot = discord.Client(loop=self.hass.loop)
if ATTR_TARGET not in kwargs:
_LOGGER.error("No target specified")
return None
# pylint: disable=unused-variable
@discord_bot.event
@asyncio.coroutine
def on_ready(): # pylint: disable=unused-variable
def on_ready():
"""Send the messages when the bot is ready."""
for channelid in kwargs[ATTR_TARGET]:
channel = discord.Object(id=channelid)
yield from discord_bot.send_message(channel, message)
try:
for channelid in kwargs[ATTR_TARGET]:
channel = discord.Object(id=channelid)
yield from discord_bot.send_message(channel, message)
except (discord.errors.HTTPException,
discord.errors.NotFound) as error:
_LOGGER.warning("Communication error: %s", error)
yield from discord_bot.logout()
yield from discord_bot.close()

View File

@ -0,0 +1,97 @@
"""
HipChat platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.hipchat/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService)
from homeassistant.const import CONF_TOKEN, CONF_HOST
REQUIREMENTS = ['hipnotify==1.0.8']
_LOGGER = logging.getLogger(__name__)
CONF_COLOR = 'color'
CONF_ROOM = 'room'
CONF_NOTIFY = 'notify'
CONF_FORMAT = 'format'
DEFAULT_COLOR = 'yellow'
DEFAULT_FORMAT = 'text'
DEFAULT_HOST = 'https://api.hipchat.com/'
DEFAULT_NOTIFY = False
VALID_COLORS = {'yellow', 'green', 'red', 'purple', 'gray', 'random'}
VALID_FORMATS = {'text', 'html'}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ROOM): vol.Coerce(int),
vol.Required(CONF_TOKEN): cv.string,
vol.Optional(CONF_COLOR, default=DEFAULT_COLOR): vol.In(VALID_COLORS),
vol.Optional(CONF_FORMAT, default=DEFAULT_FORMAT): vol.In(VALID_FORMATS),
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NOTIFY, default=DEFAULT_NOTIFY): cv.boolean,
})
def get_service(hass, config, discovery_info=None):
"""Get the HipChat notification service."""
return HipchatNotificationService(
config[CONF_TOKEN], config[CONF_ROOM], config[CONF_COLOR],
config[CONF_NOTIFY], config[CONF_FORMAT], config[CONF_HOST])
class HipchatNotificationService(BaseNotificationService):
"""Implement the notification service for HipChat."""
def __init__(self, token, default_room, default_color, default_notify,
default_format, host):
"""Initialize the service."""
self._token = token
self._default_room = default_room
self._default_color = default_color
self._default_notify = default_notify
self._default_format = default_format
self._host = host
self._rooms = {}
self._get_room(self._default_room)
def _get_room(self, room):
"""Get Room object, creating it if necessary."""
from hipnotify import Room
if room not in self._rooms:
self._rooms[room] = Room(
token=self._token, room_id=room, endpoint_url=self._host)
return self._rooms[room]
def send_message(self, message="", **kwargs):
"""Send a message."""
color = self._default_color
notify = self._default_notify
message_format = self._default_format
if kwargs.get(ATTR_DATA) is not None:
data = kwargs.get(ATTR_DATA)
if ((data.get(CONF_COLOR) is not None)
and (data.get(CONF_COLOR) in VALID_COLORS)):
color = data.get(CONF_COLOR)
if ((data.get(CONF_NOTIFY) is not None)
and isinstance(data.get(CONF_NOTIFY), bool)):
notify = data.get(CONF_NOTIFY)
if ((data.get(CONF_FORMAT) is not None)
and (data.get(CONF_FORMAT) in VALID_FORMATS)):
message_format = data.get(CONF_FORMAT)
targets = kwargs.get(ATTR_TARGET, [self._default_room])
for target in targets:
room = self._get_room(target)
room.notify(msg=message, color=color, notify=notify,
message_format=message_format)

View File

@ -6,35 +6,19 @@ https://home-assistant.io/components/notify.mysensors/
"""
from homeassistant.components import mysensors
from homeassistant.components.notify import (
ATTR_TARGET, BaseNotificationService)
ATTR_TARGET, DOMAIN, BaseNotificationService)
def get_service(hass, config, discovery_info=None):
"""Get the MySensors notification service."""
if discovery_info is None:
new_devices = mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsNotificationDevice)
if not new_devices:
return
platform_devices = []
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
if float(gateway.protocol_version) < 2.0:
continue
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_INFO: [set_req.V_TEXT],
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsNotificationDevice))
platform_devices.append(devices)
return MySensorsNotificationService(platform_devices)
return MySensorsNotificationService(hass)
class MySensorsNotificationDevice(mysensors.MySensorsDeviceEntity):
class MySensorsNotificationDevice(mysensors.MySensorsDevice):
"""Represent a MySensors Notification device."""
def send_msg(self, msg):
@ -44,24 +28,25 @@ class MySensorsNotificationDevice(mysensors.MySensorsDeviceEntity):
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, sub_msg)
def __repr__(self):
"""Return the representation."""
return "<MySensorsNotificationDevice {}>".format(self.name)
class MySensorsNotificationService(BaseNotificationService):
"""Implement MySensors notification service."""
"""Implement a MySensors notification service."""
# pylint: disable=too-few-public-methods
def __init__(self, platform_devices):
def __init__(self, hass):
"""Initialize the service."""
self.platform_devices = platform_devices
self.devices = mysensors.get_mysensors_devices(hass, DOMAIN)
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
target_devices = kwargs.get(ATTR_TARGET)
devices = []
for gw_devs in self.platform_devices:
for device in gw_devs.values():
if target_devices is None or device.name in target_devices:
devices.append(device)
devices = [device for device in self.devices.values()
if target_devices is None or device.name in target_devices]
for device in devices:
device.send_msg(message)

View File

@ -0,0 +1,70 @@
"""
Prowl notification service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.prowl/
"""
import logging
import asyncio
import async_timeout
import voluptuous as vol
from homeassistant.components.notify import (
ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, PLATFORM_SCHEMA,
BaseNotificationService)
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
_LOGGER = logging.getLogger(__name__)
_RESOURCE = 'https://api.prowlapp.com/publicapi/'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
})
@asyncio.coroutine
def async_get_service(hass, config, discovery_info=None):
"""Get the Prowl notification service."""
return ProwlNotificationService(hass, config[CONF_API_KEY])
class ProwlNotificationService(BaseNotificationService):
"""Implement the notification service for Prowl."""
def __init__(self, hass, api_key):
"""Initialize the service."""
self._hass = hass
self._api_key = api_key
@asyncio.coroutine
def async_send_message(self, message, **kwargs):
"""Send the message to the user."""
response = None
session = None
url = '{}{}'.format(_RESOURCE, 'add')
data = kwargs.get(ATTR_DATA)
payload = {
'apikey': self._api_key,
'application': 'Home-Assistant',
'event': kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT),
'description': message,
'priority': data['priority'] if data and 'priority' in data else 0
}
_LOGGER.debug("Attempting call Prowl service at %s", url)
session = async_get_clientsession(self._hass)
try:
with async_timeout.timeout(10, loop=self._hass.loop):
response = yield from session.post(url, data=payload)
result = yield from response.text()
if response.status != 200 or 'error' in result:
_LOGGER.error("Prowl service returned http "
"status %d, response %s",
response.status, result)
except asyncio.TimeoutError:
_LOGGER.error("Timeout accessing Prowl at %s", url)

View File

@ -89,15 +89,7 @@ class PushBulletNotificationService(BaseNotificationService):
if not targets:
# Backward compatibility, notify all devices in own account
if url:
self.pushbullet.push_link(title, url, body=message)
if filepath and self.hass.config.is_allowed_path(filepath):
with open(filepath, "rb") as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
self.pushbullet.push_file(title=title, body=message,
**filedata)
else:
self.pushbullet.push_note(title, message)
self._push_data(filepath, message, title, url)
_LOGGER.info("Sent notification to self")
return
@ -112,16 +104,7 @@ class PushBulletNotificationService(BaseNotificationService):
# Target is email, send directly, don't use a target object
# This also seems works to send to all devices in own account
if ttype == 'email':
if url:
self.pushbullet.push_link(
title, url, body=message, email=tname)
if filepath and self.hass.config.is_allowed_path(filepath):
with open(filepath, "rb") as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
self.pushbullet.push_file(title=title, body=message,
**filedata)
else:
self.pushbullet.push_note(title, message, email=tname)
self._push_data(filepath, message, title, url, tname)
_LOGGER.info("Sent notification to email %s", tname)
continue
@ -152,3 +135,15 @@ class PushBulletNotificationService(BaseNotificationService):
except self.pushbullet.errors.PushError:
_LOGGER.error("Notify failed to: %s/%s", ttype, tname)
continue
def _push_data(self, filepath, message, title, url, tname=None):
if url:
self.pushbullet.push_link(
title, url, body=message, email=tname)
elif filepath and self.hass.config.is_allowed_path(filepath):
with open(filepath, "rb") as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
self.pushbullet.push_file(title=title, body=message,
**filedata)
else:
self.pushbullet.push_note(title, message, email=tname)

View File

@ -14,7 +14,7 @@ from homeassistant.components.notify import (
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-pushover==0.2']
REQUIREMENTS = ['python-pushover==0.3']
_LOGGER = logging.getLogger(__name__)

View File

@ -13,7 +13,7 @@ from homeassistant.components.notify import (
from homeassistant.const import (CONF_API_KEY, CONF_SENDER, CONF_RECIPIENT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['sendgrid==4.2.1']
REQUIREMENTS = ['sendgrid==5.0.0']
_LOGGER = logging.getLogger(__name__)

View File

@ -5,20 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.slack/
"""
import logging
import requests
from requests.auth import HTTPDigestAuth
from requests.auth import HTTPBasicAuth
import requests
from requests.auth import HTTPBasicAuth
from requests.auth import HTTPDigestAuth
import voluptuous as vol
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_TITLE, ATTR_DATA,
PLATFORM_SCHEMA, BaseNotificationService)
from homeassistant.const import (
CONF_API_KEY, CONF_USERNAME, CONF_ICON)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_TITLE, ATTR_DATA, PLATFORM_SCHEMA,
BaseNotificationService)
from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_ICON)
REQUIREMENTS = ['slacker==0.9.50']
REQUIREMENTS = ['slacker==0.9.60']
_LOGGER = logging.getLogger(__name__)
@ -34,7 +33,7 @@ ATTR_FILE_PATH = 'path'
ATTR_FILE_USERNAME = 'username'
ATTR_FILE_PASSWORD = 'password'
ATTR_FILE_AUTH = 'auth'
# Any other value or absense of 'auth' lead to basic authentication being used
# Any other value or absence of 'auth' lead to basic authentication being used
ATTR_FILE_AUTH_DIGEST = 'digest'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -49,14 +48,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_service(hass, config, discovery_info=None):
"""Get the Slack notification service."""
import slacker
channel = config.get(CONF_CHANNEL)
api_key = config.get(CONF_API_KEY)
username = config.get(CONF_USERNAME)
icon = config.get(CONF_ICON)
try:
return SlackNotificationService(
config[CONF_CHANNEL],
config[CONF_API_KEY],
config.get(CONF_USERNAME, None),
config.get(CONF_ICON, None),
hass.config.is_allowed_path)
channel, api_key, username, icon, hass.config.is_allowed_path)
except slacker.Error:
_LOGGER.exception("Authentication failed")
@ -66,9 +65,8 @@ def get_service(hass, config, discovery_info=None):
class SlackNotificationService(BaseNotificationService):
"""Implement the notification service for Slack."""
def __init__(self, default_channel,
api_token, username,
icon, is_allowed_path):
def __init__(
self, default_channel, api_token, username, icon, is_allowed_path):
"""Initialize the service."""
from slacker import Slacker
self._default_channel = default_channel
@ -101,7 +99,7 @@ class SlackNotificationService(BaseNotificationService):
for target in targets:
try:
if file is not None:
# Load from file or url
# Load from file or URL
file_as_bytes = self.load_file(
url=file.get(ATTR_FILE_URL),
local_path=file.get(ATTR_FILE_PATH),
@ -113,7 +111,7 @@ class SlackNotificationService(BaseNotificationService):
filename = file.get(ATTR_FILE_URL)
else:
filename = file.get(ATTR_FILE_PATH)
# Prepare structure for slack API
# Prepare structure for Slack API
data = {
'content': None,
'filetype': None,
@ -135,35 +133,33 @@ class SlackNotificationService(BaseNotificationService):
except slacker.Error as err:
_LOGGER.error("Could not send notification. Error: %s", err)
def load_file(self, url=None, local_path=None,
username=None, password=None, auth=None):
"""Load image/document/etc from a local path or url."""
def load_file(self, url=None, local_path=None, username=None,
password=None, auth=None):
"""Load image/document/etc from a local path or URL."""
try:
if url is not None:
# check whether authentication parameters are provided
# Check whether authentication parameters are provided
if username is not None and password is not None:
# Use digest or basic authentication
if ATTR_FILE_AUTH_DIGEST == auth:
auth_ = HTTPDigestAuth(username, password)
else:
auth_ = HTTPBasicAuth(username, password)
# load file from url with authentication
# Load file from URL with authentication
req = requests.get(url, auth=auth_, timeout=CONF_TIMEOUT)
else:
# load file from url without authentication
# Load file from URL without authentication
req = requests.get(url, timeout=CONF_TIMEOUT)
return req.content
elif local_path is not None:
# Check whether path is whitelisted in configuration.yaml
if self.is_allowed_path(local_path):
# load file from local path on server
return open(local_path, "rb")
_LOGGER.warning("'%s' is not secure to load data from!",
local_path)
else:
# neither url nor path provided
_LOGGER.warning("Neither url nor local path found in params!")
_LOGGER.warning("Neither URL nor local path found in params!")
except OSError as error:
_LOGGER.error("Can't load from url or local path: %s", error)

View File

@ -16,11 +16,15 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'octoprint'
CONF_NUMBER_OF_TOOLS = 'number_of_tools'
CONF_BED = 'bed'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NUMBER_OF_TOOLS, default=0): cv.positive_int,
vol.Optional(CONF_BED, default=False): cv.boolean
}),
}, extra=vol.ALLOW_EXTRA)
@ -29,11 +33,13 @@ def setup(hass, config):
"""Set up the OctoPrint component."""
base_url = 'http://{}/api/'.format(config[DOMAIN][CONF_HOST])
api_key = config[DOMAIN][CONF_API_KEY]
number_of_tools = config[DOMAIN][CONF_NUMBER_OF_TOOLS]
bed = config[DOMAIN][CONF_BED]
hass.data[DOMAIN] = {"api": None}
try:
octoprint_api = OctoPrintAPI(base_url, api_key)
octoprint_api = OctoPrintAPI(base_url, api_key, bed, number_of_tools)
hass.data[DOMAIN]["api"] = octoprint_api
octoprint_api.get('printer')
octoprint_api.get('job')
@ -46,7 +52,7 @@ def setup(hass, config):
class OctoPrintAPI(object):
"""Simple JSON wrapper for OctoPrint's API."""
def __init__(self, api_url, key):
def __init__(self, api_url, key, bed, number_of_tools):
"""Initialize OctoPrint API and set headers needed later."""
self.api_url = api_url
self.headers = {'content-type': CONTENT_TYPE_JSON,
@ -58,11 +64,23 @@ class OctoPrintAPI(object):
self.available = False
self.printer_error_logged = False
self.job_error_logged = False
self.bed = bed
self.number_of_tools = number_of_tools
_LOGGER.error(str(bed) + " " + str(number_of_tools))
def get_tools(self):
"""Get the dynamic list of tools that temperature is monitored on."""
tools = self.printer_last_reading[0]['temperature']
return tools.keys()
"""Get the list of tools that temperature is monitored on."""
tools = []
if self.number_of_tools > 0:
for tool_number in range(0, self.number_of_tools):
tools.append("tool" + str(tool_number))
if self.bed:
tools.append('bed')
if not self.bed and self.number_of_tools == 0:
temps = self.printer_last_reading[0].get('temperature')
if temps is not None:
tools = temps.keys()
return tools
def get(self, endpoint):
"""Send a get request, and return the response as a dict."""

View File

@ -12,16 +12,18 @@ from aiohttp import web
from homeassistant.components.http import HomeAssistantView
from homeassistant.components import recorder
from homeassistant.const import (CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE,
CONF_INCLUDE, EVENT_STATE_CHANGED,
TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.const import (
CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE, TEMP_CELSIUS,
EVENT_STATE_CHANGED, TEMP_FAHRENHEIT, CONTENT_TYPE_TEXT_PLAIN)
from homeassistant import core as hacore
from homeassistant.helpers import state as state_helper
from homeassistant.util.temperature import fahrenheit_to_celsius
REQUIREMENTS = ['prometheus_client==0.0.19']
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['prometheus_client==0.0.19']
API_ENDPOINT = '/api/prometheus'
DOMAIN = 'prometheus'
DEPENDENCIES = ['http']
@ -30,8 +32,6 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: recorder.FILTER_SCHEMA,
}, extra=vol.ALLOW_EXTRA)
API_ENDPOINT = '/api/prometheus'
def setup(hass, config):
"""Activate Prometheus component."""
@ -45,11 +45,10 @@ def setup(hass, config):
metrics = Metrics(prometheus_client, exclude, include)
hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_event)
return True
class Metrics:
class Metrics(object):
"""Model all of the metrics which should be exposed to Prometheus."""
def __init__(self, prometheus_client, exclude, include):
@ -81,7 +80,7 @@ class Metrics:
entity_id not in self.include_entities):
return
handler = '_handle_' + domain
handler = '_handle_{}'.format(domain)
if hasattr(self, handler):
getattr(self, handler)(state)
@ -233,8 +232,8 @@ class PrometheusView(HomeAssistantView):
@asyncio.coroutine
def get(self, request):
"""Handle request for Prometheus metrics."""
_LOGGER.debug('Received Prometheus metrics request')
_LOGGER.debug("Received Prometheus metrics request")
return web.Response(
body=self.prometheus_client.generate_latest(),
content_type="text/plain")
content_type=CONTENT_TYPE_TEXT_PLAIN)

View File

@ -39,13 +39,13 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_SCRIPTS = 'all scripts'
_SCRIPT_ENTRY_SCHEMA = vol.Schema({
SCRIPT_ENTRY_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string,
vol.Required(CONF_SEQUENCE): cv.SCRIPT_SCHEMA,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({cv.slug: _SCRIPT_ENTRY_SCHEMA})
DOMAIN: vol.Schema({cv.slug: SCRIPT_ENTRY_SCHEMA})
}, extra=vol.ALLOW_EXTRA)
SCRIPT_SERVICE_SCHEMA = vol.Schema(dict)
@ -62,12 +62,6 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON)
@bind_hass
def reload(hass):
"""Reload script component."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
@bind_hass
def turn_on(hass, entity_id, variables=None):
"""Turn script on."""
@ -88,6 +82,21 @@ def toggle(hass, entity_id):
hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
@bind_hass
def reload(hass):
"""Reload script component."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
@bind_hass
def async_reload(hass):
"""Reload the scripts from config.
Returns a coroutine object.
"""
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
@asyncio.coroutine
def async_setup(hass, config):
"""Load the scripts from the configuration."""

View File

@ -23,12 +23,14 @@ from homeassistant.helpers.event import (
async_track_point_in_utc_time)
from homeassistant.util import dt as dt_util
REQUIREMENTS = ['buienradar==0.8']
REQUIREMENTS = ['buienradar==0.9']
_LOGGER = logging.getLogger(__name__)
MEASURED_LABEL = 'Measured'
TIMEFRAME_LABEL = 'Timeframe'
SYMBOL = 'symbol'
# Schedule next call after (minutes):
SCHEDULE_OK = 10
# When an error occurred, new call after (minutes):
@ -38,6 +40,10 @@ SCHEDULE_NOK = 2
# Key: ['label', unit, icon]
SENSOR_TYPES = {
'stationname': ['Stationname', None, None],
'condition': ['Condition', None, None],
'conditioncode': ['Condition code', None, None],
'conditiondetailed': ['Detailed condition', None, None],
'conditionexact': ['Full condition', None, None],
'symbol': ['Symbol', None, None],
'humidity': ['Humidity', '%', 'mdi:water-percent'],
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
@ -55,7 +61,67 @@ SENSOR_TYPES = {
'precipitation_forecast_average': ['Precipitation forecast average',
'mm/h', 'mdi:weather-pouring'],
'precipitation_forecast_total': ['Precipitation forecast total',
'mm', 'mdi:weather-pouring']
'mm', 'mdi:weather-pouring'],
'temperature_1d': ['Temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_2d': ['Temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_3d': ['Temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_4d': ['Temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_5d': ['Temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_1d': ['Minimum temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_2d': ['Minimum temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_3d': ['Minimum temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_4d': ['Minimum temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_5d': ['Minimum temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'],
'rain_1d': ['Rain 1d', 'mm', 'mdi:weather-pouring'],
'rain_2d': ['Rain 2d', 'mm', 'mdi:weather-pouring'],
'rain_3d': ['Rain 3d', 'mm', 'mdi:weather-pouring'],
'rain_4d': ['Rain 4d', 'mm', 'mdi:weather-pouring'],
'rain_5d': ['Rain 5d', 'mm', 'mdi:weather-pouring'],
'snow_1d': ['Snow 1d', 'cm', 'mdi:snowflake'],
'snow_2d': ['Snow 2d', 'cm', 'mdi:snowflake'],
'snow_3d': ['Snow 3d', 'cm', 'mdi:snowflake'],
'snow_4d': ['Snow 4d', 'cm', 'mdi:snowflake'],
'snow_5d': ['Snow 5d', 'cm', 'mdi:snowflake'],
'rainchance_1d': ['Rainchance 1d', '%', 'mdi:weather-pouring'],
'rainchance_2d': ['Rainchance 2d', '%', 'mdi:weather-pouring'],
'rainchance_3d': ['Rainchance 3d', '%', 'mdi:weather-pouring'],
'rainchance_4d': ['Rainchance 4d', '%', 'mdi:weather-pouring'],
'rainchance_5d': ['Rainchance 5d', '%', 'mdi:weather-pouring'],
'sunchance_1d': ['Sunchance 1d', '%', 'mdi:weather-partlycloudy'],
'sunchance_2d': ['Sunchance 2d', '%', 'mdi:weather-partlycloudy'],
'sunchance_3d': ['Sunchance 3d', '%', 'mdi:weather-partlycloudy'],
'sunchance_4d': ['Sunchance 4d', '%', 'mdi:weather-partlycloudy'],
'sunchance_5d': ['Sunchance 5d', '%', 'mdi:weather-partlycloudy'],
'windforce_1d': ['Wind force 1d', 'Bft', 'mdi:weather-windy'],
'windforce_2d': ['Wind force 2d', 'Bft', 'mdi:weather-windy'],
'windforce_3d': ['Wind force 3d', 'Bft', 'mdi:weather-windy'],
'windforce_4d': ['Wind force 4d', 'Bft', 'mdi:weather-windy'],
'windforce_5d': ['Wind force 5d', 'Bft', 'mdi:weather-windy'],
'condition_1d': ['Condition 1d', None, None],
'condition_2d': ['Condition 2d', None, None],
'condition_3d': ['Condition 3d', None, None],
'condition_4d': ['Condition 4d', None, None],
'condition_5d': ['Condition 5d', None, None],
'conditioncode_1d': ['Condition code 1d', None, None],
'conditioncode_2d': ['Condition code 2d', None, None],
'conditioncode_3d': ['Condition code 3d', None, None],
'conditioncode_4d': ['Condition code 4d', None, None],
'conditioncode_5d': ['Condition code 5d', None, None],
'conditiondetailed_1d': ['Detailed condition 1d', None, None],
'conditiondetailed_2d': ['Detailed condition 2d', None, None],
'conditiondetailed_3d': ['Detailed condition 3d', None, None],
'conditiondetailed_4d': ['Detailed condition 4d', None, None],
'conditiondetailed_5d': ['Detailed condition 5d', None, None],
'conditionexact_1d': ['Full condition 1d', None, None],
'conditionexact_2d': ['Full condition 2d', None, None],
'conditionexact_3d': ['Full condition 3d', None, None],
'conditionexact_4d': ['Full condition 4d', None, None],
'conditionexact_5d': ['Full condition 5d', None, None],
'symbol_1d': ['Symbol 1d', None, None],
'symbol_2d': ['Symbol 2d', None, None],
'symbol_3d': ['Symbol 3d', None, None],
'symbol_4d': ['Symbol 4d', None, None],
'symbol_5d': ['Symbol 5d', None, None],
}
CONF_TIMEFRAME = 'timeframe'
@ -126,23 +192,86 @@ class BrSensor(Entity):
def load_data(self, data):
"""Load the sensor with relevant data."""
# Find sensor
from buienradar.buienradar import (ATTRIBUTION, IMAGE, MEASURED,
from buienradar.buienradar import (ATTRIBUTION, CONDITION, CONDCODE,
DETAILED, EXACT, EXACTNL, FORECAST,
IMAGE, MEASURED,
PRECIPITATION_FORECAST, STATIONNAME,
SYMBOL, TIMEFRAME)
TIMEFRAME)
self._attribution = data.get(ATTRIBUTION)
self._stationname = data.get(STATIONNAME)
self._measured = data.get(MEASURED)
if self.type == SYMBOL:
# update weather symbol & status text
new_state = data.get(self.type)
img = data.get(IMAGE)
# pylint: disable=protected-access
if new_state != self._state or img != self._entity_picture:
self._state = new_state
self._entity_picture = img
return True
if self.type.endswith('_1d') or \
self.type.endswith('_2d') or \
self.type.endswith('_3d') or \
self.type.endswith('_4d') or \
self.type.endswith('_5d'):
fcday = 0
if self.type.endswith('_2d'):
fcday = 1
if self.type.endswith('_3d'):
fcday = 2
if self.type.endswith('_4d'):
fcday = 3
if self.type.endswith('_5d'):
fcday = 4
# update all other sensors
if self.type.startswith(SYMBOL) or self.type.startswith(CONDITION):
condition = data.get(FORECAST)[fcday].get(CONDITION)
if condition:
new_state = condition.get(CONDITION, None)
if self.type.startswith(SYMBOL):
new_state = condition.get(EXACTNL, None)
if self.type.startswith('conditioncode'):
new_state = condition.get(CONDCODE, None)
if self.type.startswith('conditiondetailed'):
new_state = condition.get(DETAILED, None)
if self.type.startswith('conditionexact'):
new_state = condition.get(EXACT, None)
img = condition.get(IMAGE, None)
if new_state != self._state or img != self._entity_picture:
self._state = new_state
self._entity_picture = img
return True
return False
else:
new_state = data.get(FORECAST)[fcday].get(self.type[:-3])
if new_state != self._state:
self._state = new_state
return True
return False
return False
if self.type == SYMBOL or self.type.startswith(CONDITION):
# update weather symbol & status text
condition = data.get(CONDITION, None)
if condition:
if self.type == SYMBOL:
new_state = condition.get(EXACTNL, None)
if self.type == CONDITION:
new_state = condition.get(CONDITION, None)
if self.type == 'conditioncode':
new_state = condition.get(CONDCODE, None)
if self.type == 'conditiondetailed':
new_state = condition.get(DETAILED, None)
if self.type == 'conditionexact':
new_state = condition.get(EXACT, None)
img = condition.get(IMAGE, None)
# pylint: disable=protected-access
if new_state != self._state or img != self._entity_picture:
self._state = new_state
self._entity_picture = img
return True
return False
if self.type.startswith(PRECIPITATION_FORECAST):
@ -187,11 +316,6 @@ class BrSensor(Entity):
@property
def entity_picture(self):
"""Weather symbol if type is symbol."""
from buienradar.buienradar import SYMBOL
if self.type != SYMBOL:
return None
return self._entity_picture
@property
@ -360,8 +484,8 @@ class BrData(object):
@property
def condition(self):
"""Return the condition."""
from buienradar.buienradar import SYMBOL
return self.data.get(SYMBOL)
from buienradar.buienradar import CONDITION
return self.data.get(CONDITION)
@property
def temperature(self):
@ -390,6 +514,15 @@ class BrData(object):
except (ValueError, TypeError):
return None
@property
def visibility(self):
"""Return the visibility, or None."""
from buienradar.buienradar import VISIBILITY
try:
return int(self.data.get(VISIBILITY))
except (ValueError, TypeError):
return None
@property
def wind_speed(self):
"""Return the windspeed, or None."""
@ -402,9 +535,9 @@ class BrData(object):
@property
def wind_bearing(self):
"""Return the wind bearing, or None."""
from buienradar.buienradar import WINDDIRECTION
from buienradar.buienradar import WINDAZIMUTH
try:
return int(self.data.get(WINDDIRECTION))
return int(self.data.get(WINDAZIMUTH))
except (ValueError, TypeError):
return None

View File

@ -4,16 +4,17 @@ Counter for the days till a HTTPS (TLS) certificate will expire.
For more details about this sensor please refer to the documentation at
https://home-assistant.io/components/sensor.cert_expiry/
"""
import datetime
import logging
import socket
import ssl
from datetime import datetime, timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT)
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT,
EVENT_HOMEASSISTANT_START)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
@ -21,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SSL Certificate Expiry'
DEFAULT_PORT = 443
SCAN_INTERVAL = datetime.timedelta(hours=12)
SCAN_INTERVAL = timedelta(hours=12)
TIMEOUT = 10.0
@ -34,11 +35,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up certificate expiry sensor."""
server_name = config.get(CONF_HOST)
server_port = config.get(CONF_PORT)
sensor_name = config.get(CONF_NAME)
def run_setup(event):
"""Wait until Home Assistant is fully initialized before creating.
add_devices([SSLCertificate(sensor_name, server_name, server_port)], True)
Delay the setup until Home Assistant is fully initialized.
"""
server_name = config.get(CONF_HOST)
server_port = config.get(CONF_PORT)
sensor_name = config.get(CONF_NAME)
add_devices([SSLCertificate(sensor_name, server_name, server_port)],
True)
# To allow checking of the HA certificate we must first be running.
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)
class SSLCertificate(Entity):
@ -97,6 +107,6 @@ class SSLCertificate(Entity):
return
ts_seconds = ssl.cert_time_to_seconds(cert['notAfter'])
timestamp = datetime.datetime.fromtimestamp(ts_seconds)
expiry = timestamp - datetime.datetime.today()
timestamp = datetime.fromtimestamp(ts_seconds)
expiry = timestamp - datetime.today()
self._state = expiry.days

View File

@ -30,7 +30,7 @@ UNIT_OF_MEASUREMENT = 'W'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_CHANNEL_ID): cv.string,
vol.Optional(CONF_CHANNEL_ID): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

View File

@ -19,7 +19,7 @@ from homeassistant.util import Throttle
from homeassistant.util.dt import now, parse_date
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fedexdeliverymanager==1.0.3']
REQUIREMENTS = ['fedexdeliverymanager==1.0.4']
_LOGGER = logging.getLogger(__name__)

View File

@ -17,10 +17,10 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
from homeassistant.util.icon import icon_for_battery_level
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fitbit==0.2.3']
REQUIREMENTS = ['fitbit==0.3.0']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@ -32,6 +32,7 @@ ATTR_CLIENT_SECRET = 'client_secret'
ATTR_LAST_SAVED_AT = 'last_saved_at'
CONF_MONITORED_RESOURCES = 'monitored_resources'
CONF_CLOCK_FORMAT = 'clock_format'
CONF_ATTRIBUTION = 'Data provided by Fitbit.com'
DEPENDENCIES = ['http']
@ -49,40 +50,50 @@ DEFAULT_CONFIG = {
}
FITBIT_RESOURCES_LIST = {
'activities/activityCalories': 'cal',
'activities/calories': 'cal',
'activities/caloriesBMR': 'cal',
'activities/distance': '',
'activities/elevation': '',
'activities/floors': 'floors',
'activities/heart': 'bpm',
'activities/minutesFairlyActive': 'minutes',
'activities/minutesLightlyActive': 'minutes',
'activities/minutesSedentary': 'minutes',
'activities/minutesVeryActive': 'minutes',
'activities/steps': 'steps',
'activities/tracker/activityCalories': 'cal',
'activities/tracker/calories': 'cal',
'activities/tracker/distance': '',
'activities/tracker/elevation': '',
'activities/tracker/floors': 'floors',
'activities/tracker/minutesFairlyActive': 'minutes',
'activities/tracker/minutesLightlyActive': 'minutes',
'activities/tracker/minutesSedentary': 'minutes',
'activities/tracker/minutesVeryActive': 'minutes',
'activities/tracker/steps': 'steps',
'body/bmi': 'BMI',
'body/fat': '%',
'devices/battery': 'level',
'sleep/awakeningsCount': 'times awaken',
'sleep/efficiency': '%',
'sleep/minutesAfterWakeup': 'minutes',
'sleep/minutesAsleep': 'minutes',
'sleep/minutesAwake': 'minutes',
'sleep/minutesToFallAsleep': 'minutes',
'sleep/startTime': 'start time',
'sleep/timeInBed': 'time in bed',
'body/weight': ''
'activities/activityCalories': ['Activity Calories', 'cal', 'fire'],
'activities/calories': ['Calories', 'cal', 'fire'],
'activities/caloriesBMR': ['Calories BMR', 'cal', 'fire'],
'activities/distance': ['Distance', '', 'map-marker'],
'activities/elevation': ['Elevation', '', 'walk'],
'activities/floors': ['Floors', 'floors', 'walk'],
'activities/heart': ['Resting Heart Rate', 'bpm', 'heart-pulse'],
'activities/minutesFairlyActive':
['Minutes Fairly Active', 'minutes', 'walk'],
'activities/minutesLightlyActive':
['Minutes Lightly Active', 'minutes', 'walk'],
'activities/minutesSedentary':
['Minutes Sedentary', 'minutes', 'seat-recline-normal'],
'activities/minutesVeryActive': ['Minutes Very Active', 'minutes', 'run'],
'activities/steps': ['Steps', 'steps', 'walk'],
'activities/tracker/activityCalories':
['Tracker Activity Calories', 'cal', 'fire'],
'activities/tracker/calories': ['Tracker Calories', 'cal', 'fire'],
'activities/tracker/distance': ['Tracker Distance', '', 'map-marker'],
'activities/tracker/elevation': ['Tracker Elevation', '', 'walk'],
'activities/tracker/floors': ['Tracker Floors', 'floors', 'walk'],
'activities/tracker/minutesFairlyActive':
['Tracker Minutes Fairly Active', 'minutes', 'walk'],
'activities/tracker/minutesLightlyActive':
['Tracker Minutes Lightly Active', 'minutes', 'walk'],
'activities/tracker/minutesSedentary':
['Tracker Minutes Sedentary', 'minutes', 'seat-recline-normal'],
'activities/tracker/minutesVeryActive':
['Tracker Minutes Very Active', 'minutes', 'run'],
'activities/tracker/steps': ['Tracker Steps', 'steps', 'walk'],
'body/bmi': ['BMI', 'BMI', 'human'],
'body/fat': ['Body Fat', '%', 'human'],
'body/weight': ['Weight', '', 'human'],
'devices/battery': ['Battery', None, None],
'sleep/awakeningsCount':
['Awakenings Count', 'times awaken', 'sleep'],
'sleep/efficiency': ['Sleep Efficiency', '%', 'sleep'],
'sleep/minutesAfterWakeup': ['Minutes After Wakeup', 'minutes', 'sleep'],
'sleep/minutesAsleep': ['Sleep Minutes Asleep', 'minutes', 'sleep'],
'sleep/minutesAwake': ['Sleep Minutes Awake', 'minutes', 'sleep'],
'sleep/minutesToFallAsleep':
['Sleep Minutes to Fall Asleep', 'minutes', 'sleep'],
'sleep/startTime': ['Sleep Start Time', None, 'clock'],
'sleep/timeInBed': ['Sleep Time in Bed', 'minutes', 'hotel']
}
FITBIT_MEASUREMENTS = {
@ -121,9 +132,18 @@ FITBIT_MEASUREMENTS = {
}
}
BATTERY_LEVELS = {
'High': 100,
'Medium': 50,
'Low': 20,
'Empty': 0
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES):
vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]),
vol.Optional(CONF_CLOCK_FORMAT, default='24H'):
vol.In(['12H', '24H'])
})
@ -155,7 +175,7 @@ def config_from_file(filename, config=None):
def request_app_setup(hass, config, add_devices, config_path,
discovery_info=None):
"""Assist user with configuring the Fitbit dev application."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# pylint: disable=unused-argument
def fitbit_configuration_callback(callback_data):
@ -166,7 +186,8 @@ def request_app_setup(hass, config, add_devices, config_path,
if config_file == DEFAULT_CONFIG:
error_msg = ("You didn't correctly modify fitbit.conf",
" please try again")
configurator.notify_errors(_CONFIGURING['fitbit'], error_msg)
configurator.notify_errors(_CONFIGURING['fitbit'],
error_msg)
else:
setup_platform(hass, config, add_devices, discovery_info)
else:
@ -187,7 +208,7 @@ def request_app_setup(hass, config, add_devices, config_path,
submit = "I have saved my Client ID and Client Secret into fitbit.conf."
_CONFIGURING['fitbit'] = configurator.request_config(
hass, 'Fitbit', fitbit_configuration_callback,
'Fitbit', fitbit_configuration_callback,
description=description, submit_caption=submit,
description_image="/static/images/config_fitbit_app.png"
)
@ -195,7 +216,7 @@ def request_app_setup(hass, config, add_devices, config_path,
def request_oauth_completion(hass):
"""Request user complete Fitbit OAuth2 flow."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if "fitbit" in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['fitbit'], "Failed to register, please try again.")
@ -211,7 +232,7 @@ def request_oauth_completion(hass):
description = "Please authorize Fitbit by visiting {}".format(start_url)
_CONFIGURING['fitbit'] = configurator.request_config(
hass, 'Fitbit', fitbit_configuration_callback,
'Fitbit', fitbit_configuration_callback,
description=description,
submit_caption="I have authorized Fitbit."
)
@ -233,7 +254,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
if "fitbit" in _CONFIGURING:
get_component('configurator').request_done(_CONFIGURING.pop("fitbit"))
hass.components.configurator.request_done(_CONFIGURING.pop("fitbit"))
import fitbit
@ -257,6 +278,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
registered_devs = authd_client.get_devices()
clock_format = config.get(CONF_CLOCK_FORMAT)
for resource in config.get(CONF_MONITORED_RESOURCES):
# monitor battery for all linked FitBit devices
@ -264,11 +286,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for dev_extra in registered_devs:
dev.append(FitbitSensor(
authd_client, config_path, resource,
hass.config.units.is_metric, dev_extra))
hass.config.units.is_metric, clock_format, dev_extra))
else:
dev.append(FitbitSensor(
authd_client, config_path, resource,
hass.config.units.is_metric))
hass.config.units.is_metric, clock_format))
add_devices(dev, True)
else:
@ -361,34 +383,24 @@ class FitbitSensor(Entity):
"""Implementation of a Fitbit sensor."""
def __init__(self, client, config_path, resource_type,
is_metric, extra=None):
is_metric, clock_format, extra=None):
"""Initialize the Fitbit sensor."""
self.client = client
self.config_path = config_path
self.resource_type = resource_type
self.is_metric = is_metric
self.clock_format = clock_format
self.extra = extra
pretty_resource = self.resource_type.replace('activities/', '')
pretty_resource = pretty_resource.replace('/', ' ')
pretty_resource = pretty_resource.title()
if pretty_resource == 'Body Bmi':
pretty_resource = 'BMI'
elif pretty_resource == 'Heart':
pretty_resource = 'Resting Heart Rate'
elif pretty_resource == 'Devices Battery':
if self.extra:
pretty_resource = \
'{0} Battery'.format(self.extra.get('deviceVersion'))
else:
pretty_resource = 'Battery'
self._name = pretty_resource
unit_type = FITBIT_RESOURCES_LIST[self.resource_type]
self._name = FITBIT_RESOURCES_LIST[self.resource_type][0]
if self.extra:
self._name = '{0} Battery'.format(self.extra.get('deviceVersion'))
unit_type = FITBIT_RESOURCES_LIST[self.resource_type][1]
if unit_type == "":
split_resource = self.resource_type.split('/')
try:
measurement_system = FITBIT_MEASUREMENTS[self.client.system]
except KeyError:
if is_metric:
if self.is_metric:
measurement_system = FITBIT_MEASUREMENTS['metric']
else:
measurement_system = FITBIT_MEASUREMENTS['en_US']
@ -414,9 +426,11 @@ class FitbitSensor(Entity):
@property
def icon(self):
"""Icon to use in the frontend, if any."""
if self.resource_type == 'devices/battery':
return 'mdi:battery-50'
return 'mdi:walk'
if self.resource_type == 'devices/battery' and self.extra:
battery_level = BATTERY_LEVELS[self.extra.get('battery')]
return icon_for_battery_level(battery_level=battery_level,
charging=None)
return 'mdi:{}'.format(FITBIT_RESOURCES_LIST[self.resource_type][2])
@property
def device_state_attributes(self):
@ -427,7 +441,7 @@ class FitbitSensor(Entity):
if self.extra:
attrs['model'] = self.extra.get('deviceVersion')
attrs['type'] = self.extra.get('type')
attrs['type'] = self.extra.get('type').lower()
return attrs
@ -438,7 +452,40 @@ class FitbitSensor(Entity):
else:
container = self.resource_type.replace("/", "-")
response = self.client.time_series(self.resource_type, period='7d')
self._state = response[container][-1].get('value')
raw_state = response[container][-1].get('value')
if self.resource_type == 'activities/distance':
self._state = format(float(raw_state), '.2f')
elif self.resource_type == 'activities/tracker/distance':
self._state = format(float(raw_state), '.2f')
elif self.resource_type == 'body/bmi':
self._state = format(float(raw_state), '.1f')
elif self.resource_type == 'body/fat':
self._state = format(float(raw_state), '.1f')
elif self.resource_type == 'body/weight':
self._state = format(float(raw_state), '.1f')
elif self.resource_type == 'sleep/startTime':
if raw_state == '':
self._state = '-'
elif self.clock_format == '12H':
hours, minutes = raw_state.split(':')
hours, minutes = int(hours), int(minutes)
setting = 'AM'
if hours > 12:
setting = 'PM'
hours -= 12
elif hours == 0:
hours = 12
self._state = '{}:{} {}'.format(hours, minutes, setting)
else:
self._state = raw_state
else:
if self.is_metric:
self._state = raw_state
else:
try:
self._state = '{0:,}'.format(int(raw_state))
except TypeError:
self._state = raw_state
if self.resource_type == 'activities/heart':
self._state = response[container][-1]. \

View File

@ -216,7 +216,7 @@ class FritzBoxCallMonitor(object):
self._sensor.set_attributes(att)
elif line[1] == "CONNECT":
self._sensor.set_state(VALUE_CONNECT)
att = {"with": line[4], "device": [3], "accepted": isotime}
att = {"with": line[4], "device": line[3], "accepted": isotime}
att["with_name"] = self._sensor.number_to_name(att["with"])
self._sensor.set_attributes(att)
elif line[1] == "DISCONNECT":

View File

@ -80,6 +80,8 @@ class Geizwatch(Entity):
@property
def device_state_attributes(self):
"""Return the state attributes."""
while len(self.data.prices) < 4:
self.data.prices.append("None")
attrs = {'device_name': self.data.device_name,
'description': self.description,
'unit_of_measurement': self.data.unit_of_measurement,

View File

@ -6,6 +6,7 @@ https://home-assistant.io/ecosystem/ios/
"""
from homeassistant.components import ios
from homeassistant.helpers.entity import Entity
from homeassistant.util.icon import icon_for_battery_level
DEPENDENCIES = ['ios']
@ -83,44 +84,21 @@ class IOSSensor(Entity):
device_battery = self._device[ios.ATTR_BATTERY]
battery_state = device_battery[ios.ATTR_BATTERY_STATE]
battery_level = device_battery[ios.ATTR_BATTERY_LEVEL]
rounded_level = round(battery_level, -1)
returning_icon_level = DEFAULT_ICON_LEVEL
if battery_state == ios.ATTR_BATTERY_STATE_FULL:
returning_icon_level = DEFAULT_ICON_LEVEL
if battery_state == ios.ATTR_BATTERY_STATE_CHARGING:
returning_icon_state = DEFAULT_ICON_STATE
else:
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
elif battery_state == ios.ATTR_BATTERY_STATE_CHARGING:
# Why is MDI missing 10, 50, 70?
if rounded_level in (20, 30, 40, 60, 80, 90, 100):
returning_icon_level = "{}-charging-{}".format(
DEFAULT_ICON_LEVEL, str(rounded_level))
returning_icon_state = DEFAULT_ICON_STATE
else:
returning_icon_level = "{}-charging".format(
DEFAULT_ICON_LEVEL)
returning_icon_state = DEFAULT_ICON_STATE
elif battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED:
if rounded_level < 10:
returning_icon_level = "{}-outline".format(
DEFAULT_ICON_LEVEL)
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
elif battery_level > 95:
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
returning_icon_level = "{}-outline".format(
DEFAULT_ICON_LEVEL)
else:
returning_icon_level = "{}-{}".format(DEFAULT_ICON_LEVEL,
str(rounded_level))
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
charging = True
icon_state = DEFAULT_ICON_STATE
if (battery_state == ios.ATTR_BATTERY_STATE_FULL or
battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED):
charging = False
icon_state = "{}-off".format(DEFAULT_ICON_STATE)
elif battery_state == ios.ATTR_BATTERY_STATE_UNKNOWN:
returning_icon_level = "{}-unknown".format(DEFAULT_ICON_LEVEL)
returning_icon_state = "{}-unknown".format(DEFAULT_ICON_LEVEL)
battery_level = None
charging = False
icon_state = "{}-unknown".format(DEFAULT_ICON_LEVEL)
if self.type == "state":
return returning_icon_state
return returning_icon_level
return icon_state
return icon_for_battery_level(battery_level=battery_level,
charging=charging)
def update(self):
"""Get the latest state of the sensor."""

View File

@ -0,0 +1,216 @@
"""
Sensor for checking the status of London air.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.london_air/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import requests
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
CONF_LOCATIONS = 'locations'
SCAN_INTERVAL = timedelta(minutes=30)
AUTHORITIES = [
'Barking and Dagenham',
'Bexley',
'Brent',
'Camden',
'City of London',
'Croydon',
'Ealing',
'Enfield',
'Greenwich',
'Hackney',
'Hammersmith and Fulham',
'Haringey',
'Harrow',
'Havering',
'Hillingdon',
'Islington',
'Kensington and Chelsea',
'Kingston',
'Lambeth',
'Lewisham',
'Merton',
'Redbridge',
'Richmond',
'Southwark',
'Sutton',
'Tower Hamlets',
'Wandsworth',
'Westminster']
URL = ('http://api.erg.kcl.ac.uk/AirQuality/Hourly/'
'MonitoringIndex/GroupName=London/Json')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LOCATIONS, default=AUTHORITIES):
vol.All(cv.ensure_list, [vol.In(AUTHORITIES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Tube sensor."""
data = APIData()
data.update()
sensors = []
for name in config.get(CONF_LOCATIONS):
sensors.append(AirSensor(name, data))
add_devices(sensors, True)
class APIData(object):
"""Get the latest data for all authorities."""
def __init__(self):
"""Initialize the AirData object."""
self.data = None
# Update only once in scan interval.
@Throttle(SCAN_INTERVAL)
def update(self):
"""Get the latest data from TFL."""
response = requests.get(URL, timeout=10)
if response.status_code != 200:
_LOGGER.warning("Invalid response from API")
else:
self.data = parse_api_response(response.json())
class AirSensor(Entity):
"""Single authority air sensor."""
ICON = 'mdi:cloud-outline'
def __init__(self, name, APIdata):
"""Initialize the sensor."""
self._name = name
self._api_data = APIdata
self._site_data = None
self._state = None
self._updated = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def site_data(self):
"""Return the dict of sites data."""
return self._site_data
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return self.ICON
@property
def device_state_attributes(self):
"""Return other details about the sensor state."""
attrs = {}
attrs['updated'] = self._updated
attrs['sites'] = len(self._site_data)
attrs['data'] = self._site_data
return attrs
def update(self):
"""Update the sensor."""
self._api_data.update()
self._site_data = self._api_data.data[self._name]
self._updated = self._site_data[0]['updated']
sites_status = []
for site in self._site_data:
if site['pollutants_status'] != 'no_species_data':
sites_status.append(site['pollutants_status'])
if sites_status:
self._state = max(set(sites_status), key=sites_status.count)
else:
self._state = STATE_UNKNOWN
def parse_species(species_data):
"""Iterate over list of species at each site."""
parsed_species_data = []
quality_list = []
for species in species_data:
if species['@AirQualityBand'] != 'No data':
species_dict = {}
species_dict['description'] = species['@SpeciesDescription']
species_dict['code'] = species['@SpeciesCode']
species_dict['quality'] = species['@AirQualityBand']
species_dict['index'] = species['@AirQualityIndex']
species_dict['summary'] = (species_dict['code'] + ' is '
+ species_dict['quality'])
parsed_species_data.append(species_dict)
quality_list.append(species_dict['quality'])
return parsed_species_data, quality_list
def parse_site(entry_sites_data):
"""Iterate over all sites at an authority."""
authority_data = []
for site in entry_sites_data:
site_data = {}
species_data = []
site_data['updated'] = site['@BulletinDate']
site_data['latitude'] = site['@Latitude']
site_data['longitude'] = site['@Longitude']
site_data['site_code'] = site['@SiteCode']
site_data['site_name'] = site['@SiteName'].split("-")[-1].lstrip()
site_data['site_type'] = site['@SiteType']
if isinstance(site['Species'], dict):
species_data = [site['Species']]
else:
species_data = site['Species']
parsed_species_data, quality_list = parse_species(species_data)
if not parsed_species_data:
parsed_species_data.append('no_species_data')
site_data['pollutants'] = parsed_species_data
if quality_list:
site_data['pollutants_status'] = max(set(quality_list),
key=quality_list.count)
site_data['number_of_pollutants'] = len(quality_list)
else:
site_data['pollutants_status'] = 'no_species_data'
site_data['number_of_pollutants'] = 0
authority_data.append(site_data)
return authority_data
def parse_api_response(response):
"""API can return dict or list of data so need to check."""
data = dict.fromkeys(AUTHORITIES)
for authority in AUTHORITIES:
for entry in response['HourlyAirQualityIndex']['LocalAuthority']:
if entry['@LocalAuthorityName'] == authority:
if isinstance(entry['Site'], dict):
entry_sites_data = [entry['Site']]
else:
entry_sites_data = entry['Site']
data[authority] = parse_site(entry_sites_data)
return data

View File

@ -4,89 +4,18 @@ Support for MySensors sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.sensor import DOMAIN
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for sensors."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_TEMP: [set_req.V_TEMP],
pres.S_HUM: [set_req.V_HUM],
pres.S_BARO: [set_req.V_PRESSURE, set_req.V_FORECAST],
pres.S_WIND: [set_req.V_WIND, set_req.V_GUST, set_req.V_DIRECTION],
pres.S_RAIN: [set_req.V_RAIN, set_req.V_RAINRATE],
pres.S_UV: [set_req.V_UV],
pres.S_WEIGHT: [set_req.V_WEIGHT, set_req.V_IMPEDANCE],
pres.S_POWER: [set_req.V_WATT, set_req.V_KWH],
pres.S_DISTANCE: [set_req.V_DISTANCE],
pres.S_LIGHT_LEVEL: [set_req.V_LIGHT_LEVEL],
pres.S_IR: [set_req.V_IR_RECEIVE],
pres.S_WATER: [set_req.V_FLOW, set_req.V_VOLUME],
pres.S_CUSTOM: [set_req.V_VAR1,
set_req.V_VAR2,
set_req.V_VAR3,
set_req.V_VAR4,
set_req.V_VAR5],
pres.S_SCENE_CONTROLLER: [set_req.V_SCENE_ON,
set_req.V_SCENE_OFF],
}
if float(gateway.protocol_version) < 1.5:
map_sv_types.update({
pres.S_AIR_QUALITY: [set_req.V_DUST_LEVEL],
pres.S_DUST: [set_req.V_DUST_LEVEL],
})
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_COLOR_SENSOR: [set_req.V_RGB],
pres.S_MULTIMETER: [set_req.V_VOLTAGE,
set_req.V_CURRENT,
set_req.V_IMPEDANCE],
pres.S_SOUND: [set_req.V_LEVEL],
pres.S_VIBRATION: [set_req.V_LEVEL],
pres.S_MOISTURE: [set_req.V_LEVEL],
pres.S_AIR_QUALITY: [set_req.V_LEVEL],
pres.S_DUST: [set_req.V_LEVEL],
})
map_sv_types[pres.S_LIGHT_LEVEL].append(set_req.V_LEVEL)
if float(gateway.protocol_version) >= 2.0:
map_sv_types.update({
pres.S_INFO: [set_req.V_TEXT],
pres.S_GAS: [set_req.V_FLOW, set_req.V_VOLUME],
pres.S_GPS: [set_req.V_POSITION],
pres.S_WATER_QUALITY: [set_req.V_TEMP, set_req.V_PH,
set_req.V_ORP, set_req.V_EC]
})
map_sv_types[pres.S_CUSTOM].append(set_req.V_CUSTOM)
map_sv_types[pres.S_POWER].extend(
[set_req.V_VAR, set_req.V_VA, set_req.V_POWER_FACTOR])
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsSensor, add_devices))
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsSensor, add_devices=add_devices)
class MySensorsSensor(mysensors.MySensorsDeviceEntity, Entity):
class MySensorsSensor(mysensors.MySensorsEntity):
"""Representation of a MySensors Sensor child node."""
@property

View File

@ -38,11 +38,12 @@ SENSOR_TYPES = {
'running', 0],
'processes_blocked': ['Processes Blocked', 'Count', 'system.processes',
'blocked', 0],
'system_load': ['System Load', '15 min', 'system.processes', 'running', 2],
'system_load': ['System Load', '15 min', 'system.load', 'load15', 2],
'system_io_in': ['System IO In', 'Count', 'system.io', 'in', 0],
'system_io_out': ['System IO Out', 'Count', 'system.io', 'out', 0],
'ipv4_in': ['IPv4 In', 'kb/s', 'system.ipv4', 'received', 0],
'ipv4_out': ['IPv4 Out', 'kb/s', 'system.ipv4', 'sent', 0],
'disk_free': ['Disk Free', 'GiB', 'disk_space._', 'avail', 2],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({

View File

@ -20,6 +20,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['octoprint']
DOMAIN = "octoprint"
DEFAULT_NAME = 'OctoPrint'
NOTIFICATION_ID = 'octoprint_notification'
NOTIFICATION_TITLE = 'OctoPrint sensor setup error'
SENSOR_TYPES = {
'Temperatures': ['printer', 'temperature', '*', TEMP_CELSIUS],
@ -42,12 +44,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
octoprint_api = hass.data[DOMAIN]["api"]
name = config.get(CONF_NAME)
monitored_conditions = config.get(CONF_MONITORED_CONDITIONS)
tools = octoprint_api.get_tools()
_LOGGER.error(str(tools))
if "Temperatures" in monitored_conditions:
if not tools:
hass.components.persistent_notification.create(
'Your printer appears to be offline.<br />'
'If you do not want to have your printer on <br />'
' at all times, and you would like to monitor <br /> '
'temperatures, please add <br />'
'bed and/or number&#95of&#95tools to your config <br />'
'and restart.',
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
devices = []
types = ["actual", "target"]
for octo_type in monitored_conditions:
if octo_type == "Temperatures":
for tool in octoprint_api.get_tools():
for tool in tools:
for temp_type in types:
new_sensor = OctoPrintSensor(
octoprint_api, temp_type, temp_type, name,

View File

@ -18,7 +18,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import get_component
REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/'
'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip'
@ -88,7 +87,7 @@ def setup_sabnzbd(base_url, apikey, name, hass, config, add_devices, sab_api):
def request_configuration(host, name, hass, config, add_devices, sab_api):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(_CONFIGURING[host],
@ -114,7 +113,6 @@ def request_configuration(host, name, hass, config, add_devices, sab_api):
hass.async_add_job(success)
_CONFIGURING[host] = configurator.request_config(
hass,
DEFAULT_NAME,
sabnzbd_configuration_callback,
description=('Enter the API Key'),

View File

@ -13,7 +13,8 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, CONF_UNIT_OF_MEASUREMENT)
CONF_HOST, CONF_NAME, CONF_PORT, CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN,
CONF_VALUE_TEMPLATE)
REQUIREMENTS = ['pysnmp==4.3.9']
@ -22,6 +23,8 @@ _LOGGER = logging.getLogger(__name__)
CONF_BASEOID = 'baseoid'
CONF_COMMUNITY = 'community'
CONF_VERSION = 'version'
CONF_ACCEPT_ERRORS = 'accept_errors'
CONF_DEFAULT_VALUE = 'default_value'
DEFAULT_COMMUNITY = 'public'
DEFAULT_HOST = 'localhost'
@ -45,6 +48,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION):
vol.In(SNMP_VERSIONS),
vol.Optional(CONF_ACCEPT_ERRORS, default=False): cv.boolean,
vol.Optional(CONF_DEFAULT_VALUE): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template
})
@ -61,6 +67,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
baseoid = config.get(CONF_BASEOID)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
version = config.get(CONF_VERSION)
accept_errors = config.get(CONF_ACCEPT_ERRORS)
default_value = config.get(CONF_DEFAULT_VALUE)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
errindication, _, _, _ = next(
getCmd(SnmpEngine(),
@ -69,23 +81,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
ContextData(),
ObjectType(ObjectIdentity(baseoid))))
if errindication:
if errindication and not accept_errors:
_LOGGER.error("Please check the details in the configuration file")
return False
else:
data = SnmpData(host, port, community, baseoid, version)
add_devices([SnmpSensor(data, name, unit)], True)
data = SnmpData(
host, port, community, baseoid, version, accept_errors,
default_value)
add_devices([SnmpSensor(data, name, unit, value_template)], True)
class SnmpSensor(Entity):
"""Representation of a SNMP sensor."""
def __init__(self, data, name, unit_of_measurement):
def __init__(self, data, name, unit_of_measurement,
value_template):
"""Initialize the sensor."""
self.data = data
self._name = name
self._state = None
self._unit_of_measurement = unit_of_measurement
self._value_template = value_template
@property
def name(self):
@ -105,19 +121,30 @@ class SnmpSensor(Entity):
def update(self):
"""Get the latest data and updates the states."""
self.data.update()
self._state = self.data.value
value = self.data.value
if value is None:
value = STATE_UNKNOWN
elif self._value_template is not None:
value = self._value_template.render_with_possible_json_value(
value, STATE_UNKNOWN)
self._state = value
class SnmpData(object):
"""Get the latest data and update the states."""
def __init__(self, host, port, community, baseoid, version):
def __init__(self, host, port, community, baseoid, version, accept_errors,
default_value):
"""Initialize the data object."""
self._host = host
self._port = port
self._community = community
self._baseoid = baseoid
self._version = SNMP_VERSIONS[version]
self._accept_errors = accept_errors
self._default_value = default_value
self.value = None
def update(self):
@ -133,11 +160,13 @@ class SnmpData(object):
ObjectType(ObjectIdentity(self._baseoid)))
)
if errindication:
if errindication and not self._accept_errors:
_LOGGER.error("SNMP error: %s", errindication)
elif errstatus:
elif errstatus and not self._accept_errors:
_LOGGER.error("SNMP error: %s at %s", errstatus.prettyPrint(),
errindex and restable[-1][int(errindex) - 1] or '?')
elif (errindication or errstatus) and self._accept_errors:
self.value = self._default_value
else:
for resrow in restable:
self.value = resrow[-1]
self.value = str(resrow[-1])

View File

@ -136,7 +136,7 @@ class PublicTransportData(object):
'fields[]=connections/from/departureTimestamp/&' +
'fields[]=connections/',
timeout=10)
connections = response.json()['connections'][:2]
connections = response.json()['connections'][1:3]
try:
self.times = [

View File

@ -14,7 +14,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
REQUIREMENTS = ['uber_rides==0.4.1']
REQUIREMENTS = ['uber_rides==0.5.1']
_LOGGER = logging.getLogger(__name__)

View File

@ -180,9 +180,12 @@ class UkTransportLiveBusTimeSensor(UkTransportSensor):
'estimated': departure['best_departure_estimate']
})
self._state = min(map(
_delta_mins, [bus['scheduled'] for bus in self._next_buses]
))
if self._next_buses:
self._state = min(
_delta_mins(bus['scheduled'])
for bus in self._next_buses)
else:
self._state = None
@property
def device_state_attributes(self):
@ -242,10 +245,12 @@ class UkTransportLiveTrainTimeSensor(UkTransportSensor):
'operator_name': departure['operator_name']
})
self._state = min(map(
_delta_mins,
[train['scheduled'] for train in self._next_trains]
))
if self._next_trains:
self._state = min(
_delta_mins(train['scheduled'])
for train in self._next_trains)
else:
self._state = None
@property
def device_state_attributes(self):

View File

@ -6,65 +6,44 @@ https://home-assistant.io/components/sensor.usps/
"""
from collections import defaultdict
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
ATTR_ATTRIBUTION)
from homeassistant.components.usps import DATA_USPS
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DATE
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
from homeassistant.util.dt import now, parse_datetime
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['myusps==1.1.2']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'usps'
SCAN_INTERVAL = timedelta(minutes=30)
COOKIE = 'usps_cookies.pickle'
DEPENDENCIES = ['usps']
STATUS_DELIVERED = 'delivered'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME): cv.string
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the USPS platform."""
import myusps
try:
cookie = hass.config.path(COOKIE)
session = myusps.get_session(
config.get(CONF_USERNAME), config.get(CONF_PASSWORD),
cookie_path=cookie)
except myusps.USPSError:
_LOGGER.exception('Could not connect to My USPS')
return False
if discovery_info is None:
return
add_devices([USPSPackageSensor(session, config.get(CONF_NAME)),
USPSMailSensor(session, config.get(CONF_NAME))], True)
usps = hass.data[DATA_USPS]
add_devices([USPSPackageSensor(usps),
USPSMailSensor(usps)], True)
class USPSPackageSensor(Entity):
"""USPS Package Sensor."""
def __init__(self, session, name):
def __init__(self, usps):
"""Initialize the sensor."""
self._session = session
self._name = name
self._usps = usps
self._name = self._usps.name
self._attributes = None
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return '{} packages'.format(self._name or DOMAIN)
return '{} packages'.format(self._name)
@property
def state(self):
@ -73,16 +52,16 @@ class USPSPackageSensor(Entity):
def update(self):
"""Update device state."""
import myusps
self._usps.update()
status_counts = defaultdict(int)
for package in myusps.get_packages(self._session):
for package in self._usps.packages:
status = slugify(package['primary_status'])
if status == STATUS_DELIVERED and \
parse_datetime(package['date']).date() < now().date():
continue
status_counts[status] += 1
self._attributes = {
ATTR_ATTRIBUTION: myusps.ATTRIBUTION
ATTR_ATTRIBUTION: self._usps.attribution
}
self._attributes.update(status_counts)
self._state = sum(status_counts.values())
@ -97,21 +76,26 @@ class USPSPackageSensor(Entity):
"""Icon to use in the frontend."""
return 'mdi:package-variant-closed'
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return 'packages'
class USPSMailSensor(Entity):
"""USPS Mail Sensor."""
def __init__(self, session, name):
def __init__(self, usps):
"""Initialize the sensor."""
self._session = session
self._name = name
self._usps = usps
self._name = self._usps.name
self._attributes = None
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return '{} mail'.format(self._name or DOMAIN)
return '{} mail'.format(self._name)
@property
def state(self):
@ -120,18 +104,29 @@ class USPSMailSensor(Entity):
def update(self):
"""Update device state."""
import myusps
self._state = len(myusps.get_mail(self._session))
self._usps.update()
if self._usps.mail is not None:
self._state = len(self._usps.mail)
else:
self._state = 0
@property
def device_state_attributes(self):
"""Return the state attributes."""
import myusps
return {
ATTR_ATTRIBUTION: myusps.ATTRIBUTION
}
attr = {}
attr[ATTR_ATTRIBUTION] = self._usps.attribution
try:
attr[ATTR_DATE] = self._usps.mail[0]['date']
except IndexError:
pass
return attr
@property
def icon(self):
"""Icon to use in the frontend."""
return 'mdi:mailbox'
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return 'pieces'

View File

@ -0,0 +1,55 @@
"""
Support for displaying the current version of Home Assistant.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.version/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import __version__, CONF_NAME
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Current Version"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Version sensor platform."""
name = config.get(CONF_NAME)
async_add_devices([VersionSensor(name)])
class VersionSensor(Entity):
"""Representation of a Home Assistant version sensor."""
def __init__(self, name):
"""Initialize the Version sensor."""
self._name = name
self._state = __version__
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def state(self):
"""Return the state of the sensor."""
return self._state

View File

@ -0,0 +1,113 @@
"""
This component provides HA sensor support for the worldtides.info API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.worldtidesinfo/
"""
import logging
import time
from datetime import timedelta
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE,
CONF_NAME, STATE_UNKNOWN)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'WorldTidesInfo'
SCAN_INTERVAL = timedelta(seconds=3600)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_LATITUDE): cv.latitude,
vol.Optional(CONF_LONGITUDE): cv.longitude,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the WorldTidesInfo sensor."""
name = config.get(CONF_NAME)
lat = config.get(CONF_LATITUDE, hass.config.latitude)
lon = config.get(CONF_LONGITUDE, hass.config.longitude)
key = config.get(CONF_API_KEY)
if None in (lat, lon):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
add_devices([WorldTidesInfoSensor(name, lat, lon, key)], True)
class WorldTidesInfoSensor(Entity):
"""Representation of a WorldTidesInfo sensor."""
def __init__(self, name, lat, lon, key):
"""Initialize the sensor."""
self._name = name
self._lat = lat
self._lon = lon
self._key = key
self.data = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def device_state_attributes(self):
"""Return the state attributes of this device."""
attr = {}
if "High" in str(self.data['extremes'][0]['type']):
attr['high_tide_time_utc'] = self.data['extremes'][0]['date']
attr['high_tide_height'] = self.data['extremes'][0]['height']
attr['low_tide_time_utc'] = self.data['extremes'][1]['date']
attr['low_tide_height'] = self.data['extremes'][1]['height']
elif "Low" in str(self.data['extremes'][0]['type']):
attr['high_tide_time_utc'] = self.data['extremes'][1]['date']
attr['high_tide_height'] = self.data['extremes'][1]['height']
attr['low_tide_time_utc'] = self.data['extremes'][0]['date']
attr['low_tide_height'] = self.data['extremes'][0]['height']
return attr
@property
def state(self):
"""Return the state of the device."""
if self.data:
if "High" in str(self.data['extremes'][0]['type']):
tidetime = time.strftime('%I:%M %p', time.localtime(
self.data['extremes'][0]['dt']))
return "High tide at %s" % (tidetime)
elif "Low" in str(self.data['extremes'][0]['type']):
tidetime = time.strftime('%I:%M %p', time.localtime(
self.data['extremes'][1]['dt']))
return "Low tide at %s" % (tidetime)
else:
return STATE_UNKNOWN
else:
return STATE_UNKNOWN
def update(self):
"""Get the latest data from WorldTidesInfo API."""
start = int(time.time())
resource = 'https://www.worldtides.info/api?extremes&length=86400' \
'&key=%s&lat=%s&lon=%s&start=%s' % (self._key, self._lat,
self._lon, start)
try:
self.data = requests.get(resource, timeout=10).json()
_LOGGER.debug("Data = %s", self.data)
_LOGGER.info("Tide data queried with start time set to: %s",
(start))
except ValueError as err:
_LOGGER.error("Check WorldTidesInfo %s", err.args)
self.data = None
raise

View File

@ -10,7 +10,6 @@ import os
from datetime import timedelta
from homeassistant.components.switch import SwitchDevice
from homeassistant.loader import get_component
import homeassistant.util as util
_CONFIGURING = {}
@ -51,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(
device_id, insteonhub, model, hass, add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
@ -66,7 +65,7 @@ def request_configuration(
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon Switch ' + model + ' addr: ' + device_id,
'Insteon Switch ' + model + ' addr: ' + device_id,
insteon_switch_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
@ -79,7 +78,7 @@ def setup_switch(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the switch."""
if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Device configuration done")

View File

@ -24,17 +24,25 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_AVAILABLE = 'payload_available'
CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available'
DEFAULT_NAME = 'MQTT Switch'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_OPTIMISTIC = False
DEFAULT_PAYLOAD_AVAILABLE = 'ON'
DEFAULT_PAYLOAD_NOT_AVAILABLE = 'OFF'
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_AVAILABILITY_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD_AVAILABLE,
default=DEFAULT_PAYLOAD_AVAILABLE): cv.string,
vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE): cv.string,
})
@ -58,6 +66,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_OPTIMISTIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
value_template,
)])
@ -67,7 +77,7 @@ class MqttSwitch(SwitchDevice):
def __init__(self, name, state_topic, command_topic, availability_topic,
qos, retain, payload_on, payload_off, optimistic,
value_template):
payload_available, payload_not_available, value_template):
"""Initialize the MQTT switch."""
self._state = False
self._name = name
@ -81,6 +91,8 @@ class MqttSwitch(SwitchDevice):
self._payload_off = payload_off
self._optimistic = optimistic
self._template = value_template
self._payload_available = payload_available
self._payload_not_available = payload_not_available
@asyncio.coroutine
def async_added_to_hass(self):
@ -104,9 +116,9 @@ class MqttSwitch(SwitchDevice):
@callback
def availability_message_received(topic, payload, qos):
"""Handle new MQTT availability messages."""
if payload == self._payload_on:
if payload == self._payload_available:
self._available = True
elif payload == self._payload_off:
elif payload == self._payload_not_available:
self._available = False
self.hass.async_add_job(self.async_update_ha_state())

View File

@ -4,7 +4,6 @@ Support for MySensors switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.mysensors/
"""
import logging
import os
import voluptuous as vol
@ -15,9 +14,6 @@ from homeassistant.components.switch import DOMAIN, SwitchDevice
from homeassistant.config import load_yaml_config_file
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
ATTR_IR_CODE = 'V_IR_SEND'
SERVICE_SEND_IR_CODE = 'mysensors_send_ir_code'
@ -29,82 +25,37 @@ SEND_IR_CODE_SERVICE_SCHEMA = vol.Schema({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the mysensors platform for switches."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
platform_devices = []
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_DOOR: [set_req.V_ARMED],
pres.S_MOTION: [set_req.V_ARMED],
pres.S_SMOKE: [set_req.V_ARMED],
pres.S_LIGHT: [set_req.V_LIGHT],
pres.S_LOCK: [set_req.V_LOCK_STATUS],
pres.S_IR: [set_req.V_IR_SEND],
}
device_class_map = {
pres.S_DOOR: MySensorsSwitch,
pres.S_MOTION: MySensorsSwitch,
pres.S_SMOKE: MySensorsSwitch,
pres.S_LIGHT: MySensorsSwitch,
pres.S_LOCK: MySensorsSwitch,
pres.S_IR: MySensorsIRSwitch,
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_BINARY: [set_req.V_STATUS, set_req.V_LIGHT],
pres.S_SPRINKLER: [set_req.V_STATUS],
pres.S_WATER_LEAK: [set_req.V_ARMED],
pres.S_SOUND: [set_req.V_ARMED],
pres.S_VIBRATION: [set_req.V_ARMED],
pres.S_MOISTURE: [set_req.V_ARMED],
})
map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS)
device_class_map.update({
pres.S_BINARY: MySensorsSwitch,
pres.S_SPRINKLER: MySensorsSwitch,
pres.S_WATER_LEAK: MySensorsSwitch,
pres.S_SOUND: MySensorsSwitch,
pres.S_VIBRATION: MySensorsSwitch,
pres.S_MOISTURE: MySensorsSwitch,
})
if float(gateway.protocol_version) >= 2.0:
map_sv_types.update({
pres.S_WATER_QUALITY: [set_req.V_STATUS],
})
device_class_map.update({
pres.S_WATER_QUALITY: MySensorsSwitch,
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, device_class_map, add_devices))
platform_devices.append(devices)
device_class_map = {
'S_DOOR': MySensorsSwitch,
'S_MOTION': MySensorsSwitch,
'S_SMOKE': MySensorsSwitch,
'S_LIGHT': MySensorsSwitch,
'S_LOCK': MySensorsSwitch,
'S_IR': MySensorsIRSwitch,
'S_BINARY': MySensorsSwitch,
'S_SPRINKLER': MySensorsSwitch,
'S_WATER_LEAK': MySensorsSwitch,
'S_SOUND': MySensorsSwitch,
'S_VIBRATION': MySensorsSwitch,
'S_MOISTURE': MySensorsSwitch,
'S_WATER_QUALITY': MySensorsSwitch,
}
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, device_class_map,
add_devices=add_devices)
def send_ir_code_service(service):
"""Set IR code as device state attribute."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
ir_code = service.data.get(ATTR_IR_CODE)
devices = mysensors.get_mysensors_devices(hass, DOMAIN)
if entity_ids:
_devices = [device for gw_devs in platform_devices
for device in gw_devs.values()
_devices = [device for device in devices.values()
if isinstance(device, MySensorsIRSwitch) and
device.entity_id in entity_ids]
else:
_devices = [device for gw_devs in platform_devices
for device in gw_devs.values()
_devices = [device for device in devices.values()
if isinstance(device, MySensorsIRSwitch)]
kwargs = {ATTR_IR_CODE: ir_code}
@ -120,7 +71,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
schema=SEND_IR_CODE_SERVICE_SCHEMA)
class MySensorsSwitch(mysensors.MySensorsDeviceEntity, SwitchDevice):
class MySensorsSwitch(mysensors.MySensorsEntity, SwitchDevice):
"""Representation of the value of a MySensors Switch child node."""
@property
@ -131,9 +82,7 @@ class MySensorsSwitch(mysensors.MySensorsDeviceEntity, SwitchDevice):
@property
def is_on(self):
"""Return True if switch is on."""
if self.value_type in self._values:
return self._values[self.value_type] == STATE_ON
return False
return self._values.get(self.value_type) == STATE_ON
def turn_on(self, **kwargs):
"""Turn the switch on."""
@ -159,24 +108,18 @@ class MySensorsIRSwitch(MySensorsSwitch):
def __init__(self, *args):
"""Set up instance attributes."""
MySensorsSwitch.__init__(self, *args)
super().__init__(*args)
self._ir_code = None
@property
def is_on(self):
"""Return True if switch is on."""
set_req = self.gateway.const.SetReq
if set_req.V_LIGHT in self._values:
return self._values[set_req.V_LIGHT] == STATE_ON
return False
return self._values.get(set_req.V_LIGHT) == STATE_ON
def turn_on(self, **kwargs):
"""Turn the IR switch on."""
set_req = self.gateway.const.SetReq
if set_req.V_LIGHT not in self._values:
_LOGGER.error('missing value_type: %s at node: %s, child: %s',
set_req.V_LIGHT.name, self.node_id, self.child_id)
return
if ATTR_IR_CODE in kwargs:
self._ir_code = kwargs[ATTR_IR_CODE]
self.gateway.set_child_value(
@ -194,10 +137,6 @@ class MySensorsIRSwitch(MySensorsSwitch):
def turn_off(self, **kwargs):
"""Turn the IR switch off."""
set_req = self.gateway.const.SetReq
if set_req.V_LIGHT not in self._values:
_LOGGER.error('missing value_type: %s at node: %s, child: %s',
set_req.V_LIGHT.name, self.node_id, self.child_id)
return
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_LIGHT, 0)
if self.gateway.optimistic:
@ -207,6 +146,5 @@ class MySensorsIRSwitch(MySensorsSwitch):
def update(self):
"""Update the controller with the latest value from a sensor."""
MySensorsSwitch.update(self)
if self.value_type in self._values:
self._ir_code = self._values[self.value_type]
super().update()
self._ir_code = self._values.get(self.value_type)

Some files were not shown because too many files have changed in this diff Show More