mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 23:57:06 +00:00
commit
50e5032f86
11
.coveragerc
11
.coveragerc
@ -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
3
.gitignore
vendored
@ -94,3 +94,6 @@ docs/build
|
||||
|
||||
# Windows Explorer
|
||||
desktop.ini
|
||||
/home-assistant.pyproj
|
||||
/home-assistant.sln
|
||||
/.vs/home-assistant/v14
|
||||
|
75
homeassistant/components/abode.py
Normal file
75
homeassistant/components/abode.py
Normal 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))
|
@ -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()
|
||||
|
82
homeassistant/components/alarm_control_panel/abode.py
Normal file
82
homeassistant/components/alarm_control_panel/abode.py
Normal 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()
|
@ -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__)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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'}]
|
||||
|
@ -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',
|
||||
|
81
homeassistant/components/binary_sensor/abode.py
Normal file
81
homeassistant/components/binary_sensor/abode.py
Normal 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()
|
@ -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):
|
||||
|
@ -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
|
||||
|
94
homeassistant/components/camera/usps.py
Normal file
94
homeassistant/components/camera/usps.py
Normal 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
|
@ -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]]
|
||||
|
@ -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')
|
||||
|
||||
|
||||
|
19
homeassistant/components/config/script.py
Normal file
19
homeassistant/components/config/script.py
Normal 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
|
@ -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."""
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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."""
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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!")
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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
Binary file not shown.
@ -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
Binary file not shown.
@ -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 : '');
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -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()
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
99
homeassistant/components/lock/nello.py
Normal file
99
homeassistant/components/lock/nello.py
Normal 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")
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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'):
|
||||
|
@ -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'])
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
|
||||
|
97
homeassistant/components/notify/hipchat.py
Normal file
97
homeassistant/components/notify/hipchat.py
Normal 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)
|
@ -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)
|
||||
|
70
homeassistant/components/notify/prowl.py
Normal file
70
homeassistant/components/notify/prowl.py
Normal 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)
|
@ -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)
|
||||
|
@ -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__)
|
||||
|
||||
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
})
|
||||
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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]. \
|
||||
|
@ -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":
|
||||
|
@ -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,
|
||||
|
@ -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."""
|
||||
|
216
homeassistant/components/sensor/london_air.py
Normal file
216
homeassistant/components/sensor/london_air.py
Normal 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
|
@ -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
|
||||
|
@ -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({
|
||||
|
@ -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_of_tools 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,
|
||||
|
@ -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'),
|
||||
|
@ -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])
|
||||
|
@ -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 = [
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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'
|
||||
|
55
homeassistant/components/sensor/version.py
Normal file
55
homeassistant/components/sensor/version.py
Normal 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
|
113
homeassistant/components/sensor/worldtidesinfo.py
Normal file
113
homeassistant/components/sensor/worldtidesinfo.py
Normal 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
|
@ -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")
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user