Merge pull request #19897 from home-assistant/rc

0.85.0
This commit is contained in:
Paulus Schoutsen 2019-01-09 16:51:11 -08:00 committed by GitHub
commit 70a8cac19d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
446 changed files with 14582 additions and 4014 deletions

View File

@ -73,7 +73,8 @@ omit =
homeassistant/components/comfoconnect.py
homeassistant/components/*/comfoconnect.py
homeassistant/components/daikin.py
homeassistant/components/daikin/__init__.py
homeassistant/components/daikin/const.py
homeassistant/components/*/daikin.py
homeassistant/components/digital_ocean.py
@ -105,18 +106,24 @@ omit =
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
homeassistant/components/envisalink.py
homeassistant/components/envisalink/__init__.py
homeassistant/components/*/envisalink.py
homeassistant/components/evohome.py
homeassistant/components/*/evohome.py
homeassistant/components/freebox.py
homeassistant/components/*/freebox.py
homeassistant/components/fritzbox.py
homeassistant/components/*/fritzbox.py
homeassistant/components/ecovacs.py
homeassistant/components/*/ecovacs.py
homeassistant/components/esphome/__init__.py
homeassistant/components/*/esphome.py
homeassistant/components/eufy.py
homeassistant/components/*/eufy.py
@ -160,6 +167,9 @@ omit =
homeassistant/components/homematicip_cloud.py
homeassistant/components/*/homematicip_cloud.py
homeassistant/components/homeworks.py
homeassistant/components/*/homeworks.py
homeassistant/components/huawei_lte.py
homeassistant/components/*/huawei_lte.py
@ -203,6 +213,9 @@ omit =
homeassistant/components/lametric.py
homeassistant/components/*/lametric.py
homeassistant/components/lcn.py
homeassistant/components/*/lcn.py
homeassistant/components/linode.py
homeassistant/components/*/linode.py
@ -265,6 +278,9 @@ omit =
homeassistant/components/openuv/__init__.py
homeassistant/components/*/openuv.py
homeassistant/components/plum_lightpad.py
homeassistant/components/*/plum_lightpad.py
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
@ -287,6 +303,8 @@ omit =
homeassistant/components/raspihats.py
homeassistant/components/*/raspihats.py
homeassistant/components/*/raspyrfm.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
@ -407,6 +425,7 @@ omit =
homeassistant/components/zha/__init__.py
homeassistant/components/zha/const.py
homeassistant/components/zha/event.py
homeassistant/components/zha/entities/*
homeassistant/components/zha/helpers.py
homeassistant/components/*/zha.py
@ -423,6 +442,7 @@ omit =
homeassistant/components/spider.py
homeassistant/components/*/spider.py
homeassistant/components/air_quality/opensensemap.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/canary.py
homeassistant/components/alarm_control_panel/concord232.py
@ -496,7 +516,6 @@ omit =
homeassistant/components/device_tracker/bt_smarthub.py
homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/freebox.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/google_maps.py
homeassistant/components/device_tracker/googlehome.py
@ -533,6 +552,7 @@ omit =
homeassistant/components/folder_watcher.py
homeassistant/components/foursquare.py
homeassistant/components/goalfeed.py
homeassistant/components/idteck_prox.py
homeassistant/components/ifttt.py
homeassistant/components/image_processing/dlib_face_detect.py
homeassistant/components/image_processing/dlib_face_identify.py
@ -596,6 +616,7 @@ omit =
homeassistant/components/media_player/frontier_silicon.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/harman_kardon_avr.py
homeassistant/components/media_player/horizon.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
@ -680,8 +701,10 @@ omit =
homeassistant/components/route53.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/aftership.py
homeassistant/components/sensor/airvisual.py
homeassistant/components/sensor/alpha_vantage.py
homeassistant/components/sensor/ambient_station.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
@ -692,6 +715,7 @@ omit =
homeassistant/components/sensor/bme680.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/brottsplatskartan.py
homeassistant/components/sensor/buienradar.py
homeassistant/components/sensor/cert_expiry.py
homeassistant/components/sensor/citybikes.py
@ -737,6 +761,7 @@ omit =
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gpsd.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/gtt.py
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
@ -752,6 +777,7 @@ omit =
homeassistant/components/sensor/launch_library.py
homeassistant/components/sensor/linky.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/london_underground.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/luftdaten.py
homeassistant/components/sensor/lyft.py
@ -769,6 +795,7 @@ omit =
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/netdata_public.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nmbs.py
homeassistant/components/sensor/noaa_tides.py
homeassistant/components/sensor/nsw_fuel_station.py
homeassistant/components/sensor/nut.py
@ -785,6 +812,7 @@ omit =
homeassistant/components/sensor/pocketcasts.py
homeassistant/components/sensor/pollen.py
homeassistant/components/sensor/postnl.py
homeassistant/components/sensor/prezzibenzina.py
homeassistant/components/sensor/pushbullet.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/pyload.py
@ -809,6 +837,7 @@ omit =
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/sochain.py
homeassistant/components/sensor/socialblade.py
homeassistant/components/sensor/solaredge.py
homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/spotcrime.py
@ -864,6 +893,7 @@ omit =
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pencom.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainbird.py
homeassistant/components/switch/rest.py

View File

@ -1,6 +1,7 @@
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!

View File

@ -7,6 +7,7 @@ about: Create a report to help us improve
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!

View File

@ -184,6 +184,8 @@ homeassistant/components/*/edp_redy.py @abmantis
homeassistant/components/edp_redy.py @abmantis
homeassistant/components/eight_sleep.py @mezz64
homeassistant/components/*/eight_sleep.py @mezz64
homeassistant/components/esphome/*.py @OttoWinter
homeassistant/components/*/esphome.py @OttoWinter
# H
homeassistant/components/hive.py @Rendili @KJonline
@ -211,6 +213,10 @@ homeassistant/components/melissa.py @kennedyshead
homeassistant/components/*/melissa.py @kennedyshead
homeassistant/components/*/mystrom.py @fabaff
# N
homeassistant/components/ness_alarm.py @nickw444
homeassistant/components/*/ness_alarm.py @nickw444
# O
homeassistant/components/openuv/* @bachya
homeassistant/components/*/openuv.py @bachya

View File

@ -1,4 +1,4 @@
Home Assistant |Build Status| |Coverage Status| |Chat Status| |Reviewed by Hound|
Home Assistant |Build Status| |Coverage Status| |Chat Status|
=================================================================================
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control.
@ -33,8 +33,6 @@ of a component, check the `Home Assistant help section <https://home-assistant.i
:target: https://coveralls.io/r/home-assistant/home-assistant?branch=master
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
:target: https://discord.gg/c5DvZ4e
.. |Reviewed by Hound| image:: https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg
:target: https://houndci.com
.. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png
:target: https://home-assistant.io/demo/
.. |screenshot-components| image:: https://raw.github.com/home-assistant/home-assistant/dev/docs/screenshot-components.png

View File

@ -1,5 +1,6 @@
"""Permission constants."""
CAT_ENTITIES = 'entities'
CAT_CONFIG_ENTRIES = 'config_entries'
SUBCAT_ALL = 'all'
POLICY_READ = 'read'

View File

@ -125,16 +125,23 @@ class AdsHub:
def shutdown(self, *args, **kwargs):
"""Shutdown ADS connection."""
import pyads
_LOGGER.debug("Shutting down ADS")
for notification_item in self._notification_items.values():
self._client.del_device_notification(
notification_item.hnotify,
notification_item.huser
)
_LOGGER.debug(
"Deleting device notification %d, %d",
notification_item.hnotify, notification_item.huser)
self._client.close()
try:
self._client.del_device_notification(
notification_item.hnotify,
notification_item.huser
)
except pyads.ADSError as err:
_LOGGER.error(err)
try:
self._client.close()
except pyads.ADSError as err:
_LOGGER.error(err)
def register_device(self, device):
"""Register a new device."""

View File

@ -0,0 +1,147 @@
"""
Component for handling Air Quality data for your location.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/air_quality/
"""
from datetime import timedelta
import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
ATTR_AQI = 'air_quality_index'
ATTR_ATTRIBUTION = 'attribution'
ATTR_C02 = 'carbon_dioxide'
ATTR_CO = 'carbon_monoxide'
ATTR_N2O = 'nitrogen_oxide'
ATTR_NO = 'nitrogen_monoxide'
ATTR_NO2 = 'nitrogen_dioxide'
ATTR_OZONE = 'ozone'
ATTR_PM_0_1 = 'particulate_matter_0_1'
ATTR_PM_10 = 'particulate_matter_10'
ATTR_PM_2_5 = 'particulate_matter_2_5'
ATTR_SO2 = 'sulphur_dioxide'
DOMAIN = 'air_quality'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SCAN_INTERVAL = timedelta(seconds=30)
PROP_TO_ATTR = {
'air_quality_index': ATTR_AQI,
'attribution': ATTR_ATTRIBUTION,
'carbon_dioxide': ATTR_C02,
'carbon_monoxide': ATTR_CO,
'nitrogen_oxide': ATTR_N2O,
'nitrogen_monoxide': ATTR_NO,
'nitrogen_dioxide': ATTR_NO2,
'ozone': ATTR_OZONE,
'particulate_matter_0_1': ATTR_PM_0_1,
'particulate_matter_10': ATTR_PM_10,
'particulate_matter_2_5': ATTR_PM_2_5,
'sulphur_dioxide': ATTR_SO2,
}
async def async_setup(hass, config):
"""Set up the air quality component."""
component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
return True
async def async_setup_entry(hass, entry):
"""Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
class AirQualityEntity(Entity):
"""ABC for air quality data."""
@property
def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level."""
raise NotImplementedError()
@property
def particulate_matter_10(self):
"""Return the particulate matter 10 level."""
return None
@property
def particulate_matter_0_1(self):
"""Return the particulate matter 0.1 level."""
return None
@property
def air_quality_index(self):
"""Return the Air Quality Index (AQI)."""
return None
@property
def ozone(self):
"""Return the O3 (ozone) level."""
return None
@property
def carbon_monoxide(self):
"""Return the CO (carbon monoxide) level."""
return None
@property
def carbon_dioxide(self):
"""Return the CO2 (carbon dioxide) level."""
return None
@property
def attribution(self):
"""Return the attribution."""
return None
@property
def sulphur_dioxide(self):
"""Return the SO2 (sulphur dioxide) level."""
return None
@property
def nitrogen_oxide(self):
"""Return the N2O (nitrogen oxide) level."""
return None
@property
def nitrogen_monoxide(self):
"""Return the NO (nitrogen monoxide) level."""
return None
@property
def nitrogen_dioxide(self):
"""Return the NO2 (nitrogen dioxide) level."""
return None
@property
def state_attributes(self):
"""Return the state attributes."""
data = {}
for prop, attr in PROP_TO_ATTR.items():
value = getattr(self, prop)
if value is not None:
data[attr] = value
return data
@property
def state(self):
"""Return the current state."""
return self.particulate_matter_2_5

View File

@ -0,0 +1,56 @@
"""
Demo platform that offers fake air quality data.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.air_quality import AirQualityEntity
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Air Quality."""
add_entities([
DemoAirQuality('Home', 14, 23, 100),
DemoAirQuality('Office', 4, 16, None)
])
class DemoAirQuality(AirQualityEntity):
"""Representation of Air Quality data."""
def __init__(self, name, pm_2_5, pm_10, n2o):
"""Initialize the Demo Air Quality."""
self._name = name
self._pm_2_5 = pm_2_5
self._pm_10 = pm_10
self._n2o = n2o
@property
def name(self):
"""Return the name of the sensor."""
return '{} {}'.format('Demo Air Quality', self._name)
@property
def should_poll(self):
"""No polling needed for Demo Air Quality."""
return False
@property
def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level."""
return self._pm_2_5
@property
def particulate_matter_10(self):
"""Return the particulate matter 10 level."""
return self._pm_10
@property
def nitrogen_oxide(self):
"""Return the nitrogen oxide (N2O) level."""
return self._n2o
@property
def attribution(self):
"""Return the attribution."""
return 'Powered by Home Assistant'

View File

@ -0,0 +1,105 @@
"""
Support for openSenseMap Air Quality data.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/air_quality/opensensemap/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.air_quality import (
PLATFORM_SCHEMA, AirQualityEntity)
from homeassistant.const import CONF_NAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
REQUIREMENTS = ['opensensemap-api==0.1.3']
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Data provided by openSenseMap'
CONF_STATION_ID = 'station_id'
SCAN_INTERVAL = timedelta(minutes=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STATION_ID): cv.string,
vol.Optional(CONF_NAME): cv.string,
})
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the openSenseMap air quality platform."""
from opensensemap_api import OpenSenseMap
name = config.get(CONF_NAME)
station_id = config[CONF_STATION_ID]
session = async_get_clientsession(hass)
osm_api = OpenSenseMapData(OpenSenseMap(station_id, hass.loop, session))
await osm_api.async_update()
if 'name' not in osm_api.api.data:
_LOGGER.error("Station %s is not available", station_id)
return
station_name = osm_api.api.data['name'] if name is None else name
async_add_entities([OpenSenseMapQuality(station_name, osm_api)], True)
class OpenSenseMapQuality(AirQualityEntity):
"""Implementation of an openSenseMap air quality entity."""
def __init__(self, name, osm):
"""Initialize the air quality entity."""
self._name = name
self._osm = osm
@property
def name(self):
"""Return the name of the air quality entity."""
return self._name
@property
def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level."""
return self._osm.api.pm2_5
@property
def particulate_matter_10(self):
"""Return the particulate matter 10 level."""
return self._osm.api.pm10
@property
def attribution(self):
"""Return the attribution."""
return ATTRIBUTION
async def async_update(self):
"""Get the latest data from the openSenseMap API."""
await self._osm.async_update()
class OpenSenseMapData:
"""Get the latest data and update the states."""
def __init__(self, api):
"""Initialize the data object."""
self.api = api
@Throttle(SCAN_INTERVAL)
async def async_update(self):
"""Get the latest data from the Pi-hole."""
from opensensemap_api.exceptions import OpenSenseMapError
try:
await self.api.get_data()
except OpenSenseMapError as err:
_LOGGER.error("Unable to fetch data: %s", err)

View File

@ -25,7 +25,7 @@ ATTR_CHANGED_BY = 'changed_by'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ALARM_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_CODE): cv.string,
})

View File

@ -5,14 +5,16 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ialarm/
"""
import logging
import re
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
CONF_CODE, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyialarm==0.3']
@ -36,6 +38,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_CODE): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@ -43,23 +46,25 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up an iAlarm control panel."""
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
url = 'http://{}'.format(host)
ialarm = IAlarmPanel(name, username, password, url)
ialarm = IAlarmPanel(name, code, username, password, url)
add_entities([ialarm], True)
class IAlarmPanel(alarm.AlarmControlPanel):
"""Representation of an iAlarm status."""
def __init__(self, name, username, password, url):
def __init__(self, name, code, username, password, url):
"""Initialize the iAlarm status."""
from pyialarm import IAlarm
self._name = name
self._code = str(code) if code else None
self._username = username
self._password = password
self._url = url
@ -71,6 +76,15 @@ class IAlarmPanel(alarm.AlarmControlPanel):
"""Return the name of the device."""
return self._name
@property
def code_format(self):
"""Return one or more digits/characters."""
if self._code is None:
return None
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):
"""Return the state of the device."""
@ -98,12 +112,22 @@ class IAlarmPanel(alarm.AlarmControlPanel):
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._client.disarm()
if self._validate_code(code):
self._client.disarm()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away()
if self._validate_code(code):
self._client.arm_away()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._client.arm_stay()
if self._validate_code(code):
self._client.arm_stay()
def _validate_code(self, code):
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning("Wrong code entered")
return check

View File

@ -13,13 +13,14 @@ from homeassistant.core import callback
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components import mqtt
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME, CONF_CODE)
CONF_CODE, CONF_DEVICE, CONF_NAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate, subscription)
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
CONF_STATE_TOPIC, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -30,6 +31,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
@ -45,6 +47,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@ -73,7 +77,7 @@ async def _async_setup_entity(config, async_add_entities,
async_add_entities([MqttAlarm(config, discovery_hash)])
class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""
@ -81,17 +85,20 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
"""Init the MQTT Alarm Control Panel."""
self._state = STATE_UNKNOWN
self._config = config
self._unique_id = config.get(CONF_UNIQUE_ID)
self._sub_state = None
availability_topic = config.get(CONF_AVAILABILITY_TOPIC)
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)
async def async_added_to_hass(self):
"""Subscribe mqtt events."""
@ -127,7 +134,8 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
await subscription.async_unsubscribe_topics(self.hass, self._sub_state)
self._sub_state = await subscription.async_unsubscribe_topics(
self.hass, self._sub_state)
await MqttAvailability.async_will_remove_from_hass(self)
@property
@ -140,6 +148,11 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
"""Return the name of the device."""
return self._config.get(CONF_NAME)
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@property
def state(self):
"""Return the state of the device."""

View File

@ -0,0 +1,107 @@
"""
Support for Ness D8X/D16X alarm panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ness_alarm/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.ness_alarm import (
DATA_NESS, SIGNAL_ARMING_STATE_CHANGED)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMING,
STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, STATE_ALARM_DISARMED)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['ness_alarm']
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Ness Alarm alarm control panel devices."""
if discovery_info is None:
return
device = NessAlarmPanel(hass.data[DATA_NESS], 'Alarm Panel')
async_add_entities([device])
class NessAlarmPanel(alarm.AlarmControlPanel):
"""Representation of a Ness alarm panel."""
def __init__(self, client, name):
"""Initialize the alarm panel."""
self._client = client
self._name = name
self._state = None
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ARMING_STATE_CHANGED,
self._handle_arming_state_change)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def should_poll(self):
"""Return the polling state."""
return False
@property
def code_format(self):
"""Return the regex for code format or None if no code is required."""
return 'Number'
@property
def state(self):
"""Return the state of the device."""
return self._state
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
await self._client.disarm(code)
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
await self._client.arm_away(code)
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
await self._client.arm_home(code)
async def async_alarm_trigger(self, code=None):
"""Send trigger/panic command."""
await self._client.panic(code)
@callback
def _handle_arming_state_change(self, arming_state):
"""Handle arming state update."""
from nessclient import ArmingState
if arming_state == ArmingState.UNKNOWN:
self._state = None
elif arming_state == ArmingState.DISARMED:
self._state = STATE_ALARM_DISARMED
elif arming_state == ArmingState.ARMING:
self._state = STATE_ALARM_ARMING
elif arming_state == ArmingState.EXIT_DELAY:
self._state = STATE_ALARM_ARMING
elif arming_state == ArmingState.ARMED:
self._state = STATE_ALARM_ARMED_AWAY
elif arming_state == ArmingState.ENTRY_DELAY:
self._state = STATE_ALARM_PENDING
elif arming_state == ArmingState.TRIGGERED:
self._state = STATE_ALARM_TRIGGERED
else:
_LOGGER.warning("Unhandled arming state: %s", arming_state)
self.async_schedule_update_ha_state()

View File

@ -13,7 +13,7 @@ import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.4']
@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities([NX584Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to NX584: %s", str(ex))
return False
return
class NX584Alarm(alarm.AlarmControlPanel):
@ -60,7 +60,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
# talk to the API and trigger a requests exception for setup_platform()
# to catch
self._alarm.list_zones()
self._state = STATE_UNKNOWN
self._state = None
@property
def name(self):
@ -85,11 +85,11 @@ class NX584Alarm(alarm.AlarmControlPanel):
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
dict(host=self._url, reason=ex))
self._state = STATE_UNKNOWN
self._state = None
zones = []
except IndexError:
_LOGGER.error("NX584 reports no partitions")
self._state = STATE_UNKNOWN
self._state = None
zones = []
bypassed = False
@ -107,6 +107,10 @@ class NX584Alarm(alarm.AlarmControlPanel):
else:
self._state = STATE_ALARM_ARMED_AWAY
for flag in part['condition_flags']:
if flag == "Siren on":
self._state = STATE_ALARM_TRIGGERED
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._alarm.disarm(code)

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['yalesmartalarmclient==0.1.5']
REQUIREMENTS = ['yalesmartalarmclient==0.1.6']
CONF_AREA_ID = 'area_id'

View File

@ -32,6 +32,7 @@ CONF_DEVICE_TYPE = 'type'
CONF_PANEL_DISPLAY = 'panel_display'
CONF_ZONE_NAME = 'name'
CONF_ZONE_TYPE = 'type'
CONF_ZONE_LOOP = 'loop'
CONF_ZONE_RFID = 'rfid'
CONF_ZONES = 'zones'
CONF_RELAY_ADDR = 'relayaddr'
@ -75,6 +76,8 @@ ZONE_SCHEMA = vol.Schema({
vol.Optional(CONF_ZONE_TYPE,
default=DEFAULT_ZONE_TYPE): vol.Any(DEVICE_CLASSES_SCHEMA),
vol.Optional(CONF_ZONE_RFID): cv.string,
vol.Optional(CONF_ZONE_LOOP):
vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
vol.Inclusive(CONF_RELAY_ADDR, 'relaylocation',
'Relay address and channel must exist together'): cv.byte,
vol.Inclusive(CONF_RELAY_CHAN, 'relaylocation',

View File

@ -13,8 +13,9 @@ from homeassistant.helpers import entityfilter
from . import flash_briefings, intent, smart_home
from .const import (
CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN,
CONF_FILTER, CONF_ENTITY_CONFIG)
CONF_AUDIO, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DISPLAY_URL,
CONF_ENDPOINT, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN, CONF_FILTER,
CONF_ENTITY_CONFIG)
_LOGGER = logging.getLogger(__name__)
@ -30,6 +31,9 @@ ALEXA_ENTITY_SCHEMA = vol.Schema({
})
SMART_HOME_SCHEMA = vol.Schema({
vol.Optional(CONF_ENDPOINT): cv.string,
vol.Optional(CONF_CLIENT_ID): cv.string,
vol.Optional(CONF_CLIENT_SECRET): cv.string,
vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
})

View File

@ -0,0 +1,154 @@
"""Support for Alexa skill auth."""
import asyncio
import json
import logging
from datetime import timedelta
import aiohttp
import async_timeout
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from homeassistant.util import dt
from .const import DEFAULT_TIMEOUT
_LOGGER = logging.getLogger(__name__)
LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token"
LWA_HEADERS = {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
}
PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300
STORAGE_KEY = 'alexa_auth'
STORAGE_VERSION = 1
STORAGE_EXPIRE_TIME = "expire_time"
STORAGE_ACCESS_TOKEN = "access_token"
STORAGE_REFRESH_TOKEN = "refresh_token"
class Auth:
"""Handle authentication to send events to Alexa."""
def __init__(self, hass, client_id, client_secret):
"""Initialize the Auth class."""
self.hass = hass
self.client_id = client_id
self.client_secret = client_secret
self._prefs = None
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
self._get_token_lock = asyncio.Lock(loop=hass.loop)
async def async_do_auth(self, accept_grant_code):
"""Do authentication with an AcceptGrant code."""
# access token not retrieved yet for the first time, so this should
# be an access token request
lwa_params = {
"grant_type": "authorization_code",
"code": accept_grant_code,
"client_id": self.client_id,
"client_secret": self.client_secret
}
_LOGGER.debug("Calling LWA to get the access token (first time), "
"with: %s", json.dumps(lwa_params))
return await self._async_request_new_token(lwa_params)
async def async_get_access_token(self):
"""Perform access token or token refresh request."""
async with self._get_token_lock:
if self._prefs is None:
await self.async_load_preferences()
if self.is_token_valid():
_LOGGER.debug("Token still valid, using it.")
return self._prefs[STORAGE_ACCESS_TOKEN]
if self._prefs[STORAGE_REFRESH_TOKEN] is None:
_LOGGER.debug("Token invalid and no refresh token available.")
return None
lwa_params = {
"grant_type": "refresh_token",
"refresh_token": self._prefs[STORAGE_REFRESH_TOKEN],
"client_id": self.client_id,
"client_secret": self.client_secret
}
_LOGGER.debug("Calling LWA to refresh the access token.")
return await self._async_request_new_token(lwa_params)
@callback
def is_token_valid(self):
"""Check if a token is already loaded and if it is still valid."""
if not self._prefs[STORAGE_ACCESS_TOKEN]:
return False
expire_time = dt.parse_datetime(self._prefs[STORAGE_EXPIRE_TIME])
preemptive_expire_time = expire_time - timedelta(
seconds=PREEMPTIVE_REFRESH_TTL_IN_SECONDS)
return dt.utcnow() < preemptive_expire_time
async def _async_request_new_token(self, lwa_params):
try:
session = aiohttp_client.async_get_clientsession(self.hass)
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=self.hass.loop):
response = await session.post(LWA_TOKEN_URI,
headers=LWA_HEADERS,
data=lwa_params,
allow_redirects=True)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout calling LWA to get auth token.")
return None
_LOGGER.debug("LWA response header: %s", response.headers)
_LOGGER.debug("LWA response status: %s", response.status)
if response.status != 200:
_LOGGER.error("Error calling LWA to get auth token.")
return None
response_json = await response.json()
_LOGGER.debug("LWA response body : %s", response_json)
access_token = response_json["access_token"]
refresh_token = response_json["refresh_token"]
expires_in = response_json["expires_in"]
expire_time = dt.utcnow() + timedelta(seconds=expires_in)
await self._async_update_preferences(access_token, refresh_token,
expire_time.isoformat())
return access_token
async def async_load_preferences(self):
"""Load preferences with stored tokens."""
self._prefs = await self._store.async_load()
if self._prefs is None:
self._prefs = {
STORAGE_ACCESS_TOKEN: None,
STORAGE_REFRESH_TOKEN: None,
STORAGE_EXPIRE_TIME: None
}
async def _async_update_preferences(self, access_token, refresh_token,
expire_time):
"""Update user preferences."""
if self._prefs is None:
await self.async_load_preferences()
if access_token is not None:
self._prefs[STORAGE_ACCESS_TOKEN] = access_token
if refresh_token is not None:
self._prefs[STORAGE_REFRESH_TOKEN] = refresh_token
if expire_time is not None:
self._prefs[STORAGE_EXPIRE_TIME] = expire_time
await self._store.async_save(self._prefs)

View File

@ -10,6 +10,9 @@ CONF_DISPLAY_URL = 'display_url'
CONF_FILTER = 'filter'
CONF_ENTITY_CONFIG = 'entity_config'
CONF_ENDPOINT = 'endpoint'
CONF_CLIENT_ID = 'client_id'
CONF_CLIENT_SECRET = 'client_secret'
ATTR_UID = 'uid'
ATTR_UPDATE_DATE = 'updateDate'
@ -21,3 +24,5 @@ ATTR_REDIRECTION_URL = 'redirectionURL'
SYN_RESOLUTION_MATCH = 'ER_SUCCESS_MATCH'
DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.0Z'
DEFAULT_TIMEOUT = 30

View File

@ -5,15 +5,22 @@ https://developer.amazon.com/docs/smarthome/understand-the-smart-home-skill-api.
https://developer.amazon.com/docs/device-apis/message-guide.html
"""
import asyncio
from collections import OrderedDict
from datetime import datetime
import json
import logging
import math
from uuid import uuid4
import aiohttp
import async_timeout
from homeassistant.components import (
alert, automation, binary_sensor, climate, cover, fan, group, http,
input_boolean, light, lock, media_player, scene, script, sensor, switch)
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.event import async_track_state_change
from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CLOUD_NEVER_EXPOSED_ENTITIES,
@ -21,13 +28,15 @@ from homeassistant.const import (
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON, STATE_UNLOCKED,
TEMP_CELSIUS, TEMP_FAHRENHEIT)
TEMP_CELSIUS, TEMP_FAHRENHEIT, MATCH_ALL)
import homeassistant.core as ha
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
from homeassistant.util.temperature import convert as convert_temperature
from .const import CONF_ENTITY_CONFIG, CONF_FILTER
from .const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_ENDPOINT, \
CONF_ENTITY_CONFIG, CONF_FILTER, DATE_FORMAT, DEFAULT_TIMEOUT
from .auth import Auth
_LOGGER = logging.getLogger(__name__)
@ -37,6 +46,8 @@ API_EVENT = 'event'
API_CONTEXT = 'context'
API_HEADER = 'header'
API_PAYLOAD = 'payload'
API_SCOPE = 'scope'
API_CHANGE = 'change'
API_TEMP_UNITS = {
TEMP_FAHRENHEIT: 'FAHRENHEIT',
@ -66,6 +77,8 @@ HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
EVENT_ALEXA_SMART_HOME = 'alexa_smart_home'
AUTH_KEY = "alexa.smart_home.auth"
class _DisplayCategory:
"""Possible display categories for Discovery response.
@ -375,6 +388,8 @@ class _AlexaInterface:
'name': prop_name,
'namespace': self.name(),
'value': prop_value,
'timeOfSample': datetime.now().strftime(DATE_FORMAT),
'uncertaintyInMilliseconds': 0
}
@ -390,6 +405,9 @@ class _AlexaPowerController(_AlexaInterface):
def properties_supported(self):
return [{'name': 'powerState'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@ -417,6 +435,9 @@ class _AlexaLockController(_AlexaInterface):
def properties_retrievable(self):
return True
def properties_proactively_reported(self):
return True
def get_property(self, name):
if name != 'lockState':
raise _UnsupportedProperty(name)
@ -454,6 +475,9 @@ class _AlexaBrightnessController(_AlexaInterface):
def properties_supported(self):
return [{'name': 'brightness'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@ -585,6 +609,9 @@ class _AlexaTemperatureSensor(_AlexaInterface):
def properties_supported(self):
return [{'name': 'temperature'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@ -625,6 +652,9 @@ class _AlexaContactSensor(_AlexaInterface):
def properties_supported(self):
return [{'name': 'detectionState'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@ -648,6 +678,9 @@ class _AlexaMotionSensor(_AlexaInterface):
def properties_supported(self):
return [{'name': 'detectionState'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@ -686,6 +719,9 @@ class _AlexaThermostatController(_AlexaInterface):
properties.append({'name': 'thermostatMode'})
return properties
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@ -948,8 +984,11 @@ class _Cause:
class Config:
"""Hold the configuration for Alexa."""
def __init__(self, should_expose, entity_config=None):
def __init__(self, endpoint, async_get_access_token, should_expose,
entity_config=None):
"""Initialize the configuration."""
self.endpoint = endpoint
self.async_get_access_token = async_get_access_token
self.should_expose = should_expose
self.entity_config = entity_config or {}
@ -964,12 +1003,62 @@ def async_setup(hass, config):
Even if that's disabled, the functionality in this module may still be used
by the cloud component which will call async_handle_message directly.
"""
if config.get(CONF_CLIENT_ID) and config.get(CONF_CLIENT_SECRET):
hass.data[AUTH_KEY] = Auth(hass, config[CONF_CLIENT_ID],
config[CONF_CLIENT_SECRET])
async_get_access_token = \
hass.data[AUTH_KEY].async_get_access_token if AUTH_KEY in hass.data \
else None
smart_home_config = Config(
endpoint=config.get(CONF_ENDPOINT),
async_get_access_token=async_get_access_token,
should_expose=config[CONF_FILTER],
entity_config=config.get(CONF_ENTITY_CONFIG),
)
hass.http.register_view(SmartHomeView(smart_home_config))
if AUTH_KEY in hass.data:
hass.loop.create_task(
async_enable_proactive_mode(hass, smart_home_config))
async def async_enable_proactive_mode(hass, smart_home_config):
"""Enable the proactive mode.
Proactive mode makes this component report state changes to Alexa.
"""
if smart_home_config.async_get_access_token is None:
# no function to call to get token
return
if await smart_home_config.async_get_access_token() is None:
# not ready yet
return
async def async_entity_state_listener(changed_entity, old_state,
new_state):
if not smart_home_config.should_expose(changed_entity):
_LOGGER.debug("Not exposing %s because filtered by config",
changed_entity)
return
if new_state.domain not in ENTITY_ADAPTERS:
return
alexa_changed_entity = \
ENTITY_ADAPTERS[new_state.domain](hass, smart_home_config,
new_state)
for interface in alexa_changed_entity.interfaces():
if interface.properties_proactively_reported():
await async_send_changereport_message(hass, smart_home_config,
alexa_changed_entity)
return
async_track_state_change(hass, MATCH_ALL, async_entity_state_listener)
class SmartHomeView(http.HomeAssistantView):
"""Expose Smart Home v3 payload interface via HTTP POST."""
@ -1112,6 +1201,24 @@ class _AlexaResponse:
"""
self._response[API_EVENT][API_HEADER]['correlationToken'] = token
def set_endpoint_full(self, bearer_token, endpoint_id, cookie=None):
"""Set the endpoint dictionary.
This is used to send proactive messages to Alexa.
"""
self._response[API_EVENT][API_ENDPOINT] = {
API_SCOPE: {
'type': 'BearerToken',
'token': bearer_token
}
}
if endpoint_id is not None:
self._response[API_EVENT][API_ENDPOINT]['endpointId'] = endpoint_id
if cookie is not None:
self._response[API_EVENT][API_ENDPOINT]['cookie'] = cookie
def set_endpoint(self, endpoint):
"""Set the endpoint.
@ -1222,6 +1329,62 @@ async def async_handle_message(
return response.serialize()
async def async_send_changereport_message(hass, config, alexa_entity):
"""Send a ChangeReport message for an Alexa entity."""
token = await config.async_get_access_token()
if not token:
_LOGGER.error("Invalid access token.")
return
headers = {
"Authorization": "Bearer {}".format(token),
"Content-Type": "application/json;charset=UTF-8"
}
endpoint = alexa_entity.entity_id()
# this sends all the properties of the Alexa Entity, whether they have
# changed or not. this should be improved, and properties that have not
# changed should be moved to the 'context' object
properties = list(alexa_entity.serialize_properties())
payload = {
API_CHANGE: {
'cause': {'type': _Cause.APP_INTERACTION},
'properties': properties
}
}
message = _AlexaResponse(name='ChangeReport', namespace='Alexa',
payload=payload)
message.set_endpoint_full(token, endpoint)
message_str = json.dumps(message.serialize())
try:
session = aiohttp_client.async_get_clientsession(hass)
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
response = await session.post(config.endpoint,
headers=headers,
data=message_str,
allow_redirects=True)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout calling LWA to get auth token.")
return None
response_text = await response.text()
_LOGGER.debug("Sent: %s", message_str)
_LOGGER.debug("Received (%s): %s", response.status, response_text)
if response.status != 202:
response_json = json.loads(response_text)
_LOGGER.error("Error when sending ChangeReport to Alexa: %s: %s",
response_json["payload"]["code"],
response_json["payload"]["description"])
@HANDLERS.register(('Alexa.Discovery', 'Discover'))
async def async_api_discovery(hass, config, directive, context):
"""Create a API formatted discovery response.
@ -1258,8 +1421,9 @@ async def async_api_discovery(hass, config, directive, context):
i.serialize_discovery() for i in alexa_entity.interfaces()]
if not endpoint['capabilities']:
_LOGGER.debug("Not exposing %s because it has no capabilities",
entity.entity_id)
_LOGGER.debug(
"Not exposing %s because it has no capabilities",
entity.entity_id)
continue
discovery_endpoints.append(endpoint)
@ -1270,6 +1434,25 @@ async def async_api_discovery(hass, config, directive, context):
)
@HANDLERS.register(('Alexa.Authorization', 'AcceptGrant'))
async def async_api_accept_grant(hass, config, directive, context):
"""Create a API formatted AcceptGrant response.
Async friendly.
"""
auth_code = directive.payload['grant']['code']
_LOGGER.debug("AcceptGrant code: %s", auth_code)
if AUTH_KEY in hass.data:
await hass.data[AUTH_KEY].async_do_auth(auth_code)
await async_enable_proactive_mode(hass, config)
return directive.response(
name='AcceptGrant.Response',
namespace='Alexa.Authorization',
payload={})
@HANDLERS.register(('Alexa.PowerController', 'TurnOn'))
async def async_api_turn_on(hass, config, directive, context):
"""Process a turn on request."""

View File

@ -16,7 +16,7 @@ from homeassistant.const import (
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send
REQUIREMENTS = ['pyarlo==0.2.2']
REQUIREMENTS = ['pyarlo==0.2.3']
_LOGGER = logging.getLogger(__name__)

View File

@ -14,7 +14,7 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
REQUIREMENTS = ['aioasuswrt==1.1.15']
REQUIREMENTS = ['aioasuswrt==1.1.17']
_LOGGER = logging.getLogger(__name__)

View File

@ -5,28 +5,28 @@
"no_available_service": "No hi ha serveis de notificaci\u00f3 disponibles."
},
"error": {
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho."
"invalid_code": "Codi inv\u00e0lid, si us plau torna a provar-ho."
},
"step": {
"init": {
"description": "Seleccioneu un dels serveis de notificaci\u00f3:",
"title": "Configureu una contrasenya d'un sol \u00fas a trav\u00e9s del component de notificacions"
"description": "Selecciona un dels serveis de notificaci\u00f3:",
"title": "Configuraci\u00f3 d'una contrasenya d'un sol \u00fas a trav\u00e9s del component de notificacions"
},
"setup": {
"description": "S'ha enviat una contrasenya d'un sol \u00fas mitjan\u00e7ant **notify.{notify_service}**. Introdu\u00efu-la a continuaci\u00f3:",
"title": "Verifiqueu la configuraci\u00f3"
"description": "S'ha enviat una contrasenya d'un sol \u00fas mitjan\u00e7ant **notify.{notify_service}**. Introdueix-la a continuaci\u00f3:",
"title": "Verificaci\u00f3 de la configuraci\u00f3"
}
},
"title": "Contrasenya d'un sol \u00fas del servei de notificacions"
},
"totp": {
"error": {
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho. Si obteniu aquest error repetidament, assegureu-vos que la data i hora de Home Assistant sigui correcta i precisa."
"invalid_code": "Codi inv\u00e0lid, si us plau torna a provar-ho. Si obtens aquest error repetidament, assegura't que la data i hora de Home Assistant siguin correctes i acurades."
},
"step": {
"init": {
"description": "Per activar la verificaci\u00f3 en dos passos mitjan\u00e7ant contrasenyes d'un sol \u00fas basades en temps, escanegeu el codi QR amb la vostre aplicaci\u00f3 de verificaci\u00f3. Si no en teniu cap, us recomanem [Google Authenticator](https://support.google.com/accounts/answer/1066447) o b\u00e9 [Authy](https://authy.com/). \n\n {qr_code} \n \nDespr\u00e9s d'escanejar el codi QR, introdu\u00efu el codi de sis d\u00edgits proporcionat per l'aplicaci\u00f3. Si teniu problemes per escanejar el codi QR, feu una configuraci\u00f3 manual amb el codi **`{code}`**.",
"title": "Configureu la verificaci\u00f3 en dos passos utilitzant TOTP"
"description": "Per activar la verificaci\u00f3 en dos passos mitjan\u00e7ant contrasenyes d'un sol \u00fas basades en temps, escaneja el codi QR amb la teva aplicaci\u00f3 de verificaci\u00f3. Si no en tens cap, et recomanem [Google Authenticator](https://support.google.com/accounts/answer/1066447) o b\u00e9 [Authy](https://authy.com/). \n\n {qr_code} \n \nDespr\u00e9s d'escanejar el codi QR, introdueix el codi de sis d\u00edgits proporcionat per l'aplicaci\u00f3. Si tens problemes per escanejar el codi QR, fes una configuraci\u00f3 manual amb el codi **`{code}`**.",
"title": "Configura la verificaci\u00f3 en dos passos utilitzant TOTP"
}
},
"title": "TOTP"

View File

@ -94,11 +94,11 @@ PLATFORM_SCHEMA = vol.Schema({
})
SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
TRIGGER_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_VARIABLES, default={}): dict,
})
@ -375,7 +375,15 @@ def _async_get_action(hass, config, name):
async def action(entity_id, variables, context):
"""Execute an action."""
_LOGGER.info('Executing %s', name)
await script_obj.async_run(variables, context)
hass.components.logbook.async_log_entry(
name, 'has been triggered', DOMAIN, entity_id)
try:
await script_obj.async_run(variables, context)
except Exception as err: # pylint: disable=broad-except
script_obj.async_log_exception(
_LOGGER,
'Error while executing automation {}'.format(entity_id), err)
return action

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.alarmdecoder import (
ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE,
CONF_ZONE_RFID, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE,
CONF_ZONE_RFID, CONF_ZONE_LOOP, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE,
SIGNAL_RFX_MESSAGE, SIGNAL_REL_MESSAGE, CONF_RELAY_ADDR,
CONF_RELAY_CHAN)
@ -37,10 +37,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
zone_rfid = device_config_data.get(CONF_ZONE_RFID)
zone_loop = device_config_data.get(CONF_ZONE_LOOP)
relay_addr = device_config_data.get(CONF_RELAY_ADDR)
relay_chan = device_config_data.get(CONF_RELAY_CHAN)
device = AlarmDecoderBinarySensor(
zone_num, zone_name, zone_type, zone_rfid, relay_addr, relay_chan)
zone_num, zone_name, zone_type, zone_rfid, zone_loop, relay_addr,
relay_chan)
devices.append(device)
add_entities(devices)
@ -51,7 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Representation of an AlarmDecoder binary sensor."""
def __init__(self, zone_number, zone_name, zone_type, zone_rfid,
def __init__(self, zone_number, zone_name, zone_type, zone_rfid, zone_loop,
relay_addr, relay_chan):
"""Initialize the binary_sensor."""
self._zone_number = zone_number
@ -59,6 +61,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self._state = None
self._name = zone_name
self._rfid = zone_rfid
self._loop = zone_loop
self._rfstate = None
self._relay_addr = relay_addr
self._relay_chan = relay_chan
@ -92,14 +95,14 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Return the state attributes."""
attr = {}
if self._rfid and self._rfstate is not None:
attr[ATTR_RF_BIT0] = True if self._rfstate & 0x01 else False
attr[ATTR_RF_LOW_BAT] = True if self._rfstate & 0x02 else False
attr[ATTR_RF_SUPERVISED] = True if self._rfstate & 0x04 else False
attr[ATTR_RF_BIT3] = True if self._rfstate & 0x08 else False
attr[ATTR_RF_LOOP3] = True if self._rfstate & 0x10 else False
attr[ATTR_RF_LOOP2] = True if self._rfstate & 0x20 else False
attr[ATTR_RF_LOOP4] = True if self._rfstate & 0x40 else False
attr[ATTR_RF_LOOP1] = True if self._rfstate & 0x80 else False
attr[ATTR_RF_BIT0] = bool(self._rfstate & 0x01)
attr[ATTR_RF_LOW_BAT] = bool(self._rfstate & 0x02)
attr[ATTR_RF_SUPERVISED] = bool(self._rfstate & 0x04)
attr[ATTR_RF_BIT3] = bool(self._rfstate & 0x08)
attr[ATTR_RF_LOOP3] = bool(self._rfstate & 0x10)
attr[ATTR_RF_LOOP2] = bool(self._rfstate & 0x20)
attr[ATTR_RF_LOOP4] = bool(self._rfstate & 0x40)
attr[ATTR_RF_LOOP1] = bool(self._rfstate & 0x80)
return attr
@property
@ -128,6 +131,8 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Update RF state."""
if self._rfid and message and message.serial_number == self._rfid:
self._rfstate = message.value
if self._loop:
self._state = 1 if message.loop[self._loop - 1] else 0
self.schedule_update_ha_state()
def _rel_message_callback(self, message):

View File

@ -0,0 +1,63 @@
"""Support for ESPHome binary sensors."""
import logging
from typing import TYPE_CHECKING, Optional
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.esphome import EsphomeEntity, \
platform_async_setup_entry
if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import BinarySensorInfo, BinarySensorState # noqa
DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up ESPHome binary sensors based on a config entry."""
# pylint: disable=redefined-outer-name
from aioesphomeapi import BinarySensorInfo, BinarySensorState # noqa
await platform_async_setup_entry(
hass, entry, async_add_entities,
component_key='binary_sensor',
info_type=BinarySensorInfo, entity_type=EsphomeBinarySensor,
state_type=BinarySensorState
)
class EsphomeBinarySensor(EsphomeEntity, BinarySensorDevice):
"""A binary sensor implementation for ESPHome."""
@property
def _static_info(self) -> 'BinarySensorInfo':
return super()._static_info
@property
def _state(self) -> Optional['BinarySensorState']:
return super()._state
@property
def is_on(self):
"""Return true if the binary sensor is on."""
if self._static_info.is_status_binary_sensor:
# Status binary sensors indicated connected state.
# So in their case what's usually _availability_ is now state
return self._entry_data.available
if self._state is None:
return None
return self._state.state
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._static_info.device_class
@property
def available(self):
"""Return True if entity is available."""
if self._static_info.is_status_binary_sensor:
return True
return super().available

View File

@ -10,6 +10,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT)
from homeassistant.components.fibaro import (
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
from homeassistant.const import (CONF_DEVICE_CLASS, CONF_ICON)
DEPENDENCIES = ['fibaro']
@ -45,6 +46,7 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorDevice):
super().__init__(fibaro_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
stype = None
devconf = fibaro_device.device_config
if fibaro_device.type in SENSOR_TYPES:
stype = fibaro_device.type
elif fibaro_device.baseType in SENSOR_TYPES:
@ -55,6 +57,10 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorDevice):
else:
self._device_class = None
self._icon = None
# device_config overrides:
self._device_class = devconf.get(CONF_DEVICE_CLASS,
self._device_class)
self._icon = devconf.get(CONF_ICON, self._icon)
@property
def icon(self):

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.1.8']
REQUIREMENTS = ['pyhik==0.1.9']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'

View File

@ -28,14 +28,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the HomematicIP Cloud binary sensor from a config entry."""
from homematicip.aio.device import (
AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector,
AsyncWaterSensor, AsyncRotaryHandleSensor)
AsyncWaterSensor, AsyncRotaryHandleSensor,
AsyncMotionDetectorPushButton)
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
devices = []
for device in home.devices:
if isinstance(device, (AsyncShutterContact, AsyncRotaryHandleSensor)):
devices.append(HomematicipShutterContact(home, device))
elif isinstance(device, AsyncMotionDetectorIndoor):
elif isinstance(device, (AsyncMotionDetectorIndoor,
AsyncMotionDetectorPushButton)):
devices.append(HomematicipMotionDetector(home, device))
elif isinstance(device, AsyncSmokeDetector):
devices.append(HomematicipSmokeDetector(home, device))

View File

@ -14,6 +14,7 @@ DEPENDENCIES = ['insteon']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {'openClosedSensor': 'opening',
'ioLincSensor': 'opening',
'motionSensor': 'motion',
'doorSensor': 'door',
'wetLeakSensor': 'moisture',
@ -58,7 +59,7 @@ class InsteonBinarySensor(InsteonEntity, BinarySensorDevice):
on_val = bool(self._insteon_device_state.value)
if self._insteon_device_state.name in ['lightSensor',
'openClosedSensor']:
'ioLincSensor']:
return not on_val
return on_val

View File

@ -52,7 +52,7 @@ def setup_platform(hass, config: ConfigType,
node.nid, node.parent_nid)
else:
device_type = _detect_device_type(node)
subnode_id = int(node.nid[-1])
subnode_id = int(node.nid[-1], 16)
if device_type in ('opening', 'moisture'):
# These sensors use an optional "negative" subnode 2 to snag
# all state changes

View File

@ -16,10 +16,10 @@ from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS, CONF_DEVICE)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC,
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
subscription)
MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -49,7 +49,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# This is an exception because MQTT is a message transport, not a protocol
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
@ -76,7 +77,7 @@ async def _async_setup_entity(config, async_add_entities, discovery_hash=None):
async_add_entities([MqttBinarySensor(config, discovery_hash)])
class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
@ -94,6 +95,7 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)
MqttAttributes.__init__(self, config)
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
@ -109,6 +111,7 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
"""Handle updated discovery message."""
config = PLATFORM_SCHEMA(discovery_payload)
self._config = config
await self.attributes_discovery_update(config)
await self.availability_discovery_update(config)
await self._subscribe_topics()
self.async_schedule_update_ha_state()
@ -132,7 +135,7 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(
payload)
payload, variables={'entity_id': self.entity_id})
if payload == self._config.get(CONF_PAYLOAD_ON):
self._state = True
elif payload == self._config.get(CONF_PAYLOAD_OFF):
@ -163,7 +166,9 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
await subscription.async_unsubscribe_topics(self.hass, self._sub_state)
self._sub_state = await subscription.async_unsubscribe_topics(
self.hass, self._sub_state)
await MqttAttributes.async_will_remove_from_hass(self)
await MqttAvailability.async_will_remove_from_hass(self)
@property

View File

@ -61,8 +61,7 @@ class MyStromView(HomeAssistantView):
'{}_{}'.format(button_id, button_action))
self.add_entities([self.buttons[entity_id]])
else:
new_state = True if self.buttons[entity_id].state == 'off' \
else False
new_state = self.buttons[entity_id].state == 'off'
self.buttons[entity_id].async_on_update(new_state)

View File

@ -0,0 +1,81 @@
"""
Support for Ness D8X/D16X zone states - represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ness_alarm/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.ness_alarm import (
CONF_ZONES, CONF_ZONE_TYPE, CONF_ZONE_NAME, CONF_ZONE_ID,
SIGNAL_ZONE_CHANGED, ZoneChangedData)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['ness_alarm']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Ness Alarm binary sensor devices."""
if not discovery_info:
return
configured_zones = discovery_info[CONF_ZONES]
devices = []
for zone_config in configured_zones:
zone_type = zone_config[CONF_ZONE_TYPE]
zone_name = zone_config[CONF_ZONE_NAME]
zone_id = zone_config[CONF_ZONE_ID]
device = NessZoneBinarySensor(zone_id=zone_id, name=zone_name,
zone_type=zone_type)
devices.append(device)
async_add_entities(devices)
class NessZoneBinarySensor(BinarySensorDevice):
"""Representation of an Ness alarm zone as a binary sensor."""
def __init__(self, zone_id, name, zone_type):
"""Initialize the binary_sensor."""
self._zone_id = zone_id
self._name = name
self._type = zone_type
self._state = 0
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ZONE_CHANGED, self._handle_zone_change)
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state == 1
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._type
@callback
def _handle_zone_change(self, data: ZoneChangedData):
"""Handle zone state update."""
if self._zone_id == data.zone_id:
self._state = data.state
self.async_schedule_update_ha_state()

View File

@ -7,8 +7,7 @@ https://home-assistant.io/components/binary_sensor.point/
import logging
from homeassistant.components.binary_sensor import (
DOMAIN as PARENT_DOMAIN, BinarySensorDevice)
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
from homeassistant.components.point import MinutPointEntity
from homeassistant.components.point.const import (
DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK)
@ -49,7 +48,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for device_class in EVENTS), True)
async_dispatcher_connect(
hass, POINT_DISCOVERY_NEW.format(PARENT_DOMAIN, POINT_DOMAIN),
hass, POINT_DISCOVERY_NEW.format(DOMAIN, POINT_DOMAIN),
async_discover_sensor)

View File

@ -8,9 +8,11 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.satel_integra import (CONF_ZONES,
CONF_OUTPUTS,
CONF_ZONE_NAME,
CONF_ZONE_TYPE,
SIGNAL_ZONES_UPDATED)
SIGNAL_ZONES_UPDATED,
SIGNAL_OUTPUTS_UPDATED)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -32,7 +34,17 @@ async def async_setup_platform(hass, config, async_add_entities,
for zone_num, device_config_data in configured_zones.items():
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type)
device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type,
SIGNAL_ZONES_UPDATED)
devices.append(device)
configured_outputs = discovery_info[CONF_OUTPUTS]
for zone_num, device_config_data in configured_outputs.items():
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type,
SIGNAL_OUTPUTS_UPDATED)
devices.append(device)
async_add_entities(devices)
@ -41,17 +53,18 @@ async def async_setup_platform(hass, config, async_add_entities,
class SatelIntegraBinarySensor(BinarySensorDevice):
"""Representation of an Satel Integra binary sensor."""
def __init__(self, zone_number, zone_name, zone_type):
def __init__(self, device_number, device_name, zone_type, react_to_signal):
"""Initialize the binary_sensor."""
self._zone_number = zone_number
self._name = zone_name
self._device_number = device_number
self._name = device_name
self._zone_type = zone_type
self._state = 0
self._react_to_signal = react_to_signal
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated)
self.hass, self._react_to_signal, self._devices_updated)
@property
def name(self):
@ -80,9 +93,9 @@ class SatelIntegraBinarySensor(BinarySensorDevice):
return self._zone_type
@callback
def _zones_updated(self, zones):
def _devices_updated(self, zones):
"""Update the zone's state, if needed."""
if self._zone_number in zones \
and self._state != zones[self._zone_number]:
self._state = zones[self._zone_number]
if self._device_number in zones \
and self._state != zones[self._device_number]:
self._state = zones[self._device_number]
self.async_schedule_update_ha_state()

View File

@ -9,22 +9,35 @@ https://home-assistant.io/components/binary_sensor.tellduslive/
"""
import logging
from homeassistant.components import tellduslive
from homeassistant.components import binary_sensor, tellduslive
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.tellduslive.entry import TelldusLiveEntity
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Tellstick sensors."""
if discovery_info is None:
return
client = hass.data[tellduslive.DOMAIN]
add_entities(
TelldusLiveSensor(client, binary_sensor)
for binary_sensor in discovery_info
)
"""Old way of setting up TelldusLive.
Can only be called when a user accidentally mentions the platform in their
config. But even in that case it would have been ignored.
"""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up tellduslive sensors dynamically."""
async def async_discover_binary_sensor(device_id):
"""Discover and add a discovered sensor."""
client = hass.data[tellduslive.DOMAIN]
async_add_entities([TelldusLiveSensor(client, device_id)])
async_dispatcher_connect(
hass,
tellduslive.TELLDUS_DISCOVERY_NEW.format(binary_sensor.DOMAIN,
tellduslive.DOMAIN),
async_discover_binary_sensor)
class TelldusLiveSensor(TelldusLiveEntity, BinarySensorDevice):

View File

@ -4,7 +4,10 @@ Support for WeMo sensors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.wemo/
"""
import asyncio
import logging
import async_timeout
import requests
from homeassistant.components.binary_sensor import BinarySensorDevice
@ -15,7 +18,7 @@ DEPENDENCIES = ['wemo']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Register discovered WeMo binary sensors."""
from pywemo import discovery
@ -31,7 +34,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None):
raise PlatformNotReady
if device:
add_entities_callback([WemoBinarySensor(hass, device)])
add_entities([WemoBinarySensor(hass, device)])
class WemoBinarySensor(BinarySensorDevice):
@ -41,48 +44,90 @@ class WemoBinarySensor(BinarySensorDevice):
"""Initialize the WeMo sensor."""
self.wemo = device
self._state = None
self._available = True
self._update_lock = None
self._model_name = self.wemo.model_name
self._name = self.wemo.name
self._serialnumber = self.wemo.serialnumber
wemo = hass.components.wemo
wemo.SUBSCRIPTION_REGISTRY.register(self.wemo)
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)
def _update_callback(self, _device, _type, _params):
"""Handle state changes."""
_LOGGER.info("Subscription update for %s", _device)
def _subscription_callback(self, _device, _type, _params):
"""Update the state by the Wemo sensor."""
_LOGGER.debug("Subscription update for %s", self.name)
updated = self.wemo.subscription_update(_type, _params)
self._update(force_update=(not updated))
self.hass.add_job(
self._async_locked_subscription_callback(not updated))
if not hasattr(self, 'hass'):
async def _async_locked_subscription_callback(self, force_update):
"""Handle an update from a subscription."""
# If an update is in progress, we don't do anything
if self._update_lock.locked():
return
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed with subscriptions."""
return False
await self._async_locked_update(force_update)
self.async_schedule_update_ha_state()
async def async_added_to_hass(self):
"""Wemo sensor added to HASS."""
# Define inside async context so we know our event loop
self._update_lock = asyncio.Lock()
registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
await self.hass.async_add_executor_job(registry.register, self.wemo)
registry.on(self.wemo, None, self._subscription_callback)
async def async_update(self):
"""Update WeMo state.
Wemo has an aggressive retry logic that sometimes can take over a
minute to return. If we don't get a state after 5 seconds, assume the
Wemo sensor is unreachable. If update goes through, it will be made
available again.
"""
# If an update is in progress, we don't do anything
if self._update_lock.locked():
return
try:
with async_timeout.timeout(5):
await asyncio.shield(self._async_locked_update(True))
except asyncio.TimeoutError:
_LOGGER.warning('Lost connection to %s', self.name)
self._available = False
async def _async_locked_update(self, force_update):
"""Try updating within an async lock."""
async with self._update_lock:
await self.hass.async_add_executor_job(self._update, force_update)
def _update(self, force_update=True):
"""Update the sensor state."""
try:
self._state = self.wemo.get_state(force_update)
if not self._available:
_LOGGER.info('Reconnected to %s', self.name)
self._available = True
except AttributeError as err:
_LOGGER.warning("Could not update status for %s (%s)",
self.name, err)
self._available = False
@property
def unique_id(self):
"""Return the id of this WeMo device."""
return self.wemo.serialnumber
"""Return the id of this WeMo sensor."""
return self._serialnumber
@property
def name(self):
"""Return the name of the service if any."""
return self.wemo.name
return self._name
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
def update(self):
"""Update WeMo state."""
self._update(force_update=True)
def _update(self, force_update=True):
try:
self._state = self.wemo.get_state(force_update)
except AttributeError as err:
_LOGGER.warning(
"Could not update status for %s (%s)", self.name, err)
@property
def available(self):
"""Return true if sensor is available."""
return self._available

View File

@ -409,10 +409,14 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'double'
elif value == 'both_click':
click_type = 'both'
elif value == 'double_both_click':
click_type = 'double_both'
elif value == 'shake':
click_type = 'shake'
elif value in ['long_click', 'long_both_click']:
return False
elif value == 'long_click':
click_type = 'long'
elif value == 'long_both_click':
click_type = 'long_both'
else:
_LOGGER.warning("Unsupported click_type detected: %s", value)
return False
@ -465,4 +469,12 @@ class XiaomiCube(XiaomiBinarySensor):
})
self._last_action = 'rotate'
if 'rotate_degree' in data:
self._hass.bus.fire('xiaomi_aqara.cube_action', {
'entity_id': self.entity_id,
'action_type': 'rotate',
'action_value': float(data['rotate_degree'].replace(",", "."))
})
self._last_action = 'rotate'
return True

View File

@ -9,9 +9,14 @@ import logging
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
from homeassistant.components.zha import helpers
from homeassistant.components.zha.const import (
DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW)
DATA_ZHA, DATA_ZHA_DISPATCHERS, REPORT_CONFIG_IMMEDIATE, ZHA_DISCOVERY_NEW)
from homeassistant.components.zha.entities import ZhaEntity
from homeassistant.const import STATE_ON
from homeassistant.components.zha.entities.listeners import (
OnOffListener, LevelListener
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.restore_state import RestoreEntity
_LOGGER = logging.getLogger(__name__)
@ -26,6 +31,7 @@ CLASS_MAPPING = {
0x002b: 'gas',
0x002d: 'vibration',
}
DEVICE_CLASS_OCCUPANCY = 'occupancy'
async def async_setup_platform(hass, config, async_add_entities,
@ -54,14 +60,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async def _async_setup_entities(hass, config_entry, async_add_entities,
discovery_infos):
"""Set up the ZHA binary sensors."""
from zigpy.zcl.clusters.general import OnOff
from zigpy.zcl.clusters.measurement import OccupancySensing
from zigpy.zcl.clusters.security import IasZone
entities = []
for discovery_info in discovery_infos:
from zigpy.zcl.clusters.general import OnOff
from zigpy.zcl.clusters.security import IasZone
if IasZone.cluster_id in discovery_info['in_clusters']:
entities.append(await _async_setup_iaszone(discovery_info))
elif OccupancySensing.cluster_id in discovery_info['in_clusters']:
entities.append(
BinarySensor(DEVICE_CLASS_OCCUPANCY, **discovery_info))
elif OnOff.cluster_id in discovery_info['out_clusters']:
entities.append(await _async_setup_remote(discovery_info))
entities.append(Remote(**discovery_info))
async_add_entities(entities, update_before_add=True)
@ -70,10 +81,6 @@ async def _async_setup_iaszone(discovery_info):
device_class = None
from zigpy.zcl.clusters.security import IasZone
cluster = discovery_info['in_clusters'][IasZone.cluster_id]
if discovery_info['new_join']:
await cluster.bind()
ieee = cluster.endpoint.device.application.ieee
await cluster.write_attributes({'cie_addr': ieee})
try:
zone_type = await cluster['zone_type']
@ -82,33 +89,11 @@ async def _async_setup_iaszone(discovery_info):
# If we fail to read from the device, use a non-specific class
pass
return BinarySensor(device_class, **discovery_info)
return IasZoneSensor(device_class, **discovery_info)
async def _async_setup_remote(discovery_info):
remote = Remote(**discovery_info)
if discovery_info['new_join']:
from zigpy.zcl.clusters.general import OnOff, LevelControl
out_clusters = discovery_info['out_clusters']
if OnOff.cluster_id in out_clusters:
cluster = out_clusters[OnOff.cluster_id]
await helpers.configure_reporting(
remote.entity_id, cluster, 0, min_report=0, max_report=600,
reportable_change=1
)
if LevelControl.cluster_id in out_clusters:
cluster = out_clusters[LevelControl.cluster_id]
await helpers.configure_reporting(
remote.entity_id, cluster, 0, min_report=1, max_report=600,
reportable_change=1
)
return remote
class BinarySensor(ZhaEntity, BinarySensorDevice):
"""The ZHA Binary Sensor."""
class IasZoneSensor(RestoreEntity, ZhaEntity, BinarySensorDevice):
"""The IasZoneSensor Binary Sensor."""
_domain = DOMAIN
@ -119,11 +104,6 @@ class BinarySensor(ZhaEntity, BinarySensorDevice):
from zigpy.zcl.clusters.security import IasZone
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
@property
def should_poll(self) -> bool:
"""Let zha handle polling."""
return False
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
@ -147,6 +127,26 @@ class BinarySensor(ZhaEntity, BinarySensorDevice):
res = self._ias_zone_cluster.enroll_response(0, 0)
self.hass.async_add_job(res)
async def async_added_to_hass(self):
"""Run when about to be added to hass."""
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
if self._state is not None or old_state is None:
return
_LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state)
if old_state.state == STATE_ON:
self._state = 3
else:
self._state = 0
async def async_configure(self):
"""Configure IAS device."""
await self._ias_zone_cluster.bind()
ieee = self._ias_zone_cluster.endpoint.device.application.ieee
await self._ias_zone_cluster.write_attributes({'cie_addr': ieee})
_LOGGER.debug("%s: finished configuration", self.entity_id)
async def async_update(self):
"""Retrieve latest state."""
from zigpy.types.basic import uint16_t
@ -160,81 +160,33 @@ class BinarySensor(ZhaEntity, BinarySensorDevice):
self._state = result.get('zone_status', self._state) & 3
class Remote(ZhaEntity, BinarySensorDevice):
class Remote(RestoreEntity, ZhaEntity, BinarySensorDevice):
"""ZHA switch/remote controller/button."""
_domain = DOMAIN
class OnOffListener:
"""Listener for the OnOff Zigbee cluster."""
def __init__(self, entity):
"""Initialize OnOffListener."""
self._entity = entity
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id in (0x0000, 0x0040):
self._entity.set_state(False)
elif command_id in (0x0001, 0x0041, 0x0042):
self._entity.set_state(True)
elif command_id == 0x0002:
self._entity.set_state(not self._entity.is_on)
def attribute_updated(self, attrid, value):
"""Handle attribute updates on this cluster."""
if attrid == 0:
self._entity.set_state(value)
def zdo_command(self, *args, **kwargs):
"""Handle ZDO commands on this cluster."""
pass
class LevelListener:
"""Listener for the LevelControl Zigbee cluster."""
def __init__(self, entity):
"""Initialize LevelListener."""
self._entity = entity
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id in (0x0000, 0x0004): # move_to_level, -with_on_off
self._entity.set_level(args[0])
elif command_id in (0x0001, 0x0005): # move, -with_on_off
# We should dim slowly -- for now, just step once
rate = args[1]
if args[0] == 0xff:
rate = 10 # Should read default move rate
self._entity.move_level(-rate if args[0] else rate)
elif command_id in (0x0002, 0x0006): # step, -with_on_off
# Step (technically may change on/off)
self._entity.move_level(-args[1] if args[0] else args[1])
def attribute_update(self, attrid, value):
"""Handle attribute updates on this cluster."""
if attrid == 0:
self._entity.set_level(value)
def zdo_command(self, *args, **kwargs):
"""Handle ZDO commands on this cluster."""
pass
def __init__(self, **kwargs):
"""Initialize Switch."""
super().__init__(**kwargs)
self._state = False
self._level = 0
from zigpy.zcl.clusters import general
self._out_listeners = {
general.OnOff.cluster_id: self.OnOffListener(self),
general.LevelControl.cluster_id: self.LevelListener(self),
general.OnOff.cluster_id: OnOffListener(
self,
self._out_clusters[general.OnOff.cluster_id]
)
}
@property
def should_poll(self) -> bool:
"""Let zha handle polling."""
return False
out_clusters = kwargs.get('out_clusters')
self._zcl_reporting = {}
if general.LevelControl.cluster_id in out_clusters:
self._out_listeners.update({
general.LevelControl.cluster_id: LevelListener(
self,
out_clusters[general.LevelControl.cluster_id]
)
})
@property
def is_on(self) -> bool:
@ -249,6 +201,11 @@ class Remote(ZhaEntity, BinarySensorDevice):
})
return self._device_state_attributes
@property
def zcl_reporting_config(self):
"""Return ZCL attribute reporting configuration."""
return self._zcl_reporting
def move_level(self, change):
"""Increment the level, setting state if appropriate."""
if not self._state and change > 0:
@ -270,6 +227,31 @@ class Remote(ZhaEntity, BinarySensorDevice):
self._level = 255
self.async_schedule_update_ha_state()
async def async_configure(self):
"""Bind clusters."""
from zigpy.zcl.clusters import general
await helpers.bind_cluster(
self.entity_id,
self._out_clusters[general.OnOff.cluster_id]
)
if general.LevelControl.cluster_id in self._out_clusters:
await helpers.bind_cluster(
self.entity_id,
self._out_clusters[general.LevelControl.cluster_id]
)
async def async_added_to_hass(self):
"""Run when about to be added to hass."""
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
if self._state is not None or old_state is None:
return
_LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state)
if 'level' in old_state.attributes:
self._level = old_state.attributes['level']
self._state = old_state.state == STATE_ON
async def async_update(self):
"""Retrieve latest state."""
from zigpy.zcl.clusters.general import OnOff
@ -280,3 +262,56 @@ class Remote(ZhaEntity, BinarySensorDevice):
only_cache=(not self._initialized)
)
self._state = result.get('on_off', self._state)
class BinarySensor(RestoreEntity, ZhaEntity, BinarySensorDevice):
"""ZHA switch."""
_domain = DOMAIN
_device_class = None
value_attribute = 0
def __init__(self, device_class, **kwargs):
"""Initialize the ZHA binary sensor."""
super().__init__(**kwargs)
self._device_class = device_class
self._cluster = list(kwargs['in_clusters'].values())[0]
def attribute_updated(self, attribute, value):
"""Handle attribute update from device."""
_LOGGER.debug("Attribute updated: %s %s %s", self, attribute, value)
if attribute == self.value_attribute:
self._state = bool(value)
self.async_schedule_update_ha_state()
async def async_added_to_hass(self):
"""Run when about to be added to hass."""
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
if self._state is not None or old_state is None:
return
_LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state)
self._state = old_state.state == STATE_ON
@property
def cluster(self):
"""Zigbee cluster for this entity."""
return self._cluster
@property
def zcl_reporting_config(self):
"""ZHA reporting configuration."""
return {self.cluster: {self.value_attribute: REPORT_CONFIG_IMMEDIATE}}
@property
def is_on(self) -> bool:
"""Return if the switch is on based on the statemachine."""
if self._state is None:
return False
return self._state
@property
def device_class(self) -> str:
"""Return device class from component DEVICE_CLASSES."""
return self._device_class

View File

@ -61,7 +61,7 @@ FALLBACK_STREAM_INTERVAL = 1 # seconds
MIN_STREAM_INTERVAL = 0.5 # seconds
CAMERA_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/camera.axis/
import logging
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging)
from homeassistant.const import (
CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
@ -29,6 +29,8 @@ def _get_image_url(host, port, mode):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Axis camera."""
filter_urllib3_logging()
camera_config = {
CONF_NAME: discovery_info[CONF_NAME],
CONF_USERNAME: discovery_info[CONF_USERNAME],

View File

@ -16,7 +16,7 @@ import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, CONF_VERIFY_SSL)
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_web)
@ -29,6 +29,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
CONTENT_TYPE_HEADER = 'Content-Type'
DEFAULT_NAME = 'Mjpeg Camera'
DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MJPEG_URL): cv.url,
@ -38,13 +39,22 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up a MJPEG IP Camera."""
# Filter header errors from urllib3 due to a urllib3 bug
filter_urllib3_logging()
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_entities([MjpegCamera(config)])
def filter_urllib3_logging():
"""Filter header errors from urllib3 due to a urllib3 bug."""
urllib3_logger = logging.getLogger("urllib3.connectionpool")
if not any(isinstance(x, NoHeaderErrorFilter)
for x in urllib3_logger.filters):
@ -52,10 +62,6 @@ async def async_setup_platform(hass, config, async_add_entities,
NoHeaderErrorFilter()
)
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_entities([MjpegCamera(config)])
def extract_image_from_mjpeg(stream):
"""Take in a MJPEG stream object, return the jpg from it."""
@ -95,6 +101,7 @@ class MjpegCamera(Camera):
self._auth = aiohttp.BasicAuth(
self._username, password=self._password
)
self._verify_ssl = device_info.get(CONF_VERIFY_SSL)
async def async_camera_image(self):
"""Return a still image response from the camera."""
@ -105,7 +112,10 @@ class MjpegCamera(Camera):
self.camera_image)
return image
websession = async_get_clientsession(self.hass)
websession = async_get_clientsession(
self.hass,
verify_ssl=self._verify_ssl
)
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = await websession.get(
@ -128,7 +138,12 @@ class MjpegCamera(Camera):
else:
auth = HTTPBasicAuth(self._username, self._password)
req = requests.get(
self._mjpeg_url, auth=auth, stream=True, timeout=10)
self._mjpeg_url,
auth=auth,
stream=True,
timeout=10,
verify=self._verify_ssl
)
else:
req = requests.get(self._mjpeg_url, stream=True, timeout=10)
@ -144,7 +159,10 @@ class MjpegCamera(Camera):
return await super().handle_async_mjpeg_stream(request)
# connect to stream
websession = async_get_clientsession(self.hass)
websession = async_get_clientsession(
self.hass,
verify_ssl=self._verify_ssl
)
stream_coro = websession.get(self._mjpeg_url, auth=self._auth)
return await async_aiohttp_proxy_web(self.hass, request, stream_coro)

View File

@ -8,6 +8,11 @@ from datetime import timedelta
import logging
import requests
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA
from homeassistant.const import CONF_MONITORED_CONDITIONS
import homeassistant.helpers.config_validation as cv
from homeassistant.components.camera import Camera
from homeassistant.components.skybell import (
@ -19,14 +24,33 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
IMAGE_AVATAR = 'avatar'
IMAGE_ACTIVITY = 'activity'
CONF_ACTIVITY_NAME = 'activity_name'
CONF_AVATAR_NAME = 'avatar_name'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=[IMAGE_AVATAR]):
vol.All(cv.ensure_list, [vol.In([IMAGE_AVATAR, IMAGE_ACTIVITY])]),
vol.Optional(CONF_ACTIVITY_NAME): cv.string,
vol.Optional(CONF_AVATAR_NAME): cv.string,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the platform for a Skybell device."""
cond = config[CONF_MONITORED_CONDITIONS]
names = {}
names[IMAGE_ACTIVITY] = config.get(CONF_ACTIVITY_NAME)
names[IMAGE_AVATAR] = config.get(CONF_AVATAR_NAME)
skybell = hass.data.get(SKYBELL_DOMAIN)
sensors = []
for device in skybell.get_devices():
sensors.append(SkybellCamera(device))
for camera_type in cond:
sensors.append(SkybellCamera(device, camera_type,
names.get(camera_type)))
add_entities(sensors, True)
@ -34,11 +58,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class SkybellCamera(SkybellDevice, Camera):
"""A camera implementation for Skybell devices."""
def __init__(self, device):
def __init__(self, device, camera_type, name=None):
"""Initialize a camera for a Skybell device."""
self._type = camera_type
SkybellDevice.__init__(self, device)
Camera.__init__(self)
self._name = self._device.name
if name is not None:
self._name = "{} {}".format(self._device.name, name)
else:
self._name = self._device.name
self._url = None
self._response = None
@ -47,12 +75,19 @@ class SkybellCamera(SkybellDevice, Camera):
"""Return the name of the sensor."""
return self._name
@property
def image_url(self):
"""Get the camera image url based on type."""
if self._type == IMAGE_ACTIVITY:
return self._device.activity_image
return self._device.image
def camera_image(self):
"""Get the latest camera image."""
super().update()
if self._url != self._device.image:
self._url = self._device.image
if self._url != self.image_url:
self._url = self.image_url
try:
self._response = requests.get(

View File

@ -17,7 +17,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['aioftp==0.10.1']
REQUIREMENTS = ['aioftp==0.12.0']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)

View File

@ -6,9 +6,9 @@ https://home-assistant.io/components/camera.zoneminder/
"""
import logging
from homeassistant.const import CONF_NAME
from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging)
from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -18,6 +18,7 @@ DEPENDENCIES = ['zoneminder']
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder cameras."""
filter_urllib3_logging()
zm_client = hass.data[ZONEMINDER_DOMAIN]
monitors = zm_client.get_monitors()
@ -28,22 +29,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
cameras = []
for monitor in monitors:
_LOGGER.info("Initializing camera %s", monitor.id)
cameras.append(ZoneMinderCamera(monitor))
cameras.append(ZoneMinderCamera(monitor, zm_client.verify_ssl))
add_entities(cameras)
class ZoneMinderCamera(MjpegCamera):
"""Representation of a ZoneMinder Monitor Stream."""
def __init__(self, monitor):
def __init__(self, monitor, verify_ssl):
"""Initialize as a subclass of MjpegCamera."""
device_info = {
CONF_NAME: monitor.name,
CONF_MJPEG_URL: monitor.mjpeg_image_url,
CONF_STILL_IMAGE_URL: monitor.still_image_url
CONF_STILL_IMAGE_URL: monitor.still_image_url,
CONF_VERIFY_SSL: verify_ssl
}
super().__init__(device_info)
self._is_recording = None
self._is_available = None
self._monitor = monitor
@property
@ -55,8 +58,14 @@ class ZoneMinderCamera(MjpegCamera):
"""Update our recording state from the ZM API."""
_LOGGER.debug("Updating camera state for monitor %i", self._monitor.id)
self._is_recording = self._monitor.is_recording
self._is_available = self._monitor.is_available
@property
def is_recording(self):
"""Return whether the monitor is in alarm mode."""
return self._is_recording
@property
def available(self):
"""Return True if entity is available."""
return self._is_available

View File

@ -6,7 +6,7 @@
},
"step": {
"confirm": {
"description": "Voleu configurar Google Cast?",
"description": "Vols configurar Google Cast?",
"title": "Google Cast"
}
},

View File

@ -1,7 +1,8 @@
{
"config": {
"abort": {
"no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton."
"no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.",
"single_instance_allowed": "Csak egyetlen Google Cast konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges."
},
"step": {
"confirm": {

View File

@ -92,15 +92,15 @@ CONVERTIBLE_ATTRIBUTE = [
_LOGGER = logging.getLogger(__name__)
ON_OFF_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
SET_AWAY_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AWAY_MODE): cv.boolean,
})
SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AUX_HEAT): cv.boolean,
})
SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
@ -110,28 +110,28 @@ SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
}
))
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_FAN_MODE): cv.string,
})
SET_HOLD_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_HOLD_MODE): cv.string,
})
SET_OPERATION_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_OPERATION_MODE): cv.string,
})
SET_HUMIDITY_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_HUMIDITY): vol.Coerce(float),
})
SET_SWING_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_SWING_MODE): cv.string,
})

View File

@ -15,14 +15,13 @@ from homeassistant.components.climate import (
STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
ClimateDevice)
from homeassistant.components.daikin import (
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE,
daikin_api_setup)
from homeassistant.components.daikin import DOMAIN as DAIKIN_DOMAIN
from homeassistant.components.daikin.const import (
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pydaikin==0.8']
_LOGGER = logging.getLogger(__name__)
@ -60,18 +59,18 @@ HA_ATTR_TO_DAIKIN = {
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Daikin HVAC platform."""
if discovery_info is not None:
host = discovery_info.get('ip')
name = None
_LOGGER.debug("Discovered a Daikin AC on %s", host)
else:
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
_LOGGER.debug("Added Daikin AC on %s", host)
"""Old way of setting up the Daikin HVAC platform.
api = daikin_api_setup(hass, host, name)
add_entities([DaikinClimate(api)], True)
Can only be called when a user accidentally mentions the platform in their
config. But even in that case it would have been ignored.
"""
pass
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Daikin climate based on config_entry."""
daikin_api = hass.data[DAIKIN_DOMAIN].get(entry.entry_id)
async_add_entities([DaikinClimate(daikin_api)])
class DaikinClimate(ClimateDevice):
@ -266,3 +265,8 @@ class DaikinClimate(ClimateDevice):
def update(self):
"""Retrieve latest state."""
self._api.update()
@property
def device_info(self):
"""Return a device description for device registry."""
return self._api.device_info

View File

@ -9,7 +9,8 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import (
STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice,
STATE_ON, STATE_OFF, STATE_HEAT, STATE_MANUAL, STATE_ECO, PLATFORM_SCHEMA,
ClimateDevice,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
SUPPORT_ON_OFF)
from homeassistant.const import (
@ -21,8 +22,6 @@ REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.45']
_LOGGER = logging.getLogger(__name__)
STATE_BOOST = 'boost'
STATE_AWAY = 'away'
STATE_MANUAL = 'manual'
ATTR_STATE_WINDOW_OPEN = 'window_open'
ATTR_STATE_VALVE = 'valve'
@ -65,10 +64,10 @@ class EQ3BTSmartThermostat(ClimateDevice):
self.modes = {
eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Auto: STATE_HEAT,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY,
eq3.Mode.Away: STATE_ECO,
}
self.reverse_modes = {v: k for k, v in self.modes.items()}
@ -140,20 +139,20 @@ class EQ3BTSmartThermostat(ClimateDevice):
def turn_away_mode_off(self):
"""Away mode off turns to AUTO mode."""
self.set_operation_mode(STATE_AUTO)
self.set_operation_mode(STATE_HEAT)
def turn_away_mode_on(self):
"""Set away mode on."""
self.set_operation_mode(STATE_AWAY)
self.set_operation_mode(STATE_ECO)
@property
def is_away_mode_on(self):
"""Return if we are away."""
return self.current_operation == STATE_AWAY
return self.current_operation == STATE_ECO
def turn_on(self):
"""Turn device on."""
self.set_operation_mode(STATE_AUTO)
self.set_operation_mode(STATE_HEAT)
def turn_off(self):
"""Turn device off."""

View File

@ -50,23 +50,23 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
def update_characteristics(self, characteristics):
"""Synchronise device state with Home Assistant."""
# pylint: disable=import-error
from homekit import CharacteristicsTypes as ctypes
from homekit.models.characteristics import CharacteristicsTypes
for characteristic in characteristics:
ctype = characteristic['type']
if ctype == ctypes.HEATING_COOLING_CURRENT:
if ctype == CharacteristicsTypes.HEATING_COOLING_CURRENT:
self._state = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
if ctype == ctypes.HEATING_COOLING_TARGET:
if ctype == CharacteristicsTypes.HEATING_COOLING_TARGET:
self._chars['target_mode'] = characteristic['iid']
self._features |= SUPPORT_OPERATION_MODE
self._current_mode = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
self._valid_modes = [MODE_HOMEKIT_TO_HASS.get(
mode) for mode in characteristic['valid-values']]
elif ctype == ctypes.TEMPERATURE_CURRENT:
elif ctype == CharacteristicsTypes.TEMPERATURE_CURRENT:
self._current_temp = characteristic['value']
elif ctype == ctypes.TEMPERATURE_TARGET:
elif ctype == CharacteristicsTypes.TEMPERATURE_TARGET:
self._chars['target_temp'] = characteristic['iid']
self._features |= SUPPORT_TARGET_TEMPERATURE
self._target_temp = characteristic['value']

View File

@ -6,14 +6,17 @@ https://home-assistant.io/components/climate.knx/
"""
import voluptuous as vol
from homeassistant.components.climate import (
PLATFORM_SCHEMA, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
ClimateDevice)
from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import (
PLATFORM_SCHEMA, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE, STATE_HEAT,
STATE_IDLE, STATE_MANUAL, STATE_DRY,
STATE_FAN_ONLY, STATE_ECO, ClimateDevice)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS)
from homeassistant.core import callback
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
CONF_SETPOINT_SHIFT_ADDRESS = 'setpoint_shift_address'
CONF_SETPOINT_SHIFT_STATE_ADDRESS = 'setpoint_shift_state_address'
@ -26,10 +29,17 @@ CONF_OPERATION_MODE_ADDRESS = 'operation_mode_address'
CONF_OPERATION_MODE_STATE_ADDRESS = 'operation_mode_state_address'
CONF_CONTROLLER_STATUS_ADDRESS = 'controller_status_address'
CONF_CONTROLLER_STATUS_STATE_ADDRESS = 'controller_status_state_address'
CONF_CONTROLLER_MODE_ADDRESS = 'controller_mode_address'
CONF_CONTROLLER_MODE_STATE_ADDRESS = 'controller_mode_state_address'
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = \
'operation_mode_frost_protection_address'
CONF_OPERATION_MODE_NIGHT_ADDRESS = 'operation_mode_night_address'
CONF_OPERATION_MODE_COMFORT_ADDRESS = 'operation_mode_comfort_address'
CONF_OPERATION_MODES = 'operation_modes'
CONF_ON_OFF_ADDRESS = 'on_off_address'
CONF_ON_OFF_STATE_ADDRESS = 'on_off_state_address'
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
DEFAULT_NAME = 'KNX Climate'
DEFAULT_SETPOINT_SHIFT_STEP = 0.5
@ -37,6 +47,21 @@ DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
DEPENDENCIES = ['knx']
# Map KNX operation modes to HA modes. This list might not be full.
OPERATION_MODES = {
# Map DPT 201.100 HVAC operating modes
"Frost Protection": STATE_MANUAL,
"Night": STATE_IDLE,
"Standby": STATE_ECO,
"Comfort": STATE_HEAT,
# Map DPT 201.104 HVAC control modes
"Fan only": STATE_FAN_ONLY,
"Dehumidification": STATE_DRY
}
OPERATION_MODES_INV = dict((
reversed(item) for item in OPERATION_MODES.items()))
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
@ -54,9 +79,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODES): vol.All(cv.ensure_list,
[vol.In(OPERATION_MODES)]),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
})
@ -84,6 +117,30 @@ def async_add_entities_config(hass, config, async_add_entities):
"""Set up climate for KNX platform configured within platform."""
import xknx
climate_mode = xknx.devices.ClimateMode(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME) + " Mode",
group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS),
group_address_controller_status=config.get(
CONF_CONTROLLER_STATUS_ADDRESS),
group_address_controller_status_state=config.get(
CONF_CONTROLLER_STATUS_STATE_ADDRESS),
group_address_controller_mode=config.get(
CONF_CONTROLLER_MODE_ADDRESS),
group_address_controller_mode_state=config.get(
CONF_CONTROLLER_MODE_STATE_ADDRESS),
group_address_operation_mode_protection=config.get(
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS),
group_address_operation_mode_night=config.get(
CONF_OPERATION_MODE_NIGHT_ADDRESS),
group_address_operation_mode_comfort=config.get(
CONF_OPERATION_MODE_COMFORT_ADDRESS),
operation_modes=config.get(
CONF_OPERATION_MODES))
hass.data[DATA_KNX].xknx.devices.add(climate_mode)
climate = xknx.devices.Climate(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
@ -96,20 +153,15 @@ def async_add_entities_config(hass, config, async_add_entities):
setpoint_shift_step=config.get(CONF_SETPOINT_SHIFT_STEP),
setpoint_shift_max=config.get(CONF_SETPOINT_SHIFT_MAX),
setpoint_shift_min=config.get(CONF_SETPOINT_SHIFT_MIN),
group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS),
group_address_controller_status=config.get(
CONF_CONTROLLER_STATUS_ADDRESS),
group_address_controller_status_state=config.get(
CONF_CONTROLLER_STATUS_STATE_ADDRESS),
group_address_operation_mode_protection=config.get(
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS),
group_address_operation_mode_night=config.get(
CONF_OPERATION_MODE_NIGHT_ADDRESS),
group_address_operation_mode_comfort=config.get(
CONF_OPERATION_MODE_COMFORT_ADDRESS))
group_address_on_off=config.get(
CONF_ON_OFF_ADDRESS),
group_address_on_off_state=config.get(
CONF_ON_OFF_STATE_ADDRESS),
min_temp=config.get(CONF_MIN_TEMP),
max_temp=config.get(CONF_MAX_TEMP),
mode=climate_mode)
hass.data[DATA_KNX].xknx.devices.add(climate)
async_add_entities([KNXClimate(climate)])
@ -119,26 +171,25 @@ class KNXClimate(ClimateDevice):
def __init__(self, device):
"""Initialize of a KNX climate device."""
self.device = device
self._unit_of_measurement = TEMP_CELSIUS
@property
def supported_features(self):
"""Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE
if self.device.supports_operation_mode:
if self.device.mode.supports_operation_mode:
support |= SUPPORT_OPERATION_MODE
if self.device.supports_on_off:
support |= SUPPORT_ON_OFF
return support
def async_register_callbacks(self):
async def async_added_to_hass(self):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
@property
def name(self):
"""Return the name of the KNX device."""
@ -157,7 +208,7 @@ class KNXClimate(ClimateDevice):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
return self._unit_of_measurement
@property
def current_temperature(self):
@ -195,20 +246,37 @@ class KNXClimate(ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.device.supports_operation_mode:
return self.device.operation_mode.value
if self.device.mode.supports_operation_mode:
return OPERATION_MODES.get(self.device.mode.operation_mode.value)
return None
@property
def operation_list(self):
"""Return the list of available operation modes."""
return [operation_mode.value for
return [OPERATION_MODES.get(operation_mode.value) for
operation_mode in
self.device.get_supported_operation_modes()]
self.device.mode.operation_modes]
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
if self.device.supports_operation_mode:
if self.device.mode.supports_operation_mode:
from xknx.knx import HVACOperationMode
knx_operation_mode = HVACOperationMode(operation_mode)
await self.device.set_operation_mode(knx_operation_mode)
knx_operation_mode = HVACOperationMode(
OPERATION_MODES_INV.get(operation_mode))
await self.device.mode.set_operation_mode(knx_operation_mode)
await self.async_update_ha_state()
@property
def is_on(self):
"""Return true if the device is on."""
if self.device.supports_on_off:
return self.device.is_on
return None
async def async_turn_on(self):
"""Turn on."""
await self.device.turn_on()
async def async_turn_off(self):
"""Turn off."""
await self.device.turn_off()

View File

@ -19,7 +19,7 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['millheater==0.2.9']
REQUIREMENTS = ['millheater==0.3.3']
_LOGGER = logging.getLogger(__name__)

View File

@ -18,12 +18,13 @@ from homeassistant.components.climate import (
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_ON,
STATE_OFF)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability, MqttDiscoveryUpdate,
subscription)
MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -78,6 +79,8 @@ CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
CONF_TEMP_STEP = 'temp_step'
CONF_UNIQUE_ID = 'unique_id'
TEMPLATE_KEYS = (
CONF_POWER_STATE_TEMPLATE,
CONF_MODE_STATE_TEMPLATE,
@ -139,8 +142,9 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float)
vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float),
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@ -174,12 +178,14 @@ async def _async_setup_entity(hass, config, async_add_entities,
)])
class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
ClimateDevice):
"""Representation of an MQTT climate device."""
def __init__(self, hass, config, discovery_hash):
"""Initialize the climate device."""
self._config = config
self._unique_id = config.get(CONF_UNIQUE_ID)
self._sub_state = None
self.hass = hass
@ -201,11 +207,13 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)
async def async_added_to_hass(self):
"""Handle being added to home assistant."""
@ -453,7 +461,8 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
await subscription.async_unsubscribe_topics(self.hass, self._sub_state)
self._sub_state = await subscription.async_unsubscribe_topics(
self.hass, self._sub_state)
await MqttAvailability.async_will_remove_from_hass(self)
@property
@ -466,6 +475,11 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
"""Return the name of the climate device."""
return self._config.get(CONF_NAME)
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@property
def temperature_unit(self):
"""Return the unit of measurement."""

View File

@ -17,7 +17,7 @@ from homeassistant.const import (
CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['radiotherm==1.4.1']
REQUIREMENTS = ['radiotherm==2.0.0']
_LOGGER = logging.getLogger(__name__)
@ -235,13 +235,15 @@ class RadioThermostat(ClimateDevice):
self._name = self.device.name['raw']
# Request the current state from the thermostat.
data = self.device.tstat['raw']
import radiotherm
try:
data = self.device.tstat['raw']
except radiotherm.validate.RadiothermTstatError:
_LOGGER.error('%s (%s) was busy (invalid value returned)',
self._name, self.device.host)
return
current_temp = data['temp']
if current_temp == -1:
_LOGGER.error('%s (%s) was busy (temp == -1)', self._name,
self.device.host)
return
# Map thermostat values into various STATE_ flags.
self._current_temperature = current_temp

View File

@ -99,6 +99,8 @@ async def async_setup(hass, config):
kwargs[CONF_GOOGLE_ACTIONS] = GACTIONS_SCHEMA({})
kwargs[CONF_ALEXA] = alexa_sh.Config(
endpoint=None,
async_get_access_token=None,
should_expose=alexa_conf[CONF_FILTER],
entity_config=alexa_conf.get(CONF_ENTITY_CONFIG),
)

View File

@ -39,7 +39,7 @@ async def async_setup(hass):
return True
@websocket_api.require_owner
@websocket_api.require_admin
@websocket_api.async_response
async def websocket_list(hass, connection, msg):
"""Return a list of users."""
@ -49,7 +49,7 @@ async def websocket_list(hass, connection, msg):
websocket_api.result_message(msg['id'], result))
@websocket_api.require_owner
@websocket_api.require_admin
@websocket_api.async_response
async def websocket_delete(hass, connection, msg):
"""Delete a user."""
@ -72,7 +72,7 @@ async def websocket_delete(hass, connection, msg):
websocket_api.result_message(msg['id']))
@websocket_api.require_owner
@websocket_api.require_admin
@websocket_api.async_response
async def websocket_create(hass, connection, msg):
"""Create a user."""

View File

@ -3,7 +3,6 @@ import voluptuous as vol
from homeassistant.auth.providers import homeassistant as auth_ha
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api.decorators import require_owner
WS_TYPE_CREATE = 'config/auth_provider/homeassistant/create'
@ -54,7 +53,7 @@ def _get_provider(hass):
raise RuntimeError('Provider not found')
@require_owner
@websocket_api.require_admin
@websocket_api.async_response
async def websocket_create(hass, connection, msg):
"""Create credentials and attach to a user."""
@ -91,7 +90,7 @@ async def websocket_create(hass, connection, msg):
connection.send_message(websocket_api.result_message(msg['id']))
@require_owner
@websocket_api.require_admin
@websocket_api.async_response
async def websocket_delete(hass, connection, msg):
"""Delete username and related credential."""
@ -123,6 +122,7 @@ async def websocket_delete(hass, connection, msg):
websocket_api.result_message(msg['id']))
@websocket_api.require_admin
@websocket_api.async_response
async def websocket_change_password(hass, connection, msg):
"""Change user password."""

View File

@ -1,7 +1,9 @@
"""Http views to control the config manager."""
from homeassistant import config_entries, data_entry_flow
from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES
from homeassistant.components.http import HomeAssistantView
from homeassistant.exceptions import Unauthorized
from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView)
@ -63,6 +65,9 @@ class ConfigManagerEntryResourceView(HomeAssistantView):
async def delete(self, request, entry_id):
"""Delete a config entry."""
if not request['hass_user'].is_admin:
raise Unauthorized(config_entry_id=entry_id, permission='remove')
hass = request.app['hass']
try:
@ -85,12 +90,26 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
Example of a non-user initiated flow is a discovered Hue hub that
requires user interaction to finish setup.
"""
if not request['hass_user'].is_admin:
raise Unauthorized(
perm_category=CAT_CONFIG_ENTRIES, permission='add')
hass = request.app['hass']
return self.json([
flw for flw in hass.config_entries.flow.async_progress()
if flw['context']['source'] != config_entries.SOURCE_USER])
# pylint: disable=arguments-differ
async def post(self, request):
"""Handle a POST request."""
if not request['hass_user'].is_admin:
raise Unauthorized(
perm_category=CAT_CONFIG_ENTRIES, permission='add')
# pylint: disable=no-value-for-parameter
return await super().post(request)
class ConfigManagerFlowResourceView(FlowManagerResourceView):
"""View to interact with the flow manager."""
@ -98,6 +117,24 @@ class ConfigManagerFlowResourceView(FlowManagerResourceView):
url = '/api/config/config_entries/flow/{flow_id}'
name = 'api:config:config_entries:flow:resource'
async def get(self, request, flow_id):
"""Get the current state of a data_entry_flow."""
if not request['hass_user'].is_admin:
raise Unauthorized(
perm_category=CAT_CONFIG_ENTRIES, permission='add')
return await super().get(request, flow_id)
# pylint: disable=arguments-differ
async def post(self, request, flow_id):
"""Handle a POST request."""
if not request['hass_user'].is_admin:
raise Unauthorized(
perm_category=CAT_CONFIG_ENTRIES, permission='add')
# pylint: disable=no-value-for-parameter
return await super().post(request, flow_id)
class ConfigManagerAvailableFlowView(HomeAssistantView):
"""View to query available flows."""

View File

@ -33,7 +33,7 @@ SERVICE_INCREMENT = 'increment'
SERVICE_RESET = 'reset'
SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
CONFIG_SCHEMA = vol.Schema({

View File

@ -60,7 +60,7 @@ INTENT_OPEN_COVER = 'HassOpenCover'
INTENT_CLOSE_COVER = 'HassCloseCover'
COVER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
COVER_SET_COVER_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({

View File

@ -0,0 +1,89 @@
"""Support for ESPHome covers."""
import logging
from typing import TYPE_CHECKING, Optional
from homeassistant.components.cover import CoverDevice, SUPPORT_CLOSE, \
SUPPORT_OPEN, SUPPORT_STOP
from homeassistant.components.esphome import EsphomeEntity, \
platform_async_setup_entry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_CLOSED, STATE_OPEN
from homeassistant.helpers.typing import HomeAssistantType
if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import CoverInfo, CoverState # noqa
DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistantType,
entry: ConfigEntry, async_add_entities) -> None:
"""Set up ESPHome covers based on a config entry."""
# pylint: disable=redefined-outer-name
from aioesphomeapi import CoverInfo, CoverState # noqa
await platform_async_setup_entry(
hass, entry, async_add_entities,
component_key='cover',
info_type=CoverInfo, entity_type=EsphomeCover,
state_type=CoverState
)
COVER_STATE_INT_TO_STR = {
0: STATE_OPEN,
1: STATE_CLOSED
}
class EsphomeCover(EsphomeEntity, CoverDevice):
"""A cover implementation for ESPHome."""
@property
def _static_info(self) -> 'CoverInfo':
return super()._static_info
@property
def _state(self) -> Optional['CoverState']:
return super()._state
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
@property
def assumed_state(self) -> bool:
"""Return true if we do optimistic updates."""
return self._static_info.is_optimistic
@property
def is_closed(self) -> Optional[bool]:
"""Return if the cover is closed or not."""
if self._state is None:
return None
return COVER_STATE_INT_TO_STR[self._state.state]
async def async_open_cover(self, **kwargs) -> None:
"""Open the cover."""
from aioesphomeapi.client import COVER_COMMAND_OPEN
await self._client.cover_command(key=self._static_info.key,
command=COVER_COMMAND_OPEN)
async def async_close_cover(self, **kwargs) -> None:
"""Close cover."""
from aioesphomeapi.client import COVER_COMMAND_CLOSE
await self._client.cover_command(key=self._static_info.key,
command=COVER_COMMAND_CLOSE)
async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
from aioesphomeapi.client import COVER_COMMAND_STOP
await self._client.cover_command(key=self._static_info.key,
command=COVER_COMMAND_STOP)

View File

@ -287,7 +287,8 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
await subscription.async_unsubscribe_topics(self.hass, self._sub_state)
self._sub_state = await subscription.async_unsubscribe_topics(
self.hass, self._sub_state)
await MqttAvailability.async_will_remove_from_hass(self)
@property

View File

@ -15,8 +15,8 @@ from homeassistant.components.rflink import (
from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.const import CONF_NAME, STATE_OPEN
DEPENDENCIES = ['rflink']
@ -60,9 +60,17 @@ async def async_setup_platform(hass, config, async_add_entities,
async_add_entities(devices_from_config(config))
class RflinkCover(RflinkCommand, CoverDevice):
class RflinkCover(RflinkCommand, CoverDevice, RestoreEntity):
"""Rflink entity which can switch on/stop/off (eg: cover)."""
async def async_added_to_hass(self):
"""Restore RFLink cover state (OPEN/CLOSE)."""
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
if old_state is not None:
self._state = old_state.state == STATE_OPEN
def _handle_event(self, event):
"""Adjust state if Rflink picks up a remote command for this device."""
self.cancel_queued_send_commands()

View File

@ -8,20 +8,36 @@ https://home-assistant.io/components/cover.tellduslive/
"""
import logging
from homeassistant.components import tellduslive
from homeassistant.components import cover, tellduslive
from homeassistant.components.cover import CoverDevice
from homeassistant.components.tellduslive.entry import TelldusLiveEntity
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Telldus Live covers."""
if discovery_info is None:
return
"""Old way of setting up TelldusLive.
client = hass.data[tellduslive.DOMAIN]
add_entities(TelldusLiveCover(client, cover) for cover in discovery_info)
Can only be called when a user accidentally mentions the platform in their
config. But even in that case it would have been ignored.
"""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up tellduslive sensors dynamically."""
async def async_discover_cover(device_id):
"""Discover and add a discovered sensor."""
client = hass.data[tellduslive.DOMAIN]
async_add_entities([TelldusLiveCover(client, device_id)])
async_dispatcher_connect(
hass,
tellduslive.TELLDUS_DISCOVERY_NEW.format(cover.DOMAIN,
tellduslive.DOMAIN),
async_discover_cover,
)
class TelldusLiveCover(TelldusLiveEntity, CoverDevice):

View File

@ -18,9 +18,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
model = device['model']
if model == 'curtain':
devices.append(XiaomiGenericCover(device, "Curtain",
{'status': 'status',
'pos': 'curtain_level'},
gateway))
'status', gateway))
add_entities(devices)
@ -45,20 +43,20 @@ class XiaomiGenericCover(XiaomiDevice, CoverDevice):
def close_cover(self, **kwargs):
"""Close the cover."""
self._write_to_hub(self._sid, **{self._data_key['status']: 'close'})
self._write_to_hub(self._sid, **{self._data_key: 'close'})
def open_cover(self, **kwargs):
"""Open the cover."""
self._write_to_hub(self._sid, **{self._data_key['status']: 'open'})
self._write_to_hub(self._sid, **{self._data_key: 'open'})
def stop_cover(self, **kwargs):
"""Stop the cover."""
self._write_to_hub(self._sid, **{self._data_key['status']: 'stop'})
self._write_to_hub(self._sid, **{self._data_key: 'stop'})
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)})
self._write_to_hub(self._sid, **{ATTR_CURTAIN_LEVEL: str(position)})
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""

View File

@ -1,139 +0,0 @@
"""
Platform for the Daikin AC.
For more details about this component, please refer to the documentation
https://home-assistant.io/components/daikin/
"""
import logging
from datetime import timedelta
from socket import timeout
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.discovery import SERVICE_DAIKIN
from homeassistant.const import (
CONF_HOSTS, CONF_ICON, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_TYPE
)
from homeassistant.helpers import discovery
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
REQUIREMENTS = ['pydaikin==0.8']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'daikin'
ATTR_TARGET_TEMPERATURE = 'target_temperature'
ATTR_INSIDE_TEMPERATURE = 'inside_temperature'
ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
COMPONENT_TYPES = ['climate', 'sensor']
SENSOR_TYPE_TEMPERATURE = 'temperature'
SENSOR_TYPES = {
ATTR_INSIDE_TEMPERATURE: {
CONF_NAME: 'Inside Temperature',
CONF_ICON: 'mdi:thermometer',
CONF_TYPE: SENSOR_TYPE_TEMPERATURE
},
ATTR_OUTSIDE_TEMPERATURE: {
CONF_NAME: 'Outside Temperature',
CONF_ICON: 'mdi:thermometer',
CONF_TYPE: SENSOR_TYPE_TEMPERATURE
}
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(
CONF_HOSTS, default=[]
): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(
CONF_MONITORED_CONDITIONS,
default=list(SENSOR_TYPES.keys())
): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)])
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Establish connection with Daikin."""
def discovery_dispatch(service, discovery_info):
"""Dispatcher for Daikin discovery events."""
host = discovery_info.get('ip')
if daikin_api_setup(hass, host) is None:
return
for component in COMPONENT_TYPES:
load_platform(hass, component, DOMAIN, discovery_info,
config)
discovery.listen(hass, SERVICE_DAIKIN, discovery_dispatch)
for host in config.get(DOMAIN, {}).get(CONF_HOSTS, []):
if daikin_api_setup(hass, host) is None:
continue
discovery_info = {
'ip': host,
CONF_MONITORED_CONDITIONS:
config[DOMAIN][CONF_MONITORED_CONDITIONS]
}
load_platform(hass, 'sensor', DOMAIN, discovery_info, config)
return True
def daikin_api_setup(hass, host, name=None):
"""Create a Daikin instance only once."""
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
api = hass.data[DOMAIN].get(host)
if api is None:
from pydaikin import appliance
try:
device = appliance.Appliance(host)
except timeout:
_LOGGER.error("Connection to Daikin could not be established")
return False
if name is None:
name = device.values['name']
api = DaikinApi(device, name)
return api
class DaikinApi:
"""Keep the Daikin instance in one place and centralize the update."""
def __init__(self, device, name):
"""Initialize the Daikin Handle."""
self.device = device
self.name = name
self.ip_address = device.ip
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, **kwargs):
"""Pull the latest data from Daikin."""
try:
self.device.update_status()
except timeout:
_LOGGER.warning(
"Connection failed for %s", self.ip_address
)
@property
def mac(self):
"""Return mac-address of device."""
return self.device.values.get('mac')

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "El dispositiu ja est\u00e0 configurat",
"device_fail": "S'ha produ\u00eft un error inesperat al crear el dispositiu.",
"device_timeout": "S'ha acabat el temps d'espera en la connexi\u00f3 amb el dispositiu."
},
"step": {
"user": {
"data": {
"host": "Amfitri\u00f3"
},
"description": "Introdueix l'adre\u00e7a IP del teu Daikin AC.",
"title": "Configuraci\u00f3 de Daikin AC"
}
},
"title": "Daikin AC"
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured",
"device_fail": "Unexpected error creating device.",
"device_timeout": "Timeout connecting to the device."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"description": "Enter IP address of your Daikin AC.",
"title": "Configure Daikin AC"
}
},
"title": "Daikin AC"
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"device_fail": "\uc7a5\uce58\ub97c \uad6c\uc131\ud558\ub294\uc911 \uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
"device_timeout": "\uc7a5\uce58 \uc5f0\uacb0 \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4."
},
"step": {
"user": {
"data": {
"host": "\ud638\uc2a4\ud2b8"
},
"description": "Daikin AC \uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.",
"title": "Daikin AC \uad6c\uc131"
}
},
"title": "Daikin AC"
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Apparat ass scho konfigur\u00e9iert",
"device_fail": "Onerwaarte Feeler beim erstelle vum Apparat.",
"device_timeout": "Z\u00e4it Iwwerschreidung beim verbannen mam Apparat."
},
"step": {
"user": {
"data": {
"host": "Apparat"
},
"description": "Gitt d'IP Adresse vum Daikin AC an:",
"title": "Daikin AC konfigur\u00e9ieren"
}
},
"title": "Daikin AC"
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
"device_fail": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.",
"device_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443."
},
"step": {
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442"
},
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0432\u0430\u0448\u0435\u0433\u043e Daikin AC.",
"title": "Daikin AC"
}
},
"title": "Daikin AC"
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Naprava je \u017ee konfigurirana",
"device_fail": "Nepri\u010dakovana napaka pri ustvarjanju naprave.",
"device_timeout": "\u010casovna omejitev za priklop na napravo je potekla."
},
"step": {
"user": {
"data": {
"host": "Gostitelj"
},
"description": "Vnesite naslov IP va\u0161e Daikin klime.",
"title": "Nastavite Daikin klimo"
}
},
"title": "Daikin AC"
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"device_fail": "\u5275\u5efa\u88dd\u7f6e\u6642\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002",
"device_timeout": "\u9023\u7dda\u81f3\u88dd\u7f6e\u903e\u6642\u3002"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u6a5f\u7aef"
},
"description": "\u8f38\u5165\u60a8\u7684\u5927\u91d1\u7a7a\u8abf IP \u4f4d\u5740\u3002",
"title": "\u8a2d\u5b9a\u5927\u91d1\u7a7a\u8abf"
}
},
"title": "\u5927\u91d1\u7a7a\u8abf\uff08Daikin AC\uff09"
}
}

View File

@ -0,0 +1,146 @@
"""
Platform for the Daikin AC.
For more details about this component, please refer to the documentation
https://home-assistant.io/components/daikin/
"""
import asyncio
from datetime import timedelta
import logging
from socket import timeout
import async_timeout
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_HOSTS
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util import Throttle
from . import config_flow # noqa pylint_disable=unused-import
from .const import KEY_HOST
REQUIREMENTS = ['pydaikin==0.9']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'daikin'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
COMPONENT_TYPES = ['climate', 'sensor']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(
CONF_HOSTS, default=[]
): vol.All(cv.ensure_list, [cv.string]),
})
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass, config):
"""Establish connection with Daikin."""
if DOMAIN not in config:
return True
hosts = config[DOMAIN].get(CONF_HOSTS)
if not hosts:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={'source': SOURCE_IMPORT}))
for host in hosts:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={'source': SOURCE_IMPORT},
data={
KEY_HOST: host,
}))
return True
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Establish connection with Daikin."""
conf = entry.data
daikin_api = await daikin_api_setup(hass, conf[KEY_HOST])
if not daikin_api:
return False
hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api})
await asyncio.wait([
hass.config_entries.async_forward_entry_setup(entry, component)
for component in COMPONENT_TYPES
])
return True
async def async_unload_entry(hass, config_entry):
"""Unload a config entry."""
await asyncio.wait([
hass.config_entries.async_forward_entry_unload(config_entry, component)
for component in COMPONENT_TYPES
])
hass.data[DOMAIN].pop(config_entry.entry_id)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
return True
async def daikin_api_setup(hass, host):
"""Create a Daikin instance only once."""
from pydaikin.appliance import Appliance
try:
with async_timeout.timeout(10):
device = await hass.async_add_executor_job(Appliance, host)
except asyncio.TimeoutError:
_LOGGER.error("Connection to Daikin could not be established")
return None
except Exception: # pylint: disable=broad-except
_LOGGER.error("Unexpected error creating device")
return None
name = device.values['name']
api = DaikinApi(device, name)
return api
class DaikinApi:
"""Keep the Daikin instance in one place and centralize the update."""
def __init__(self, device, name):
"""Initialize the Daikin Handle."""
self.device = device
self.name = name
self.ip_address = device.ip
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, **kwargs):
"""Pull the latest data from Daikin."""
try:
self.device.update_status()
except timeout:
_LOGGER.warning(
"Connection failed for %s", self.ip_address
)
@property
def mac(self):
"""Return mac-address of device."""
return self.device.values.get(CONNECTION_NETWORK_MAC)
@property
def device_info(self):
"""Return a device description for device registry."""
info = self.device.values
return {
'connections': {(CONNECTION_NETWORK_MAC, self.mac)},
'identifieres': self.mac,
'manufacturer': 'Daikin',
'model': info.get('model'),
'name': info.get('name'),
'sw_version': info.get('ver').replace('_', '.'),
}

View File

@ -0,0 +1,74 @@
"""Config flow for the Daikin platform."""
import asyncio
import logging
import async_timeout
import voluptuous as vol
from homeassistant import config_entries
from .const import KEY_HOST, KEY_IP, KEY_MAC
_LOGGER = logging.getLogger(__name__)
@config_entries.HANDLERS.register('daikin')
class FlowHandler(config_entries.ConfigFlow):
"""Handle a config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
async def _create_entry(self, host, mac):
"""Register new entry."""
# Check if mac already is registered
for entry in self._async_current_entries():
if entry.data[KEY_MAC] == mac:
return self.async_abort(reason='already_configured')
return self.async_create_entry(
title=host,
data={
KEY_HOST: host,
KEY_MAC: mac
})
async def _create_device(self, host):
"""Create device."""
from pydaikin.appliance import Appliance
try:
with async_timeout.timeout(10):
device = await self.hass.async_add_executor_job(
Appliance, host)
except asyncio.TimeoutError:
return self.async_abort(reason='device_timeout')
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected error creating device")
return self.async_abort(reason='device_fail')
mac = device.values.get('mac')
return await self._create_entry(host, mac)
async def async_step_user(self, user_input=None):
"""User initiated config flow."""
if user_input is None:
return self.async_show_form(
step_id='user',
data_schema=vol.Schema({
vol.Required(KEY_HOST): str
})
)
return await self._create_device(user_input[KEY_HOST])
async def async_step_import(self, user_input):
"""Import a config entry."""
host = user_input.get(KEY_HOST)
if not host:
return await self.async_step_user()
return await self._create_device(host)
async def async_step_discovery(self, user_input):
"""Initialize step from discovery."""
_LOGGER.info("Discovered device: %s", user_input)
return await self._create_entry(user_input[KEY_IP],
user_input[KEY_MAC])

View File

@ -0,0 +1,25 @@
"""Constants for Daikin."""
from homeassistant.const import CONF_ICON, CONF_NAME, CONF_TYPE
ATTR_TARGET_TEMPERATURE = 'target_temperature'
ATTR_INSIDE_TEMPERATURE = 'inside_temperature'
ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature'
SENSOR_TYPE_TEMPERATURE = 'temperature'
SENSOR_TYPES = {
ATTR_INSIDE_TEMPERATURE: {
CONF_NAME: 'Inside Temperature',
CONF_ICON: 'mdi:thermometer',
CONF_TYPE: SENSOR_TYPE_TEMPERATURE
},
ATTR_OUTSIDE_TEMPERATURE: {
CONF_NAME: 'Outside Temperature',
CONF_ICON: 'mdi:thermometer',
CONF_TYPE: SENSOR_TYPE_TEMPERATURE
}
}
KEY_HOST = 'host'
KEY_MAC = 'mac'
KEY_IP = 'ip'

View File

@ -0,0 +1,19 @@
{
"config": {
"title": "Daikin AC",
"step": {
"user": {
"title": "Configure Daikin AC",
"description": "Enter IP address of your Daikin AC.",
"data": {
"host": "Host"
}
}
},
"abort": {
"device_timeout": "Timeout connecting to the device.",
"device_fail": "Unexpected error creating device.",
"already_configured": "Device is already configured"
}
}
}

View File

@ -14,7 +14,7 @@
"host": "Amfitri\u00f3",
"port": "Port"
},
"title": "Definiu la passarel\u00b7la deCONZ"
"title": "Definici\u00f3 de la passarel\u00b7la deCONZ"
},
"link": {
"description": "Desbloqueja la teva passarel\u00b7la d'enlla\u00e7 deCONZ per a registrar-te amb Home Assistant.\n\n1. V\u00e9s a la configuraci\u00f3 del sistema deCONZ\n2. Prem el bot\u00f3 \"Desbloquejar passarel\u00b7la\"",
@ -23,7 +23,7 @@
"options": {
"data": {
"allow_clip_sensor": "Permet la importaci\u00f3 de sensors virtuals",
"allow_deconz_groups": "Permet la importaci\u00f3 de grups deCONZ"
"allow_deconz_groups": "Permetre la importaci\u00f3 de grups deCONZ"
},
"title": "Opcions de configuraci\u00f3 addicionals per deCONZ"
}

View File

@ -22,8 +22,10 @@
},
"options": {
"data": {
"allow_clip_sensor": "Virtu\u00e1lis szenzorok import\u00e1l\u00e1s\u00e1nak enged\u00e9lyez\u00e9se"
}
"allow_clip_sensor": "Virtu\u00e1lis szenzorok import\u00e1l\u00e1s\u00e1nak enged\u00e9lyez\u00e9se",
"allow_deconz_groups": "deCONZ csoportok import\u00e1l\u00e1s\u00e1nak enged\u00e9lyez\u00e9se"
},
"title": "Extra be\u00e1ll\u00edt\u00e1si lehet\u0151s\u00e9gek a deCONZhoz"
}
},
"title": "deCONZ Zigbee gateway"

View File

@ -28,6 +28,6 @@
"title": "Extra Konfiguratiouns Optiounen fir deCONZ"
}
},
"title": "deCONZ"
"title": "deCONZ Zigbee gateway"
}
}

View File

@ -15,6 +15,7 @@ DEPENDENCIES = ['conversation', 'introduction', 'zone']
DOMAIN = 'demo'
COMPONENTS_WITH_DEMO_PLATFORM = [
'air_quality',
'alarm_control_panel',
'binary_sensor',
'calendar',

View File

@ -24,9 +24,15 @@ BT_PREFIX = 'BT_'
CONF_REQUEST_RSSI = 'request_rssi'
CONF_DEVICE_ID = "device_id"
DEFAULT_DEVICE_ID = -1
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TRACK_NEW): cv.boolean,
vol.Optional(CONF_REQUEST_RSSI): cv.boolean
vol.Optional(CONF_REQUEST_RSSI): cv.boolean,
vol.Optional(CONF_DEVICE_ID, default=DEFAULT_DEVICE_ID):
vol.All(vol.Coerce(int), vol.Range(min=-1))
})
@ -44,11 +50,13 @@ def setup_scanner(hass, config, see, discovery_info=None):
see(mac="{}{}".format(BT_PREFIX, mac), host_name=name,
attributes=attributes, source_type=SOURCE_TYPE_BLUETOOTH)
device_id = config.get(CONF_DEVICE_ID)
def discover_devices():
"""Discover Bluetooth devices."""
result = bluetooth.discover_devices(
duration=8, lookup_names=True, flush_cache=True,
lookup_class=False)
lookup_class=False, device_id=device_id)
_LOGGER.debug("Bluetooth devices discovered = %d", len(result))
return result

View File

@ -1,56 +1,25 @@
"""
Support for device tracking through Freebox routers.
Support for Freebox devices (Freebox v6 and Freebox mini 4K).
This tracker keeps track of the devices connected to the configured Freebox.
For more details about this platform, please refer to the documentation at
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_tracker.freebox/
"""
import asyncio
import copy
import logging
import socket
from collections import namedtuple
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.device_tracker import DeviceScanner
from homeassistant.components.freebox import DATA_FREEBOX
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
from homeassistant.const import (
CONF_HOST, CONF_PORT)
REQUIREMENTS = ['aiofreepybox==0.0.5']
DEPENDENCIES = ['freebox']
_LOGGER = logging.getLogger(__name__)
FREEBOX_CONFIG_FILE = 'freebox.conf'
PLATFORM_SCHEMA = vol.All(
PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): cv.port
}))
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up the Freebox device tracker and start the polling."""
freebox_config = copy.deepcopy(config)
if discovery_info is not None:
freebox_config[CONF_HOST] = discovery_info['properties']['api_domain']
freebox_config[CONF_PORT] = discovery_info['properties']['https_port']
_LOGGER.info("Discovered Freebox server: %s:%s",
freebox_config[CONF_HOST], freebox_config[CONF_PORT])
scanner = FreeboxDeviceScanner(hass, freebox_config, async_see)
interval = freebox_config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
await scanner.async_start(hass, interval)
return True
async def async_get_scanner(hass, config):
"""Validate the configuration and return a Freebox scanner."""
scanner = FreeboxDeviceScanner(hass.data[DATA_FREEBOX])
await scanner.async_connect()
return scanner if scanner.success_init else None
Device = namedtuple('Device', ['id', 'name', 'ip'])
@ -62,59 +31,41 @@ def _build_device(device_dict):
device_dict['l3connectivities'][0]['addr'])
class FreeboxDeviceScanner:
"""This class scans for devices connected to the Freebox."""
class FreeboxDeviceScanner(DeviceScanner):
"""Queries the Freebox device."""
def __init__(self, hass, config, async_see):
def __init__(self, fbx):
"""Initialize the scanner."""
from aiofreepybox import Freepybox
self.last_results = {}
self.success_init = False
self.connection = fbx
self.host = config[CONF_HOST]
self.port = config[CONF_PORT]
self.token_file = hass.config.path(FREEBOX_CONFIG_FILE)
self.async_see = async_see
async def async_connect(self):
"""Initialize connection to the router."""
# Test the router is accessible.
data = await self.connection.lan.get_hosts_list()
self.success_init = data is not None
# Hardcode the app description to avoid invalidating the authentication
# file at each new version.
# The version can be changed if we want the user to re-authorize HASS
# on her Freebox.
app_desc = {
'app_id': 'hass',
'app_name': 'Home Assistant',
'app_version': '0.65',
'device_name': socket.gethostname()
}
api_version = 'v1' # Use the lowest working version.
self.fbx = Freepybox(
app_desc=app_desc,
token_file=self.token_file,
api_version=api_version)
async def async_start(self, hass, interval):
"""Perform a first update and start polling at the given interval."""
async def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
await self.async_update_info()
interval = max(interval, MIN_TIME_BETWEEN_SCANS)
async_track_time_interval(hass, self.async_update_info, interval)
return [device.id for device in self.last_results]
async def async_update_info(self, now=None):
"""Check the Freebox for devices."""
from aiofreepybox.exceptions import HttpRequestError
async def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
name = next((
result.name for result in self.last_results
if result.id == device), None)
return name
_LOGGER.info('Scanning devices')
async def async_update_info(self):
"""Ensure the information from the Freebox router is up to date."""
_LOGGER.debug('Checking Devices')
await self.fbx.open(self.host, self.port)
try:
hosts = await self.fbx.lan.get_hosts_list()
except HttpRequestError:
_LOGGER.exception('Failed to scan devices')
else:
active_devices = [_build_device(device)
for device in hosts
if device['active']]
hosts = await self.connection.lan.get_hosts_list()
if active_devices:
await asyncio.wait([self.async_see(mac=d.id, host_name=d.name)
for d in active_devices])
last_results = [_build_device(device)
for device in hosts
if device['active']]
await self.fbx.close()
self.last_results = last_results

View File

@ -23,10 +23,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_URL): cv.url,
})
HOSTS_PATH = "wlan_host_list.Hosts"
def get_scanner(hass, config):
"""Get a Huawei LTE router scanner."""
data = hass.data[DATA_KEY].get_data(config)
data.subscribe(HOSTS_PATH)
return HuaweiLteScanner(data)
@ -43,7 +46,7 @@ class HuaweiLteScanner(DeviceScanner):
self.data.update()
self._hosts = {
x["MacAddress"]: x
for x in self.data["wlan_host_list.Hosts.Host"]
for x in self.data[HOSTS_PATH + ".Host"]
if x.get("MacAddress")
}
return list(self._hosts)

View File

@ -200,7 +200,9 @@ class Icloud(DeviceScanner):
self._intervals = {}
for device in self.api.devices:
status = device.status(DEVICESTATUSSET)
_LOGGER.debug('Device Status is %s', status)
devicename = slugify(status['name'].replace(' ', '', 99))
_LOGGER.info('Adding icloud device: %s', devicename)
if devicename in self.devices:
_LOGGER.error('Multiple devices with name: %s', devicename)
continue
@ -404,6 +406,7 @@ class Icloud(DeviceScanner):
continue
status = device.status(DEVICESTATUSSET)
_LOGGER.debug('Device Status is %s', status)
dev_id = status['name'].replace(' ', '', 99)
dev_id = slugify(dev_id)
attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get(
@ -441,9 +444,9 @@ class Icloud(DeviceScanner):
return
self.api.authenticate()
for device in self.api.devices:
if devicename is None or device == self.devices[devicename]:
if str(device) == str(self.devices[devicename]):
_LOGGER.info("Playing Lost iPhone sound for %s", devicename)
device.play_sound()
def update_icloud(self, devicename=None):

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_PASSWORD, CONF_USERNAME
)
REQUIREMENTS = ['ndms2_client==0.0.5']
REQUIREMENTS = ['ndms2_client==0.0.6']
_LOGGER = logging.getLogger(__name__)

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_SSL,
CONF_DEVICES, CONF_EXCLUDE)
REQUIREMENTS = ['pynetgear==0.5.1']
REQUIREMENTS = ['pynetgear==0.5.2']
_LOGGER = logging.getLogger(__name__)

View File

@ -12,20 +12,22 @@ import voluptuous as vol
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL,
CONF_PASSWORD, CONF_USERNAME)
CONF_PASSWORD, CONF_USERNAME, ATTR_BATTERY_LEVEL)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import slugify
REQUIREMENTS = ['pytraccar==0.1.2']
REQUIREMENTS = ['pytraccar==0.2.1']
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
ATTR_CATEGORY = 'category'
ATTR_GEOFENCE = 'geofence'
ATTR_MOTION = 'motion'
ATTR_SPEED = 'speed'
ATTR_TRACKER = 'tracker'
DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
@ -78,13 +80,21 @@ class TraccarScanner:
await self._api.get_device_info()
for devicename in self._api.device_info:
device = self._api.device_info[devicename]
device_attributes = {
ATTR_ADDRESS: device['address'],
ATTR_GEOFENCE: device['geofence'],
ATTR_CATEGORY: device['category'],
ATTR_TRACKER: 'traccar'
}
attr = {}
attr[ATTR_TRACKER] = 'traccar'
if device.get('address') is not None:
attr[ATTR_ADDRESS] = device['address']
if device.get('geofence') is not None:
attr[ATTR_GEOFENCE] = device['geofence']
if device.get('category') is not None:
attr[ATTR_CATEGORY] = device['category']
if device.get('speed') is not None:
attr[ATTR_SPEED] = device['speed']
if device.get('battery') is not None:
attr[ATTR_BATTERY_LEVEL] = device['battery']
if device.get('motion') is not None:
attr[ATTR_MOTION] = device['motion']
await self._async_see(
dev_id=slugify(device['device_id']),
gps=(device['latitude'], device['longitude']),
attributes=device_attributes)
gps=(device.get('latitude'), device.get('longitude')),
attributes=attr)

View File

@ -1,16 +1,16 @@
{
"config": {
"abort": {
"not_internet_accessible": "La vostra inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Dialogflow.",
"not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Dialogflow.",
"one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia."
},
"create_entry": {
"default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [integraci\u00f3 webhook de Dialogflow]({dialogflow_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/json\n\nVegeu [la documentaci\u00f3]({docs_url}) per a m\u00e9s detalls."
"default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar la [integraci\u00f3 webhook de Dialogflow]({dialogflow_url}). \n\n Completa la seg\u00fcent informaci\u00f3: \n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n- Tipus de contingut: application/json\n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls."
},
"step": {
"user": {
"description": "Esteu segur que voleu configurar Dialogflow?",
"title": "Configureu el Webhook de Dialogflow"
"description": "Est\u00e0s segur que vols configurar Dialogflow?",
"title": "Configuraci\u00f3 del Webhook de Dialogflow"
}
},
"title": "Dialogflow"

View File

@ -1,7 +1,12 @@
{
"config": {
"abort": {
"not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a Dialogflow \u00fczenetek fogad\u00e1s\u00e1hoz.",
"one_instance_allowed": "Csak egyetlen konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges."
},
"step": {
"user": {
"description": "Biztosan be szeretn\u00e9d \u00e1ll\u00edtani az Dialogflowt?",
"title": "Dialogflow Webhook be\u00e1ll\u00edt\u00e1sa"
}
},

View File

@ -21,7 +21,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==2.2.0']
REQUIREMENTS = ['netdisco==2.3.0']
DOMAIN = 'discovery'
@ -44,13 +44,20 @@ SERVICE_SABNZBD = 'sabnzbd'
SERVICE_SAMSUNG_PRINTER = 'samsung_printer'
SERVICE_HOMEKIT = 'homekit'
SERVICE_OCTOPRINT = 'octoprint'
SERVICE_FREEBOX = 'freebox'
SERVICE_IGD = 'igd'
SERVICE_DLNA_DMR = 'dlna_dmr'
CONFIG_ENTRY_HANDLERS = {
SERVICE_DAIKIN: 'daikin',
SERVICE_DECONZ: 'deconz',
'esphome': 'esphome',
'google_cast': 'cast',
SERVICE_HUE: 'hue',
SERVICE_TELLDUSLIVE: 'tellduslive',
SERVICE_IKEA_TRADFRI: 'tradfri',
'sonos': 'sonos',
SERVICE_IGD: 'upnp',
}
SERVICE_HANDLERS = {
@ -62,12 +69,11 @@ SERVICE_HANDLERS = {
SERVICE_APPLE_TV: ('apple_tv', None),
SERVICE_WINK: ('wink', None),
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
SERVICE_TELLDUSLIVE: ('tellduslive', None),
SERVICE_DAIKIN: ('daikin', None),
SERVICE_SABNZBD: ('sabnzbd', None),
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
SERVICE_KONNECTED: ('konnected', None),
SERVICE_OCTOPRINT: ('octoprint', None),
SERVICE_FREEBOX: ('freebox', None),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
'roku': ('media_player', 'roku'),
@ -87,12 +93,11 @@ SERVICE_HANDLERS = {
'volumio': ('media_player', 'volumio'),
'lg_smart_device': ('media_player', 'lg_soundbar'),
'nanoleaf_aurora': ('light', 'nanoleaf_aurora'),
'freebox': ('device_tracker', 'freebox'),
}
OPTIONAL_SERVICE_HANDLERS = {
SERVICE_HOMEKIT: ('homekit_controller', None),
'dlna_dmr': ('media_player', 'dlna_dmr'),
SERVICE_DLNA_DMR: ('media_player', 'dlna_dmr'),
}
CONF_IGNORE = 'ignore'
@ -134,7 +139,7 @@ async def async_setup(hass, config):
discovery_hash = json.dumps([service, info], sort_keys=True)
if discovery_hash in already_discovered:
logger.debug("Already discoverd service %s %s.", service, info)
logger.debug("Already discovered service %s %s.", service, info)
return
already_discovered.add(discovery_hash)
@ -169,20 +174,23 @@ async def async_setup(hass, config):
async def scan_devices(now):
"""Scan for devices."""
results = await hass.async_add_job(_discover, netdisco)
try:
results = await hass.async_add_job(_discover, netdisco)
for result in results:
hass.async_create_task(new_service_found(*result))
for result in results:
hass.async_create_task(new_service_found(*result))
except OSError:
logger.error("Network is unreachable")
async_track_point_in_utc_time(hass, scan_devices,
dt_util.utcnow() + SCAN_INTERVAL)
async_track_point_in_utc_time(
hass, scan_devices, dt_util.utcnow() + SCAN_INTERVAL)
@callback
def schedule_first(event):
"""Schedule the first discovery when Home Assistant starts up."""
async_track_point_in_utc_time(hass, scan_devices, dt_util.utcnow())
# discovery local services
# Discovery for local services
if 'HASSIO' in os.environ:
hass.async_create_task(new_service_found(SERVICE_HASSIO, {}))

View File

@ -104,7 +104,7 @@ def setup(hass, config):
return False
# Subscribe to doorbell or motion events
if events is not None:
if events:
doorstation.update_schedule(hass)
hass.data[DOMAIN] = doorstations

View File

@ -26,7 +26,7 @@ EDP_REDY = 'edp_redy'
DATA_UPDATE_TOPIC = '{0}_data_update'.format(DOMAIN)
UPDATE_INTERVAL = 60
REQUIREMENTS = ['edp_redy==0.0.2']
REQUIREMENTS = ['edp_redy==0.0.3']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({

View File

@ -21,7 +21,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
REQUIREMENTS = ['pyeight==0.0.9']
REQUIREMENTS = ['pyeight==0.1.0']
_LOGGER = logging.getLogger(__name__)

View File

@ -11,12 +11,12 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_TIMEOUT
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
REQUIREMENTS = ['pyenvisalink==3.7']
REQUIREMENTS = ['pyenvisalink==3.8']
_LOGGER = logging.getLogger(__name__)
@ -46,6 +46,7 @@ DEFAULT_KEEPALIVE = 60
DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening'
DEFAULT_PANIC = 'Police'
DEFAULT_TIMEOUT = 10
SIGNAL_ZONE_UPDATE = 'envisalink.zones_updated'
SIGNAL_PARTITION_UPDATE = 'envisalink.partition_updated'
@ -65,7 +66,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])),
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASS): cv.string,
vol.Required(CONF_CODE): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
@ -77,9 +78,21 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(
CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL): vol.Coerce(int),
vol.Optional(
CONF_TIMEOUT,
default=DEFAULT_TIMEOUT): vol.Coerce(int),
}),
}, extra=vol.ALLOW_EXTRA)
SERVICE_CUSTOM_FUNCTION = 'invoke_custom_function'
ATTR_CUSTOM_FUNCTION = 'pgm'
ATTR_PARTITION = 'partition'
SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_CUSTOM_FUNCTION): cv.string,
vol.Required(ATTR_PARTITION): cv.string,
})
async def async_setup(hass, config):
"""Set up for Envisalink devices."""
@ -99,11 +112,12 @@ async def async_setup(hass, config):
zone_dump = conf.get(CONF_ZONEDUMP_INTERVAL)
zones = conf.get(CONF_ZONES)
partitions = conf.get(CONF_PARTITIONS)
connection_timeout = conf.get(CONF_TIMEOUT)
sync_connect = asyncio.Future(loop=hass.loop)
controller = EnvisalinkAlarmPanel(
host, port, panel_type, version, user, password, zone_dump,
keep_alive, hass.loop)
keep_alive, hass.loop, connection_timeout)
hass.data[DATA_EVL] = controller
@callback
@ -153,6 +167,12 @@ async def async_setup(hass, config):
_LOGGER.info("Shutting down Envisalink")
controller.stop()
async def handle_custom_function(call):
"""Handle custom/PGM service."""
custom_function = call.data.get(ATTR_CUSTOM_FUNCTION)
partition = call.data.get(ATTR_PARTITION)
controller.command_output(code, partition, custom_function)
controller.callback_zone_timer_dump = zones_updated_callback
controller.callback_zone_state_change = zones_updated_callback
controller.callback_partition_state_change = partition_updated_callback
@ -190,6 +210,11 @@ async def async_setup(hass, config):
}, config
))
hass.services.async_register(DOMAIN,
SERVICE_CUSTOM_FUNCTION,
handle_custom_function,
schema=SERVICE_SCHEMA)
return True

View File

@ -0,0 +1,15 @@
# Describes the format for available Envisalink services.
invoke_custom_function:
description: >
Allows users with DSC panels to trigger a PGM output (1-4).
Note that you need to specify the alarm panel's "code" parameter for this to work.
fields:
partition:
description: >
The alarm panel partition to trigger the PGM output on.
Typically this is just "1".
example: "1"
pgm:
description: The PGM number to trigger on the alarm panel. This will be 1-4.
example: "2"

View File

@ -0,0 +1,30 @@
{
"config": {
"abort": {
"already_configured": "ESP ja est\u00e0 configurat"
},
"error": {
"connection_error": "No s'ha pogut connectar amb ESP. Verifica que l'arxiu YAML cont\u00e9 la l\u00ednia 'api:'.",
"invalid_password": "Contrasenya inv\u00e0lida!",
"resolve_error": "No s'ha pogut trobar l'adre\u00e7a de l'ESP. Si l'error persisteix, configura una adre\u00e7a IP est\u00e0tica: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
},
"step": {
"authenticate": {
"data": {
"password": "Contrasenya"
},
"description": "Introdueix la contrasenya que has posat en la teva configuraci\u00f3.",
"title": "Introdueix la contrasenya"
},
"user": {
"data": {
"host": "Amfitri\u00f3",
"port": "Port"
},
"description": "Introdueix la informaci\u00f3 de connexi\u00f3 del teu node [ESPHome](https://esphomelib.com/).",
"title": "ESPHome"
}
},
"title": "ESPHome"
}
}

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