Merge remote-tracking branch 'upstream/dev' into http-zeroconf

This commit is contained in:
Robbie Trencheny 2016-04-10 18:04:56 -07:00
commit f30b406334
107 changed files with 2693 additions and 1048 deletions

View File

@ -1,3 +1,5 @@
Feature requests should go in the forum: https://community.home-assistant.io/c/feature-requests
**Home Assistant release (`hass --version`):**

1
.gitignore vendored
View File

@ -1,6 +1,5 @@
config/*
!config/home-assistant.conf.default
homeassistant/components/frontend/www_static/polymer/bower_components/*
# There is not a better solution afaik..
!config/custom_components

View File

@ -1,9 +1,9 @@
homeassistant:
# Omitted values in this section will be auto detected using freegeoip.net
# Omitted values in this section will be auto detected using freegeoip.io
# Location required to calculate the time the sun rises and sets.
# Cooridinates are also used for location for weather related components.
# Google Maps can be used to determine more precise GPS cooridinates.
# Coordinates are also used for location for weather related components.
# Google Maps can be used to determine more precise GPS coordinates.
latitude: 32.87336
longitude: 117.22743

View File

@ -22,6 +22,7 @@ from homeassistant.const import (
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
TEMP_CELCIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers.entity import Entity
@ -293,7 +294,10 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
enable_logging(hass, verbose, daemon, log_rotate_days)
config_dict = config_util.load_yaml_config_file(config_path)
try:
config_dict = config_util.load_yaml_config_file(config_path)
except HomeAssistantError:
return None
return from_config_dict(config_dict, hass, enable_log=False,
skip_pip=skip_pip)

View File

@ -12,6 +12,7 @@ from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent

View File

@ -6,43 +6,55 @@ https://home-assistant.io/components/alarm_control_panel.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "MQTT Alarm"
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_DISARM = "DISARM"
DEFAULT_PAYLOAD_ARM_HOME = "ARM_HOME"
DEFAULT_PAYLOAD_ARM_AWAY = "ARM_AWAY"
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_CODE = 'code'
DEFAULT_NAME = "MQTT Alarm"
DEFAULT_DISARM = "DISARM"
DEFAULT_ARM_HOME = "ARM_HOME"
DEFAULT_ARM_AWAY = "ARM_AWAY"
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_CODE): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT platform."""
if config.get('state_topic') is None:
_LOGGER.error("Missing required variable: state_topic")
return False
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
add_devices([MqttAlarm(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('command_topic'),
config.get('qos', DEFAULT_QOS),
config.get('payload_disarm', DEFAULT_PAYLOAD_DISARM),
config.get('payload_arm_home', DEFAULT_PAYLOAD_ARM_HOME),
config.get('payload_arm_away', DEFAULT_PAYLOAD_ARM_AWAY),
config.get('code'))])
config[CONF_NAME],
config[CONF_STATE_TOPIC],
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_PAYLOAD_DISARM],
config[CONF_PAYLOAD_ARM_HOME],
config[CONF_PAYLOAD_ARM_AWAY],
config.get(CONF_CODE))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
@ -62,7 +74,7 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
self._code = str(code) if code else None
self._code = code
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""

View File

@ -4,26 +4,29 @@ Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#mqtt-trigger
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.const import CONF_PLATFORM
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['mqtt']
CONF_TOPIC = 'topic'
CONF_PAYLOAD = 'payload'
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): mqtt.DOMAIN,
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD): cv.string,
})
def trigger(hass, config, action):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
topic = config[CONF_TOPIC]
payload = config.get(CONF_PAYLOAD)
if topic is None:
logging.getLogger(__name__).error(
"Missing configuration key %s", CONF_TOPIC)
return False
def mqtt_automation_listener(msg_topic, msg_payload, qos):
"""Listen for MQTT messages."""
if payload is None or payload == msg_payload:

View File

@ -6,43 +6,50 @@ https://home-assistant.io/components/binary_sensor.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_SENSOR_CLASS = 'sensor_class'
CONF_PAYLOAD_ON = 'payload_on'
CONF_PAYLOAD_OFF = 'payload_off'
DEFAULT_NAME = 'MQTT Binary sensor'
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None):
vol.Any(vol.In(SENSOR_CLASSES), vol.SetTo(None)),
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add MQTT binary sensor."""
if config.get('state_topic') is None:
_LOGGER.error('Missing required variable: state_topic')
return False
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
add_devices([MqttBinarySensor(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic', None),
sensor_class,
config.get('qos', DEFAULT_QOS),
config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE))])
config[CONF_NAME],
config[CONF_STATE_TOPIC],
config[CONF_SENSOR_CLASS],
config[CONF_QOS],
config[CONF_PAYLOAD_ON],
config[CONF_PAYLOAD_OFF],
config.get(CONF_VALUE_TEMPLATE)
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes

View File

@ -136,7 +136,7 @@ def setup(hass, config):
'Home Alone']},
'who_cooks': {'icon': 'mdi:panda',
'initial': 'Anne Therese',
'name': 'Who cooks today',
'name': 'Cook today',
'options': ['Paulus', 'Anne Therese']}}})
# Set up input boolean
bootstrap.setup_component(
@ -144,6 +144,11 @@ def setup(hass, config):
{'input_boolean': {'notify': {'icon': 'mdi:car',
'initial': False,
'name': 'Notify Anne Therese is home'}}})
# Set up weblink
bootstrap.setup_component(
hass, 'weblink',
{'weblink': {'entities': [{'name': 'Router',
'url': 'http://192.168.1.1'}]}})
# Setup configurator
configurator_ids = []

View File

@ -204,6 +204,7 @@ class DeviceTracker(object):
return
# If no device can be found, create it
dev_id = util.ensure_unique_string(dev_id, self.devices.keys())
device = Device(
self.hass, self.consider_home, self.home_range, self.track_new,
dev_id, mac, (host_name or dev_id).replace('_', ' '))

View File

@ -6,28 +6,27 @@ https://home-assistant.io/components/device_tracker.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant import util
from homeassistant.components.mqtt import CONF_QOS
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['mqtt']
CONF_QOS = 'qos'
CONF_DEVICES = 'devices'
DEFAULT_QOS = 0
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES): {cv.string: mqtt.valid_subscribe_topic},
})
def setup_scanner(hass, config, see):
"""Setup the MQTT tracker."""
devices = config.get(CONF_DEVICES)
qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS)
if not isinstance(devices, dict):
_LOGGER.error('Expected %s to be a dict, found %s', CONF_DEVICES,
devices)
return False
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
dev_id_lookup = {}

View File

@ -9,14 +9,15 @@ import threading
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
CONF_PORT
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear==0.3.2']
REQUIREMENTS = ['pynetgear==0.3.3']
def get_scanner(hass, config):
@ -25,12 +26,13 @@ def get_scanner(hass, config):
host = info.get(CONF_HOST)
username = info.get(CONF_USERNAME)
password = info.get(CONF_PASSWORD)
port = info.get(CONF_PORT)
if password is not None and host is None:
_LOGGER.warning('Found username or password but no host')
return None
scanner = NetgearDeviceScanner(host, username, password)
scanner = NetgearDeviceScanner(host, username, password, port)
return scanner if scanner.success_init else None
@ -38,7 +40,7 @@ def get_scanner(hass, config):
class NetgearDeviceScanner(object):
"""Queries a Netgear wireless router using the SOAP-API."""
def __init__(self, host, username, password):
def __init__(self, host, username, password, port):
"""Initialize the scanner."""
import pynetgear
@ -49,8 +51,10 @@ class NetgearDeviceScanner(object):
self._api = pynetgear.Netgear()
elif username is None:
self._api = pynetgear.Netgear(password, host)
else:
elif port is None:
self._api = pynetgear.Netgear(password, host, username)
else:
self._api = pynetgear.Netgear(password, host, username, port)
_LOGGER.info("Logging in")

View File

@ -80,6 +80,11 @@ def setup_scanner(hass, config, see):
if not isinstance(data, dict) or data.get('_type') != 'transition':
return
if data.get('desc') is None:
_LOGGER.error(
"Location missing from `enter/exit` message - "
"please turn `Share` on in OwnTracks app")
return
# OwnTracks uses - at the start of a beacon zone
# to switch on 'hold mode' - ignore this
location = data['desc'].lstrip("-")

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED)
DOMAIN = "discovery"
REQUIREMENTS = ['netdisco==0.6.1']
REQUIREMENTS = ['netdisco==0.6.2']
SCAN_INTERVAL = 300 # seconds

View File

@ -1,2 +1,2 @@
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
VERSION = "45956327284e66953bfbed5d172928b8"
VERSION = "7baf9a2bf8d94448eeaa6a1ceaf8464e"

View File

@ -0,0 +1 @@
Copyright 2011 Google Inc. All Rights Reserved.

View File

@ -0,0 +1,17 @@
<p>Roboto has a dual nature. It has a mechanical skeleton and the forms are
largely geometric. At the same time, the font features friendly and open
curves. While some grotesks distort their letterforms to force a rigid rhythm,
Roboto doesnt compromise, allowing letters to be settled into their natural
width. This makes for a more natural reading rhythm more commonly found in
humanist and serif types.</p>
<p>This is the normal family, which can be used alongside the
<a href="http://www.google.com/fonts/specimen/Roboto+Condensed">Roboto Condensed</a> family and the
<a href="http://www.google.com/fonts/specimen/Roboto+Slab">Roboto Slab</a> family.</p>
<p>
<b>Updated January 14 2015:</b>
Christian Robertson and the Material Design team unveiled the latest version of Roboto at Google I/O last year, and it is now available from Google Fonts.
Existing websites using Roboto via Google Fonts will start using the latest version automatically.
If you have installed the fonts on your computer, please download them again and re-install.
</p>

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,129 @@
{
"name": "Roboto",
"designer": "Christian Robertson",
"license": "Apache2",
"visibility": "External",
"category": "Sans Serif",
"size": 86523,
"fonts": [
{
"name": "Roboto",
"style": "normal",
"weight": 100,
"filename": "Roboto-Thin.ttf",
"postScriptName": "Roboto-Thin",
"fullName": "Roboto Thin",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "italic",
"weight": 100,
"filename": "Roboto-ThinItalic.ttf",
"postScriptName": "Roboto-ThinItalic",
"fullName": "Roboto Thin Italic",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "normal",
"weight": 300,
"filename": "Roboto-Light.ttf",
"postScriptName": "Roboto-Light",
"fullName": "Roboto Light",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "italic",
"weight": 300,
"filename": "Roboto-LightItalic.ttf",
"postScriptName": "Roboto-LightItalic",
"fullName": "Roboto Light Italic",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "normal",
"weight": 400,
"filename": "Roboto-Regular.ttf",
"postScriptName": "Roboto-Regular",
"fullName": "Roboto",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "italic",
"weight": 400,
"filename": "Roboto-Italic.ttf",
"postScriptName": "Roboto-Italic",
"fullName": "Roboto Italic",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "normal",
"weight": 500,
"filename": "Roboto-Medium.ttf",
"postScriptName": "Roboto-Medium",
"fullName": "Roboto Medium",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "italic",
"weight": 500,
"filename": "Roboto-MediumItalic.ttf",
"postScriptName": "Roboto-MediumItalic",
"fullName": "Roboto Medium Italic",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "normal",
"weight": 700,
"filename": "Roboto-Bold.ttf",
"postScriptName": "Roboto-Bold",
"fullName": "Roboto Bold",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "italic",
"weight": 700,
"filename": "Roboto-BoldItalic.ttf",
"postScriptName": "Roboto-BoldItalic",
"fullName": "Roboto Bold Italic",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "normal",
"weight": 900,
"filename": "Roboto-Black.ttf",
"postScriptName": "Roboto-Black",
"fullName": "Roboto Black",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
},
{
"name": "Roboto",
"style": "italic",
"weight": 900,
"filename": "Roboto-BlackItalic.ttf",
"postScriptName": "Roboto-BlackItalic",
"fullName": "Roboto Black Italic",
"copyright": "Copyright 2011 Google Inc. All Rights Reserved."
}
],
"subsets": [
"cyrillic",
"cyrillic-ext",
"greek",
"greek-ext",
"latin",
"latin-ext",
"menu",
"vietnamese"
],
"dateAdded": "2013-01-09"
}

View File

@ -0,0 +1,17 @@
<p>
Roboto Mono is a monospaced addition to the <a href="https://www.google.com/fonts/specimen/Roboto">Roboto</a> type family.
Like the other members of the Roboto family, the fonts are optimized for readability on screens across a wide variety of devices and reading environments.
While the monospaced version is related to its variable width cousin, it doesn&#8217;t hesitate to change forms to better fit the constraints of a monospaced environment.
For example, narrow glyphs like &#8216;I&#8217;, &#8216;l&#8217; and &#8216;i&#8217; have added serifs for more even texture while wider glyphs are adjusted for weight.
Curved caps like &#8216;C&#8217; and &#8216;O&#8217; take on the straighter sides from Roboto Condensed.
</p>
<p>
Special consideration is given to glyphs important for reading and writing software source code.
Letters with similar shapes are easy to tell apart.
Digit &#8216;1&#8217;, lowercase &#8216;l&#8217; and capital &#8216;I&#8217; are easily differentiated as are zero and the letter &#8216;O&#8217;.
Punctuation important for code has also been considered.
For example, the curly braces &#8216;{&nbsp;}&#8217; have exaggerated points to clearly differentiate them from parenthesis &#8216;(&nbsp;)&#8217; and braces &#8216;[&nbsp;]&#8217;.
Periods and commas are also exaggerated to identify them more quickly.
The scale and weight of symbols commonly used as operators have also been optimized.
</p>

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,111 @@
{
"name": "Roboto Mono",
"designer": "Christian Robertson",
"license": "Apache2",
"visibility": "External",
"category": "Monospace",
"size": 51290,
"fonts": [
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-Thin",
"fullName": "Roboto Mono Thin",
"style": "normal",
"weight": 100,
"filename": "RobotoMono-Thin.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-ThinItalic",
"fullName": "Roboto Mono Thin Italic",
"style": "italic",
"weight": 100,
"filename": "RobotoMono-ThinItalic.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-Light",
"fullName": "Roboto Mono Light",
"style": "normal",
"weight": 300,
"filename": "RobotoMono-Light.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-LightItalic",
"fullName": "Roboto Mono Light Italic",
"style": "italic",
"weight": 300,
"filename": "RobotoMono-LightItalic.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-Regular",
"fullName": "Roboto Mono",
"style": "normal",
"weight": 400,
"filename": "RobotoMono-Regular.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-Italic",
"fullName": "Roboto Mono Italic",
"style": "italic",
"weight": 400,
"filename": "RobotoMono-Italic.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-Medium",
"fullName": "Roboto Mono Medium",
"style": "normal",
"weight": 500,
"filename": "RobotoMono-Medium.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-MediumItalic",
"fullName": "Roboto Mono Medium Italic",
"style": "italic",
"weight": 500,
"filename": "RobotoMono-MediumItalic.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-Bold",
"fullName": "Roboto Mono Bold",
"style": "normal",
"weight": 700,
"filename": "RobotoMono-Bold.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
},
{
"name": "Roboto Mono",
"postScriptName": "RobotoMono-BoldItalic",
"fullName": "Roboto Mono Bold Italic",
"style": "italic",
"weight": 700,
"filename": "RobotoMono-BoldItalic.ttf",
"copyright": "Copyright 2015 Google Inc. All Rights Reserved."
}
],
"subsets": [
"cyrillic",
"cyrillic-ext",
"greek",
"greek-ext",
"latin",
"latin-ext",
"menu",
"vietnamese"
],
"dateAdded": "2015-05-13"
}

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 567d8e6fba2199b22e4e5d1730f8f74bb81a402b
Subproject commit de852d20bd635796c1a2d15befae79e7d0a2fae6

View File

@ -0,0 +1,139 @@
"""
Support for MQTT garage doors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/garage_door.mqtt/
"""
import logging
import voluptuous as vol
from homeassistant.const import (STATE_OPEN, STATE_CLOSED, SERVICE_OPEN,
SERVICE_CLOSE)
import homeassistant.components.mqtt as mqtt
from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__)
CONF_STATE_OPEN = 'state_open'
CONF_STATE_CLOSED = 'state_closed'
CONF_SERVICE_OPEN = 'service_open'
CONF_SERVICE_CLOSE = 'service_close'
DEFAULT_NAME = 'MQTT Garage Door'
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_SERVICE_OPEN, default=SERVICE_OPEN): cv.string,
vol.Optional(CONF_SERVICE_CLOSE, default=SERVICE_CLOSE): cv.string
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Garage Door."""
add_devices_callback([MqttGarageDoor(
hass,
config[CONF_NAME],
config.get(CONF_STATE_TOPIC),
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_RETAIN],
config[CONF_STATE_OPEN],
config[CONF_STATE_CLOSED],
config[CONF_SERVICE_OPEN],
config[CONF_SERVICE_CLOSE],
config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttGarageDoor(GarageDoorDevice):
"""Representation of a MQTT garage door."""
def __init__(self, hass, name, state_topic, command_topic, qos, retain,
state_open, state_closed, service_open, service_close,
optimistic, value_template):
"""Initialize the garage door."""
self._hass = hass
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._retain = retain
self._state_open = state_open
self._state_closed = state_closed
self._service_open = service_open
self._service_close = service_close
self._optimistic = optimistic or state_topic is None
self._state = False
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
if payload == self._state_open:
self._state = True
self.update_ha_state()
elif payload == self._state_closed:
self._state = False
self.update_ha_state()
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
else:
mqtt.subscribe(hass, self._state_topic, message_received,
self._qos)
@property
def name(self):
"""Return the name of the garage door if any."""
return self._name
@property
def is_opened(self):
"""Return true if door is closed."""
return self._state
@property
def is_closed(self):
"""Return true if door is closed."""
return self._state is False
@property
def assumed_state(self):
"""Return true if we do optimistic updates."""
return self._optimistic
def close_door(self):
"""Close the door."""
mqtt.publish(self.hass, self._command_topic, self._service_close,
self._qos, self._retain)
if self._optimistic:
# Optimistically assume that door has changed state.
self._state = False
self.update_ha_state()
def open_door(self):
"""Open the door."""
mqtt.publish(self.hass, self._command_topic, self._service_open,
self._qos, self._retain)
if self._optimistic:
# Optimistically assume that door has changed state.
self._state = True
self.update_ha_state()

View File

@ -39,7 +39,7 @@ def _conf_preprocess(value):
return value
_SINGLE_GROUP_CONFIG = vol.Schema(vol.All(_conf_preprocess, {
vol.Required(CONF_ENTITIES): cv.entity_ids,
vol.Optional(CONF_ENTITIES): vol.Any(None, cv.entity_ids),
CONF_VIEW: bool,
CONF_NAME: str,
CONF_ICON: cv.icon,
@ -145,7 +145,7 @@ def setup(hass, config):
"""Setup all groups found definded in the configuration."""
for object_id, conf in config.get(DOMAIN, {}).items():
name = conf.get(CONF_NAME, object_id)
entity_ids = conf[CONF_ENTITIES]
entity_ids = conf.get(CONF_ENTITIES) or []
icon = conf.get(CONF_ICON)
view = conf.get(CONF_VIEW)

View File

@ -77,7 +77,9 @@ def setup(hass, config):
name='HTTP-server').start())
hass.http = server
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
hass.config.api = rem.API(server_host if server_host != '0.0.0.0'
else util.get_local_ip(),
api_password, server_port,
ssl_certificate is not None)
return True

View File

@ -92,8 +92,8 @@ LIGHT_TURN_ON_SCHEMA = vol.Schema({
ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)),
vol.Coerce(tuple)),
ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=154, max=500)),
ATTR_FLASH: [FLASH_SHORT, FLASH_LONG],
ATTR_EFFECT: [EFFECT_COLORLOOP, EFFECT_RANDOM, EFFECT_WHITE],
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
ATTR_EFFECT: vol.In([EFFECT_COLORLOOP, EFFECT_RANDOM, EFFECT_WHITE]),
})
LIGHT_TURN_OFF_SCHEMA = vol.Schema({

View File

@ -7,46 +7,85 @@ https://home-assistant.io/components/light.mqtt/
import logging
from functools import partial
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light)
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import render_with_possible_json_value
from homeassistant.util import convert
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_STATE_VALUE_TEMPLATE = 'state_value_template'
CONF_BRIGHTNESS_STATE_TOPIC = 'brightness_state_topic'
CONF_BRIGHTNESS_COMMAND_TOPIC = 'brightness_command_topic'
CONF_BRIGHTNESS_VALUE_TEMPLATE = 'brightness_value_template'
CONF_RGB_STATE_TOPIC = 'rgb_state_topic'
CONF_RGB_COMMAND_TOPIC = 'rgb_command_topic'
CONF_RGB_VALUE_TEMPLATE = 'rgb_value_template'
CONF_PAYLOAD_ON = 'payload_on'
CONF_PAYLOAD_OFF = 'payload_off'
CONF_BRIGHTNESS_SCALE = 'brightness_scale'
DEFAULT_NAME = 'MQTT Light'
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_OPTIMISTIC = False
DEFAULT_BRIGHTNESS_SCALE = 255
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_BRIGHTNESS_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_BRIGHTNESS_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_BRIGHTNESS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_RGB_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE):
vol.All(vol.Coerce(int), vol.Range(min=1)),
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Light."""
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
config.setdefault(CONF_STATE_VALUE_TEMPLATE,
config.get(CONF_VALUE_TEMPLATE))
add_devices_callback([MqttLight(
hass,
convert(config.get('name'), str, DEFAULT_NAME),
{key: convert(config.get(key), str) for key in
(typ + topic
for typ in ('', 'brightness_', 'rgb_')
for topic in ('state_topic', 'command_topic'))},
{key: convert(config.get(key + '_value_template'), str)
for key in ('state', 'brightness', 'rgb')},
convert(config.get('qos'), int, DEFAULT_QOS),
config[CONF_NAME],
{
'on': convert(config.get('payload_on'), str, DEFAULT_PAYLOAD_ON),
'off': convert(config.get('payload_off'), str, DEFAULT_PAYLOAD_OFF)
key: config.get(key) for key in (
CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC,
CONF_BRIGHTNESS_STATE_TOPIC,
CONF_BRIGHTNESS_COMMAND_TOPIC,
CONF_RGB_STATE_TOPIC,
CONF_RGB_COMMAND_TOPIC,
)
},
convert(config.get('optimistic'), bool, DEFAULT_OPTIMISTIC),
convert(config.get('brightness_scale'), int, DEFAULT_BRIGHTNESS_SCALE)
{
'state': config.get(CONF_STATE_VALUE_TEMPLATE),
'brightness': config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE),
'rgb': config.get(CONF_RGB_VALUE_TEMPLATE)
},
config[CONF_QOS],
config[CONF_RETAIN],
{
'on': config[CONF_PAYLOAD_ON],
'off': config[CONF_PAYLOAD_OFF],
},
config[CONF_OPTIMISTIC],
config[CONF_BRIGHTNESS_SCALE],
)])
@ -54,13 +93,14 @@ class MqttLight(Light):
"""MQTT light."""
# pylint: disable=too-many-arguments,too-many-instance-attributes
def __init__(self, hass, name, topic, templates, qos, payload, optimistic,
brightness_scale):
def __init__(self, hass, name, topic, templates, qos, retain, payload,
optimistic, brightness_scale):
"""Initialize MQTT light."""
self._hass = hass
self._name = name
self._topic = topic
self._qos = qos
self._retain = retain
self._payload = payload
self._optimistic = optimistic or topic["state_topic"] is None
self._optimistic_rgb = optimistic or topic["rgb_state_topic"] is None
@ -156,7 +196,8 @@ class MqttLight(Light):
self._topic["rgb_command_topic"] is not None:
mqtt.publish(self._hass, self._topic["rgb_command_topic"],
"{},{},{}".format(*kwargs[ATTR_RGB_COLOR]), self._qos)
"{},{},{}".format(*kwargs[ATTR_RGB_COLOR]),
self._qos, self._retain)
if self._optimistic_rgb:
self._rgb = kwargs[ATTR_RGB_COLOR]
@ -167,14 +208,14 @@ class MqttLight(Light):
percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255
device_brightness = int(percent_bright * self._brightness_scale)
mqtt.publish(self._hass, self._topic["brightness_command_topic"],
device_brightness, self._qos)
device_brightness, self._qos, self._retain)
if self._optimistic_brightness:
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
mqtt.publish(self._hass, self._topic["command_topic"],
self._payload["on"], self._qos)
self._payload["on"], self._qos, self._retain)
if self._optimistic:
# Optimistically assume that switch has changed state.
@ -187,7 +228,7 @@ class MqttLight(Light):
def turn_off(self, **kwargs):
"""Turn the device off."""
mqtt.publish(self._hass, self._topic["command_topic"],
self._payload["off"], self._qos)
self._payload["off"], self._qos, self._retain)
if self._optimistic:
# Optimistically assume that switch has changed state.

View File

@ -13,6 +13,8 @@ DEPENDENCIES = ['rfxtrx']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the RFXtrx platform."""

View File

@ -6,40 +6,51 @@ https://home-assistant.io/components/lock.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.lock import LockDevice
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_LOCK = 'payload_lock'
CONF_PAYLOAD_UNLOCK = 'payload_unlock'
DEFAULT_NAME = "MQTT Lock"
DEFAULT_PAYLOAD_LOCK = "LOCK"
DEFAULT_PAYLOAD_UNLOCK = "UNLOCK"
DEFAULT_QOS = 0
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK):
cv.string,
vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK):
cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the MQTT lock."""
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
add_devices_callback([MqttLock(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('command_topic'),
config.get('qos', DEFAULT_QOS),
config.get('retain', DEFAULT_RETAIN),
config.get('payload_lock', DEFAULT_PAYLOAD_LOCK),
config.get('payload_unlock', DEFAULT_PAYLOAD_UNLOCK),
config.get('optimistic', DEFAULT_OPTIMISTIC),
config[CONF_NAME],
config.get(CONF_STATE_TOPIC),
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_RETAIN],
config[CONF_PAYLOAD_LOCK],
config[CONF_PAYLOAD_UNLOCK],
config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE))])

View File

@ -20,11 +20,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
'Living Room', 'eyU3bRy2x44',
'♥♥ The Best Fireplace Video (3 hours)'),
DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours'),
DemoMusicPlayer(), DemoTVShowPlayer(), DemoReceiver(),
DemoMusicPlayer(), DemoTVShowPlayer(),
])
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/hqdefault.jpg'
YOUTUBE_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
@ -35,9 +35,7 @@ MUSIC_PLAYER_SUPPORT = \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
NETFLIX_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
RECEIVER_SUPPORT = SUPPORT_SELECT_SOURCE
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
class AbstractDemoPlayer(MediaPlayerDevice):
@ -210,7 +208,8 @@ class DemoMusicPlayer(AbstractDemoPlayer):
@property
def media_image_url(self):
"""Return the image url of current playing media."""
return 'https://graph.facebook.com/107771475912710/picture'
return 'https://graph.facebook.com/v2.5/107771475912710/' \
'picture?type=large'
@property
def media_title(self):
@ -269,6 +268,7 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
super().__init__('Lounge room')
self._cur_episode = 1
self._episode_count = 13
self._source = 'dvd'
@property
def media_content_id(self):
@ -288,7 +288,7 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
@property
def media_image_url(self):
"""Return the image url of current playing media."""
return 'https://graph.facebook.com/HouseofCards/picture'
return 'https://graph.facebook.com/v2.5/HouseofCards/picture?width=400'
@property
def media_title(self):
@ -315,6 +315,11 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
"""Return the current running application."""
return "Netflix"
@property
def source(self):
"""Return the current input source."""
return self._source
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
@ -340,28 +345,7 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
self._cur_episode += 1
self.update_ha_state()
class DemoReceiver(AbstractDemoPlayer):
"""A Demo receiver that only supports changing source input."""
# We only implement the methods that we support
# pylint: disable=abstract-method
def __init__(self):
"""Initialize the demo device."""
super().__init__('receiver')
self._source = 'dvd'
@property
def source(self):
"""Return the current input source."""
return self._source
def select_source(self, source):
"""Set the input source."""
self._source = source
self.update_ha_state()
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return RECEIVER_SUPPORT

View File

@ -9,7 +9,8 @@ import urllib
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import (
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
@ -17,7 +18,8 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['jsonrpc-requests==0.1']
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
SUPPORT_PLAY_MEDIA
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -268,3 +270,7 @@ class KodiDevice(MediaPlayerDevice):
self._server.Player.Seek(players[0]['playerid'], time)
self.update_ha_state()
def play_media(self, media_type, media_id):
"""Send the play_media command to the media player."""
self._server.Player.Open({media_type: media_id}, {})

View File

@ -14,11 +14,11 @@ import voluptuous as vol
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
import homeassistant.util as util
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_VALUE_TEMPLATE)
_LOGGER = logging.getLogger(__name__)
@ -41,6 +41,11 @@ CONF_PASSWORD = 'password'
CONF_CERTIFICATE = 'certificate'
CONF_PROTOCOL = 'protocol'
CONF_STATE_TOPIC = 'state_topic'
CONF_COMMAND_TOPIC = 'command_topic'
CONF_QOS = 'qos'
CONF_RETAIN = 'retain'
PROTOCOL_31 = '3.1'
PROTOCOL_311 = '3.1.1'
@ -53,25 +58,71 @@ DEFAULT_PROTOCOL = PROTOCOL_311
ATTR_TOPIC = 'topic'
ATTR_PAYLOAD = 'payload'
ATTR_PAYLOAD_TEMPLATE = 'payload_template'
ATTR_QOS = 'qos'
ATTR_RETAIN = 'retain'
ATTR_QOS = CONF_QOS
ATTR_RETAIN = CONF_RETAIN
MAX_RECONNECT_WAIT = 300 # seconds
# Service call validation schema
def mqtt_topic(value):
"""Validate that we can publish using this MQTT topic."""
if isinstance(value, str) and all(c not in value for c in '#+\0'):
def valid_subscribe_topic(value, invalid_chars='\0'):
"""Validate that we can subscribe using this MQTT topic."""
if isinstance(value, str) and all(c not in value for c in invalid_chars):
return vol.Length(min=1, max=65535)(value)
raise vol.Invalid('Invalid MQTT topic name')
def valid_publish_topic(value):
"""Validate that we can publish using this MQTT topic."""
return valid_subscribe_topic(value, invalid_chars='#+\0')
_VALID_QOS_SCHEMA = vol.All(vol.Coerce(int), vol.In([0, 1, 2]))
_HBMQTT_CONFIG_SCHEMA = vol.Schema(dict)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_CLIENT_ID): cv.string,
vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(CONF_BROKER): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT):
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CERTIFICATE): cv.isfile,
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL):
vol.All(cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])),
vol.Optional(CONF_EMBEDDED): _HBMQTT_CONFIG_SCHEMA,
}),
}, extra=vol.ALLOW_EXTRA)
MQTT_BASE_PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): DOMAIN,
vol.Optional(CONF_SCAN_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
})
# Sensor type platforms subscribe to mqtt events
MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
# Switch type platforms publish to mqtt and may subscribe
MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
# Service call validation schema
MQTT_PUBLISH_SCHEMA = vol.Schema({
vol.Required(ATTR_TOPIC): mqtt_topic,
vol.Required(ATTR_TOPIC): valid_publish_topic,
vol.Exclusive(ATTR_PAYLOAD, 'payload'): object,
vol.Exclusive(ATTR_PAYLOAD_TEMPLATE, 'payload'): cv.string,
vol.Required(ATTR_QOS, default=DEFAULT_QOS):
vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
vol.Required(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
vol.Required(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
}, required=True)
@ -136,8 +187,8 @@ def setup(hass, config):
# pylint: disable=too-many-locals
conf = config.get(DOMAIN, {})
client_id = util.convert(conf.get(CONF_CLIENT_ID), str)
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
client_id = conf.get(CONF_CLIENT_ID)
keepalive = conf.get(CONF_KEEPALIVE)
broker_config = _setup_server(hass, config)
@ -151,16 +202,11 @@ def setup(hass, config):
if CONF_BROKER in conf:
broker = conf[CONF_BROKER]
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
username = util.convert(conf.get(CONF_USERNAME), str)
password = util.convert(conf.get(CONF_PASSWORD), str)
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
protocol = util.convert(conf.get(CONF_PROTOCOL), str, DEFAULT_PROTOCOL)
if protocol not in (PROTOCOL_31, PROTOCOL_311):
_LOGGER.error('Invalid protocol specified: %s. Allowed values: %s, %s',
protocol, PROTOCOL_31, PROTOCOL_311)
return False
port = conf[CONF_PORT]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
certificate = conf.get(CONF_CERTIFICATE)
protocol = conf[CONF_PROTOCOL]
# For cloudmqtt.com, secured connection, auto fill in certificate
if certificate is None and 19999 < port < 30000 and \

View File

@ -6,9 +6,11 @@ https://home-assistant.io/components/mqtt_eventstream/
"""
import json
import voluptuous as vol
import homeassistant.loader as loader
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.components.mqtt import SERVICE_PUBLISH as MQTT_SVC_PUBLISH
from homeassistant.components.mqtt import (
valid_publish_topic, valid_subscribe_topic)
from homeassistant.const import (
ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_SERVICE_EXECUTED,
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
@ -18,12 +20,23 @@ from homeassistant.remote import JSONEncoder
DOMAIN = "mqtt_eventstream"
DEPENDENCIES = ['mqtt']
CONF_PUBLISH_TOPIC = 'publish_topic'
CONF_SUBSCRIBE_TOPIC = 'subscribe_topic'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_PUBLISH_TOPIC): valid_publish_topic,
vol.Optional(CONF_SUBSCRIBE_TOPIC): valid_subscribe_topic,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup th MQTT eventstream component."""
"""Setup the MQTT eventstream component."""
mqtt = loader.get_component('mqtt')
pub_topic = config[DOMAIN].get('publish_topic', None)
sub_topic = config[DOMAIN].get('subscribe_topic', None)
conf = config.get(DOMAIN, {})
pub_topic = conf.get(CONF_PUBLISH_TOPIC)
sub_topic = conf.get(CONF_SUBSCRIBE_TOPIC)
def _event_publisher(event):
"""Handle events by publishing them on the MQTT queue."""
@ -36,8 +49,8 @@ def setup(hass, config):
# to the MQTT topic, or you will end up in an infinite loop.
if event.event_type == EVENT_CALL_SERVICE:
if (
event.data.get('domain') == MQTT_DOMAIN and
event.data.get('service') == MQTT_SVC_PUBLISH and
event.data.get('domain') == mqtt.DOMAIN and
event.data.get('service') == mqtt.SERVICE_PUBLISH and
event.data[ATTR_SERVICE_DATA].get('topic') == pub_topic
):
return

View File

@ -9,7 +9,8 @@ import logging
import homeassistant.bootstrap as bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED, TEMP_CELCIUS)
EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED, TEMP_CELCIUS,
CONF_OPTIMISTIC)
from homeassistant.helpers import validate_config
CONF_GATEWAYS = 'gateways'
@ -19,7 +20,6 @@ CONF_PERSISTENCE = 'persistence'
CONF_PERSISTENCE_FILE = 'persistence_file'
CONF_VERSION = 'version'
CONF_BAUD_RATE = 'baud_rate'
CONF_OPTIMISTIC = 'optimistic'
DEFAULT_VERSION = '1.4'
DEFAULT_BAUD_RATE = 115200

View File

@ -168,6 +168,7 @@ class RecorderRun(object):
class Recorder(threading.Thread):
"""A threaded recorder class."""
# pylint: disable=too-many-instance-attributes
def __init__(self, hass):
"""Initialize the recorder."""
threading.Thread.__init__(self)
@ -179,6 +180,7 @@ class Recorder(threading.Thread):
self.lock = threading.Lock()
self.recording_start = dt_util.utcnow()
self.utc_offset = dt_util.now().utcoffset().total_seconds()
self.db_path = self.hass.config.path(DB_FILE)
def start_recording(event):
"""Start recording."""
@ -302,8 +304,7 @@ class Recorder(threading.Thread):
def _setup_connection(self):
"""Ensure database is ready to fly."""
db_path = self.hass.config.path(DB_FILE)
self.conn = sqlite3.connect(db_path, check_same_thread=False)
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
self.conn.row_factory = sqlite3.Row
# Make sure the database is closed whenever Python exits

View File

@ -5,15 +5,19 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/rfxtrx/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.util import slugify
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.const import ATTR_ENTITY_ID
REQUIREMENTS = ['pyRFXtrx==0.6.5']
DOMAIN = "rfxtrx"
ATTR_AUTOMATIC_ADD = 'automatic_add'
ATTR_DEVICE = 'device'
ATTR_DEBUG = 'debug'
ATTR_STATE = 'state'
@ -21,8 +25,10 @@ ATTR_NAME = 'name'
ATTR_PACKETID = 'packetid'
ATTR_FIREEVENT = 'fire_event'
ATTR_DATA_TYPE = 'data_type'
ATTR_DUMMY = "dummy"
SIGNAL_REPETITIONS = 1
ATTR_DUMMY = 'dummy'
CONF_SIGNAL_REPETITIONS = 'signal_repetitions'
CONF_DEVICES = 'devices'
DEFAULT_SIGNAL_REPETITIONS = 1
EVENT_BUTTON_PRESSED = 'button_pressed'
@ -32,6 +38,42 @@ _LOGGER = logging.getLogger(__name__)
RFXOBJECT = None
def validate_packetid(value):
"""Validate that value is a valid packet id for rfxtrx."""
if get_rfx_object(value):
return value
else:
raise vol.Invalid('invalid packet id for {}'.format(value))
# Share between rfxtrx platforms
VALID_DEVICE_ID = vol.All(cv.string, vol.Lower)
VALID_SENSOR_DEVICE_ID = vol.All(VALID_DEVICE_ID,
vol.truth(lambda val:
val.startswith('sensor_')))
DEVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_PACKETID): validate_packetid,
vol.Optional(ATTR_FIREEVENT, default=False): cv.boolean,
})
DEFAULT_SCHEMA = vol.Schema({
vol.Required("platform"): DOMAIN,
vol.Required(CONF_DEVICES): {cv.slug: DEVICE_SCHEMA},
vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean,
vol.Optional(CONF_SIGNAL_REPETITIONS, default=DEFAULT_SIGNAL_REPETITIONS):
vol.Coerce(int),
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(ATTR_DEVICE): VALID_DEVICE_ID,
vol.Optional(ATTR_DEBUG, default=False): cv.boolean,
vol.Optional(ATTR_DUMMY, default=False): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup the RFXtrx component."""
# Declare the Handle event
@ -56,16 +98,9 @@ def setup(hass, config):
# Init the rfxtrx module.
global RFXOBJECT
if ATTR_DEVICE not in config[DOMAIN]:
_LOGGER.error(
"can not find device parameter in %s YAML configuration section",
DOMAIN
)
return False
device = config[DOMAIN][ATTR_DEVICE]
debug = config[DOMAIN].get(ATTR_DEBUG, False)
dummy_connection = config[DOMAIN].get(ATTR_DUMMY, False)
debug = config[DOMAIN][ATTR_DEBUG]
dummy_connection = config[DOMAIN][ATTR_DUMMY]
if dummy_connection:
RFXOBJECT =\
@ -103,16 +138,16 @@ def get_rfx_object(packetid):
def get_devices_from_config(config, device):
"""Read rfxtrx configuration."""
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS)
signal_repetitions = config[CONF_SIGNAL_REPETITIONS]
devices = []
for device_id, entity_info in config.get('devices', {}).items():
for device_id, entity_info in config[CONF_DEVICES].items():
if device_id in RFX_DEVICES:
continue
_LOGGER.info("Add %s rfxtrx", entity_info[ATTR_NAME])
# Check if i must fire event
fire_event = entity_info.get(ATTR_FIREEVENT, False)
fire_event = entity_info[ATTR_FIREEVENT]
datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event}
rfxobject = get_rfx_object(entity_info[ATTR_PACKETID])
@ -127,7 +162,7 @@ def get_new_device(event, config, device):
"""Add entity if not exist and the automatic_add is True."""
device_id = slugify(event.device.id_string.lower())
if device_id not in RFX_DEVICES:
automatic_add = config.get('automatic_add', False)
automatic_add = config[ATTR_AUTOMATIC_ADD]
if not automatic_add:
return
@ -140,8 +175,7 @@ def get_new_device(event, config, device):
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (device_id, pkt_id)
datas = {ATTR_STATE: False, ATTR_FIREEVENT: False}
signal_repetitions = config.get('signal_repetitions',
SIGNAL_REPETITIONS)
signal_repetitions = config[CONF_SIGNAL_REPETITIONS]
new_device = device(entity_name, event, datas,
signal_repetitions)
RFX_DEVICES[device_id] = new_device
@ -178,8 +212,6 @@ def apply_received_command(event):
is_on = RFX_DEVICES[device_id]._brightness > 0
RFX_DEVICES[device_id]._state = is_on
RFX_DEVICES[device_id].update_ha_state()
else:
return
# Fire event
if RFX_DEVICES[device_id].should_fire_event:

View File

@ -6,38 +6,50 @@ https://home-assistant.io/components/rollershutter.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.rollershutter import RollershutterDevice
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_UP = 'payload_up'
CONF_PAYLOAD_DOWN = 'payload_down'
CONF_PAYLOAD_STOP = 'payload_stop'
DEFAULT_NAME = "MQTT Rollershutter"
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_UP = "UP"
DEFAULT_PAYLOAD_DOWN = "DOWN"
DEFAULT_PAYLOAD_STOP = "STOP"
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_UP, default=DEFAULT_PAYLOAD_UP): cv.string,
vol.Optional(CONF_PAYLOAD_DOWN, default=DEFAULT_PAYLOAD_DOWN): cv.string,
vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string,
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Rollershutter."""
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
add_devices_callback([MqttRollershutter(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('command_topic'),
config.get('qos', DEFAULT_QOS),
config.get('payload_up', DEFAULT_PAYLOAD_UP),
config.get('payload_down', DEFAULT_PAYLOAD_DOWN),
config.get('payload_stop', DEFAULT_PAYLOAD_STOP),
config.get(CONF_VALUE_TEMPLATE))])
config[CONF_NAME],
config.get(CONF_STATE_TOPIC),
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_PAYLOAD_UP],
config[CONF_PAYLOAD_DOWN],
config[CONF_PAYLOAD_STOP],
config.get(CONF_VALUE_TEMPLATE)
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes

View File

@ -67,20 +67,29 @@ def _alias_stripper(validator):
return validate
_DELAY_SCHEMA = {
vol.Required(CONF_DELAY): vol.All({
CONF_ALIAS: cv.string,
'days': vol.All(vol.Coerce(int), vol.Range(min=0)),
'seconds': vol.All(vol.Coerce(int), vol.Range(min=0)),
'microseconds': vol.All(vol.Coerce(int), vol.Range(min=0)),
'milliseconds': vol.All(vol.Coerce(int), vol.Range(min=0)),
'minutes': vol.All(vol.Coerce(int), vol.Range(min=0)),
'hours': vol.All(vol.Coerce(int), vol.Range(min=0)),
'weeks': vol.All(vol.Coerce(int), vol.Range(min=0)),
}, cv.has_at_least_one_key(
'days', 'seconds', 'microseconds', 'milliseconds', 'minutes', 'hours',
'weeks'))
}
_TIMESPEC = vol.Schema({
'days': cv.positive_int,
'hours': cv.positive_int,
'minutes': cv.positive_int,
'seconds': cv.positive_int,
'milliseconds': cv.positive_int,
})
_TIMESPEC_REQ = cv.has_at_least_one_key(
'days', 'hours', 'minutes', 'seconds', 'milliseconds',
)
_DELAY_SCHEMA = vol.Any(
vol.Schema({
vol.Required(CONF_DELAY): vol.All(_TIMESPEC.extend({
vol.Optional(CONF_ALIAS): cv.string
}), _TIMESPEC_REQ)
}),
# Alternative format in case people forgot to indent after 'delay:'
vol.All(_TIMESPEC.extend({
vol.Required(CONF_DELAY): None,
vol.Optional(CONF_ALIAS): cv.string,
}), _TIMESPEC_REQ)
)
_EVENT_SCHEMA = cv.EVENT_SCHEMA.extend({
CONF_ALIAS: cv.string,
@ -234,7 +243,9 @@ class Script(ToggleEntity):
self._listener = None
self.turn_on()
delay = timedelta(**action[CONF_DELAY])
timespec = action[CONF_DELAY] or action.copy()
timespec.pop(CONF_DELAY, None)
delay = timedelta(**timespec)
self._listener = track_point_in_utc_time(
self.hass, script_delay, date_util.utcnow() + delay)
self._cur = cur + 1

View File

@ -6,33 +6,40 @@ https://home-assistant.io/components/sensor.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN
from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN
from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "MQTT Sensor"
DEFAULT_QOS = 0
DEPENDENCIES = ['mqtt']
CONF_UNIT_OF_MEASUREMENT = 'unit_of_measurement'
DEFAULT_NAME = "MQTT Sensor"
PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup MQTT Sensor."""
if config.get('state_topic') is None:
_LOGGER.error("Missing required variable: state_topic")
return False
add_devices_callback([MqttSensor(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('qos', DEFAULT_QOS),
config.get('unit_of_measurement'),
config.get(CONF_VALUE_TEMPLATE))])
config[CONF_NAME],
config[CONF_STATE_TOPIC],
config[CONF_QOS],
config.get(CONF_UNIT_OF_MEASUREMENT),
config.get(CONF_VALUE_TEMPLATE),
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes

View File

@ -6,13 +6,16 @@ https://home-assistant.io/components/sensor.rfxtrx/
"""
import logging
from collections import OrderedDict
import voluptuous as vol
import homeassistant.components.rfxtrx as rfxtrx
from homeassistant.const import TEMP_CELCIUS
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
from homeassistant.components.rfxtrx import (
ATTR_PACKETID, ATTR_NAME, ATTR_DATA_TYPE)
ATTR_AUTOMATIC_ADD, ATTR_PACKETID, ATTR_NAME,
CONF_DEVICES, ATTR_DATA_TYPE)
DEPENDENCIES = ['rfxtrx']
@ -26,19 +29,48 @@ DATA_TYPES = OrderedDict([
('Total usage', 'W')])
_LOGGER = logging.getLogger(__name__)
DEVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_NAME, default=None): cv.string,
vol.Required(ATTR_PACKETID): rfxtrx.validate_packetid,
vol.Optional(ATTR_DATA_TYPE, default=None):
vol.In(list(DATA_TYPES.keys())),
})
def _valid_device(value):
"""Validate a dictionary of devices definitions."""
config = OrderedDict()
for key, device in value.items():
try:
key = rfxtrx.VALID_SENSOR_DEVICE_ID(key)
config[key] = DEVICE_SCHEMA(device)
if not config[key][ATTR_NAME]:
config[key][ATTR_NAME] = key
except vol.MultipleInvalid as ex:
raise vol.Invalid('Rfxtrx sensor {} is invalid: {}'
.format(key, ex))
return config
PLATFORM_SCHEMA = vol.Schema({
vol.Required("platform"): rfxtrx.DOMAIN,
vol.Required(CONF_DEVICES): vol.All(dict, _valid_device),
vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean,
}, extra=vol.ALLOW_EXTRA)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the RFXtrx platform."""
from RFXtrx import SensorEvent
sensors = []
for device_id, entity_info in config.get('devices', {}).items():
for device_id, entity_info in config['devices'].items():
if device_id in rfxtrx.RFX_DEVICES:
continue
_LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME])
event = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID])
new_sensor = RfxtrxSensor(event, entity_info[ATTR_NAME],
entity_info.get(ATTR_DATA_TYPE, None))
entity_info[ATTR_DATA_TYPE])
rfxtrx.RFX_DEVICES[slugify(device_id)] = new_sensor
sensors.append(new_sensor)
@ -62,7 +94,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
return
# Add entity if not exist and the automatic_add is True
if config.get('automatic_add', True):
if config[ATTR_AUTOMATIC_ADD]:
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (device_id, pkt_id)
_LOGGER.info(

View File

@ -5,9 +5,10 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.yr/
"""
import logging
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.helpers.entity import Entity
from homeassistant.util import dt as dt_util
@ -18,6 +19,8 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['xmltodict']
CONF_MONITORED_CONDITIONS = 'monitored_conditions'
# Sensor types are defined like so:
SENSOR_TYPES = {
'symbol': ['Symbol', None],
@ -36,6 +39,13 @@ SENSOR_TYPES = {
'dewpointTemperature': ['Dewpoint temperature', '°C'],
}
PLATFORM_SCHEMA = vol.Schema({
vol.Required('platform'): 'yr',
vol.Optional(CONF_MONITORED_CONDITIONS, default=[]):
[vol.In(SENSOR_TYPES.keys())],
vol.Optional('elevation'): cv.positive_int,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Yr.no sensor."""
@ -58,12 +68,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
weather = YrData(coordinates)
dev = []
if 'monitored_conditions' in config:
for variable in config['monitored_conditions']:
if variable not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
else:
dev.append(YrSensor(variable, weather))
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
dev.append(YrSensor(sensor_type, weather))
# add symbol as default sensor
if len(dev) == 0:

View File

@ -14,7 +14,7 @@ from homeassistant.helpers.event import (
from homeassistant.util import dt as dt_util
from homeassistant.util import location as location_util
REQUIREMENTS = ['astral==0.9']
REQUIREMENTS = ['astral==1.0']
DOMAIN = "sun"
ENTITY_ID = "sun.sun"
@ -113,8 +113,8 @@ def setup(hass, config):
from astral import Location
location = Location(('', '', latitude, longitude, hass.config.time_zone,
elevation))
location = Location(('', '', latitude, longitude,
hass.config.time_zone.zone, elevation))
sun = Sun(hass, location)
sun.point_in_time_listener(dt_util.utcnow())

View File

@ -6,41 +6,49 @@ https://home-assistant.io/components/switch.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
from homeassistant.util import convert
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_ON = 'payload_on'
CONF_PAYLOAD_OFF = 'payload_off'
DEFAULT_NAME = "MQTT Switch"
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_ON = "ON"
DEFAULT_PAYLOAD_OFF = "OFF"
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT switch."""
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
add_devices_callback([MqttSwitch(
hass,
convert(config.get('name'), str, DEFAULT_NAME),
config.get('state_topic'),
config.get('command_topic'),
convert(config.get('qos'), int, DEFAULT_QOS),
convert(config.get('retain'), bool, DEFAULT_RETAIN),
convert(config.get('payload_on'), str, DEFAULT_PAYLOAD_ON),
convert(config.get('payload_off'), str, DEFAULT_PAYLOAD_OFF),
convert(config.get('optimistic'), bool, DEFAULT_OPTIMISTIC),
config[CONF_NAME],
config.get(CONF_STATE_TOPIC),
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_RETAIN],
config[CONF_PAYLOAD_ON],
config[CONF_PAYLOAD_OFF],
config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE))])

View File

@ -26,9 +26,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
_LOGGER.error("Missing required variable: host")
continue
host = switch.get('host')
mac = switch.get('mac')
try:
switches.append(S20Switch(switch.get('name', DEFAULT_NAME),
S20(host)))
S20(host, mac=mac)))
_LOGGER.info("Initialized S20 at %s", host)
except S20Exception:
_LOGGER.exception("S20 at %s couldn't be initialized",

View File

@ -6,6 +6,7 @@ https://home-assistant.io/components/switch.rfxtrx/
"""
import logging
import homeassistant.components.rfxtrx as rfxtrx
from homeassistant.components.switch import SwitchDevice
@ -13,6 +14,8 @@ DEPENDENCIES = ['rfxtrx']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the RFXtrx platform."""

View File

@ -270,12 +270,12 @@ class ThermostatDevice(Entity):
@property
def min_temp(self):
"""Return the minimum temperature."""
return round(convert(7, TEMP_CELCIUS, self.unit_of_measurement))
return convert(7, TEMP_CELCIUS, self.unit_of_measurement)
@property
def max_temp(self):
"""Return the maximum temperature."""
return round(convert(35, TEMP_CELCIUS, self.unit_of_measurement))
return convert(35, TEMP_CELCIUS, self.unit_of_measurement)
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""

View File

@ -7,12 +7,17 @@ https://home-assistant.io/components/thermostat.homematic/
import logging
import socket
from xmlrpc.client import ServerProxy
from xmlrpc.client import Error
from collections import namedtuple
from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import TEMP_CELCIUS
from homeassistant.helpers.temperature import convert
REQUIREMENTS = []
_LOGGER = logging.getLogger(__name__)
CONF_ADDRESS = 'address'
CONF_DEVICES = 'devices'
CONF_ID = 'id'
@ -20,29 +25,59 @@ PROPERTY_SET_TEMPERATURE = 'SET_TEMPERATURE'
PROPERTY_VALVE_STATE = 'VALVE_STATE'
PROPERTY_ACTUAL_TEMPERATURE = 'ACTUAL_TEMPERATURE'
PROPERTY_BATTERY_STATE = 'BATTERY_STATE'
PROPERTY_LOWBAT = 'LOWBAT'
PROPERTY_CONTROL_MODE = 'CONTROL_MODE'
PROPERTY_BURST_MODE = 'BURST_RX'
TYPE_HM_THERMOSTAT = 'HOMEMATIC_THERMOSTAT'
TYPE_HM_WALLTHERMOSTAT = 'HOMEMATIC_WALLTHERMOSTAT'
TYPE_MAX_THERMOSTAT = 'MAX_THERMOSTAT'
_LOGGER = logging.getLogger(__name__)
HomematicConfig = namedtuple('HomematicConfig',
['device_type',
'platform_type',
'channel',
'maint_channel'])
HM_TYPE_MAPPING = {
'HM-CC-RT-DN': HomematicConfig('HM-CC-RT-DN',
TYPE_HM_THERMOSTAT,
4, 4),
'HM-CC-RT-DN-BoM': HomematicConfig('HM-CC-RT-DN-BoM',
TYPE_HM_THERMOSTAT,
4, 4),
'HM-TC-IT-WM-W-EU': HomematicConfig('HM-TC-IT-WM-W-EU',
TYPE_HM_WALLTHERMOSTAT,
2, 2),
'BC-RT-TRX-CyG': HomematicConfig('BC-RT-TRX-CyG',
TYPE_MAX_THERMOSTAT,
1, 0),
'BC-RT-TRX-CyG-2': HomematicConfig('BC-RT-TRX-CyG-2',
TYPE_MAX_THERMOSTAT,
1, 0),
'BC-RT-TRX-CyG-3': HomematicConfig('BC-RT-TRX-CyG-3',
TYPE_MAX_THERMOSTAT,
1, 0)
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Homematic thermostat."""
devices = []
try:
homegear = ServerProxy(config[CONF_ADDRESS])
address = config[CONF_ADDRESS]
homegear = ServerProxy(address)
for name, device_cfg in config[CONF_DEVICES].items():
# get device description to detect the type
device_type = homegear.getDeviceDescription(
device_cfg[CONF_ID] + ':-1')['TYPE']
if device_type in ['HM-CC-RT-DN', 'HM-CC-RT-DN-BoM']:
devices.append(HomematicThermostat(homegear,
device_cfg[CONF_ID],
name, 4))
elif device_type == 'HM-TC-IT-WM-W-EU':
devices.append(HomematicThermostat(homegear,
device_cfg[CONF_ID],
name, 2))
if device_type in HM_TYPE_MAPPING.keys():
devices.append(HomematicThermostat(
HM_TYPE_MAPPING[device_type],
address,
device_cfg[CONF_ID],
name))
else:
raise ValueError(
"Device Type '{}' currently not supported".format(
@ -60,14 +95,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class HomematicThermostat(ThermostatDevice):
"""Representation of a Homematic thermostat."""
def __init__(self, device, _id, name, channel):
def __init__(self, hm_config, address, _id, name):
"""Initialize the thermostat."""
self.device = device
self._hm_config = hm_config
self.address = address
self._id = _id
self._channel = channel
self._name = name
self._full_device_name = '{}:{}'.format(self._id, self._channel)
self._full_device_name = '{}:{}'.format(self._id,
self._hm_config.channel)
self._maint_device_name = '{}:{}'.format(self._id,
self._hm_config.maint_channel)
self._current_temperature = None
self._target_temperature = None
self._valve = None
@ -97,9 +134,10 @@ class HomematicThermostat(ThermostatDevice):
def set_temperature(self, temperature):
"""Set new target temperature."""
self.device.setValue(self._full_device_name,
PROPERTY_SET_TEMPERATURE,
temperature)
device = ServerProxy(self.address)
device.setValue(self._full_device_name,
PROPERTY_SET_TEMPERATURE,
temperature)
@property
def device_state_attributes(self):
@ -108,21 +146,49 @@ class HomematicThermostat(ThermostatDevice):
"battery": self._battery,
"mode": self._mode}
@property
def min_temp(self):
"""Return the minimum temperature - 4.5 means off."""
return convert(4.5, TEMP_CELCIUS, self.unit_of_measurement)
@property
def max_temp(self):
"""Return the maximum temperature - 30.5 means on."""
return convert(30.5, TEMP_CELCIUS, self.unit_of_measurement)
def update(self):
"""Update the data from the thermostat."""
try:
self._current_temperature = self.device.getValue(
device = ServerProxy(self.address)
self._current_temperature = device.getValue(
self._full_device_name,
PROPERTY_ACTUAL_TEMPERATURE)
self._target_temperature = self.device.getValue(
self._target_temperature = device.getValue(
self._full_device_name,
PROPERTY_SET_TEMPERATURE)
self._valve = self.device.getValue(self._full_device_name,
PROPERTY_VALVE_STATE)
self._battery = self.device.getValue(self._full_device_name,
PROPERTY_BATTERY_STATE)
self._mode = self.device.getValue(self._full_device_name,
PROPERTY_CONTROL_MODE)
except socket.error:
self._valve = device.getValue(
self._full_device_name,
PROPERTY_VALVE_STATE)
self._mode = device.getValue(
self._full_device_name,
PROPERTY_CONTROL_MODE)
if self._hm_config.platform_type in [TYPE_HM_THERMOSTAT,
TYPE_HM_WALLTHERMOSTAT]:
self._battery = device.getValue(self._maint_device_name,
PROPERTY_BATTERY_STATE)
elif self._hm_config.platform_type == TYPE_MAX_THERMOSTAT:
# emulate homematic battery voltage,
# max reports lowbat if voltage < 2.2V
# while homematic battery_state should
# be between 1.5V and 4.6V
lowbat = device.getValue(self._maint_device_name,
PROPERTY_LOWBAT)
if lowbat:
self._battery = 1.5
else:
self._battery = 4.6
except Error:
_LOGGER.exception("Did not receive any temperature data from the "
"homematic API.")

View File

@ -70,7 +70,7 @@ class NestThermostat(ThermostatDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return round(self.device.temperature, 1)
return self.device.temperature
@property
def operation(self):
@ -102,21 +102,21 @@ class NestThermostat(ThermostatDevice):
else:
temp = target
return round(temp, 1)
return temp
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.device.mode == 'range':
return round(self.device.target[0], 1)
return round(self.target_temperature, 1)
return self.device.target[0]
return self.target_temperature
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.device.mode == 'range':
return round(self.device.target[1], 1)
return round(self.target_temperature, 1)
return self.device.target[1]
return self.target_temperature
@property
def is_away_mode_on(self):

View File

@ -80,7 +80,7 @@ class RadioThermostat(ThermostatDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return round(self._current_temperature, 1)
return self._current_temperature
@property
def operation(self):
@ -90,7 +90,7 @@ class RadioThermostat(ThermostatDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return round(self._target_temperature, 1)
return self._target_temperature
def update(self):
"""Update the data from the thermostat."""

View File

@ -20,6 +20,11 @@ ENTITY_ID = 'updater.updater'
def setup(hass, config):
"""Setup the updater component."""
if 'dev' in CURRENT_VERSION:
# This component only makes sense in release versions
_LOGGER.warning('Updater not supported in development version')
return False
def check_newest_version(_=None):
"""Check if a new version is available and report if one is."""
newest = get_newest_version()

View File

@ -109,7 +109,7 @@ def setup(hass, base_config):
(LIGHT, DISCOVER_LIGHTS),
(SWITCH, DISCOVER_SWITCHES))):
component = get_component(comp_name)
bootstrap.setup_component(hass, component.DOMAIN, config)
bootstrap.setup_component(hass, component.DOMAIN, base_config)
hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
{ATTR_SERVICE: discovery,
ATTR_DISCOVERED: {}})

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.components import discovery
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['pywemo==0.3.24']
REQUIREMENTS = ['pywemo==0.4.1']
DOMAIN = 'wemo'
DISCOVER_LIGHTS = 'wemo.light'

View File

@ -1,7 +1,7 @@
# coding: utf-8
"""Constants used by Home Assistant components."""
__version__ = "0.17.0.dev0"
__version__ = "0.18.0.dev0"
REQUIRED_PYTHON_VER = (3, 4)
PLATFORM_FORMAT = '{}.{}'
@ -23,12 +23,14 @@ CONF_CUSTOMIZE = "customize"
CONF_PLATFORM = "platform"
CONF_HOST = "host"
CONF_PORT = "port"
CONF_HOSTS = "hosts"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
CONF_API_KEY = "api_key"
CONF_ACCESS_TOKEN = "access_token"
CONF_FILENAME = "filename"
CONF_OPTIMISTIC = 'optimistic'
CONF_SCAN_INTERVAL = "scan_interval"
CONF_VALUE_TEMPLATE = "value_template"

View File

@ -16,6 +16,7 @@ from homeassistant.util import slugify
# Home Assistant types
byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255))
small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1))
positive_int = vol.All(vol.Coerce(int), vol.Range(min=0))
latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90),
msg='invalid latitude')
longitude = vol.All(vol.Coerce(float), vol.Range(min=-180, max=180),
@ -34,6 +35,11 @@ def boolean(value):
return bool(value)
def isfile(value):
"""Validate that the value is an existing file."""
return vol.IsFile('not a file')(value)
def ensure_list(value):
"""Wrap value in list if it is not one."""
return value if isinstance(value, list) else [value]
@ -41,6 +47,7 @@ def ensure_list(value):
def entity_id(value):
"""Validate Entity ID."""
value = string(value).lower()
if valid_entity_id(value):
return value
raise vol.Invalid('Entity ID {} does not match format <domain>.<object_id>'
@ -52,10 +59,7 @@ def entity_ids(value):
if isinstance(value, str):
value = [ent_id.strip() for ent_id in value.split(',')]
for ent_id in value:
entity_id(ent_id)
return value
return [entity_id(ent_id) for ent_id in value]
def icon(value):
@ -188,7 +192,6 @@ def key_dependency(key, dependency):
"""Test dependencies."""
if not isinstance(value, dict):
raise vol.Invalid('key dependencies require a dict')
print(key, value)
if key in value and dependency not in value:
raise vol.Invalid('dependency violation - key "{}" requires '
'key "{}" to exist'.format(key, dependency))

View File

@ -1,7 +1,7 @@
"""YAML utility functions."""
import logging
import os
from collections import Counter, OrderedDict
from collections import OrderedDict
import yaml
@ -10,17 +10,28 @@ from homeassistant.exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-ancestors
class SafeLineLoader(yaml.SafeLoader):
"""Loader class that keeps track of line numbers."""
def compose_node(self, parent, index):
"""Annotate a node with the first line it was seen."""
last_line = self.line
node = super(SafeLineLoader, self).compose_node(parent, index)
node.__line__ = last_line + 1
return node
def load_yaml(fname):
"""Load a YAML file."""
try:
with open(fname, encoding='utf-8') as conf_file:
# If configuration file is empty YAML returns None
# We convert that to an empty dict
return yaml.safe_load(conf_file) or {}
except yaml.YAMLError:
error = 'Error reading YAML configuration file {}'.format(fname)
_LOGGER.exception(error)
raise HomeAssistantError(error)
return yaml.load(conf_file, Loader=SafeLineLoader) or {}
except yaml.YAMLError as exc:
_LOGGER.error(exc)
raise HomeAssistantError(exc)
def _include_yaml(loader, node):
@ -37,11 +48,21 @@ def _ordered_dict(loader, node):
"""Load YAML mappings into an ordered dict to preserve key order."""
loader.flatten_mapping(node)
nodes = loader.construct_pairs(node)
dups = [k for k, v in Counter(k for k, _ in nodes).items() if v > 1]
if dups:
raise yaml.YAMLError("ERROR: duplicate keys: {}".format(dups))
return OrderedDict(nodes)
seen = {}
for (key, _), (node, _) in zip(nodes, node.value):
line = getattr(node, '__line__', 'unknown')
if key in seen:
fname = getattr(loader.stream, 'name', '')
first_mark = yaml.Mark(fname, 0, seen[key], -1, None, None)
second_mark = yaml.Mark(fname, 0, line, -1, None, None)
raise yaml.MarkedYAMLError(
context="duplicate key: \"{}\"".format(key),
context_mark=first_mark, problem_mark=second_mark,
)
seen[key] = line
return OrderedDict(nodes)
yaml.SafeLoader.add_constructor('!include', _include_yaml)
yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,

View File

@ -26,7 +26,7 @@ TwitterAPI==2.3.6
apcaccess==0.0.4
# homeassistant.components.sun
astral==0.9
astral==1.0
# homeassistant.components.light.blinksticklight
blinkstick==1.1.7
@ -132,7 +132,7 @@ messagebird==1.1.1
mficlient==0.3.0
# homeassistant.components.discovery
netdisco==0.6.1
netdisco==0.6.2
# homeassistant.components.sensor.neurio_energy
neurio==0.2.10
@ -189,7 +189,7 @@ pyicloud==0.7.2
pyloopenergy==0.0.7
# homeassistant.components.device_tracker.netgear
pynetgear==0.3.2
pynetgear==0.3.3
# homeassistant.components.alarm_control_panel.nx584
# homeassistant.components.binary_sensor.nx584
@ -241,7 +241,7 @@ pyuserinput==0.1.9
pyvera==0.2.10
# homeassistant.components.wemo
pywemo==0.3.24
pywemo==0.4.1
# homeassistant.components.thermostat.radiotherm
radiotherm==1.2

View File

@ -1,5 +1,7 @@
# This is a simple service file for systems with systemd to tun HA as user.
#
# For details please check https://home-assistant.io/getting-started/autostart/
#
[Unit]
Description=Home Assistant for %i
After=network.target
@ -9,6 +11,7 @@ Type=simple
User=%i
# Enable the following line if you get network-related HA errors during boot
#ExecStartPre=/usr/bin/sleep 60
# Use `whereis hass` to determine the path of hass
ExecStart=/usr/bin/hass
SendSIGKILL=no

View File

@ -4,11 +4,13 @@ from datetime import timedelta
from unittest import mock
from homeassistant import core as ha, loader
from homeassistant.bootstrap import _setup_component
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.util.dt as date_util
from homeassistant.const import (
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED,
EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE,
ATTR_DISCOVERED, SERVER_PORT)
ATTR_DISCOVERED, SERVER_PORT, TEMP_CELCIUS)
from homeassistant.components import sun, mqtt
_TEST_INSTANCE_PORT = SERVER_PORT
@ -33,6 +35,8 @@ def get_test_home_assistant(num_threads=None):
hass.config.config_dir = get_test_config_dir()
hass.config.latitude = 32.87336
hass.config.longitude = -117.22743
hass.config.time_zone = date_util.get_time_zone('US/Pacific')
hass.config.temperature_unit = TEMP_CELCIUS
if 'custom_components.test' not in loader.AVAILABLE_COMPONENTS:
loader.prepare(hass)
@ -123,12 +127,11 @@ def mock_http_component(hass):
@mock.patch('homeassistant.components.mqtt.MQTT')
def mock_mqtt_component(hass, mock_mqtt):
"""Mock the MQTT component."""
mqtt.setup(hass, {
_setup_component(hass, mqtt.DOMAIN, {
mqtt.DOMAIN: {
mqtt.CONF_BROKER: 'mock-broker',
}
})
hass.config.components.append(mqtt.DOMAIN)
return mock_mqtt

View File

@ -1,7 +1,7 @@
"""The tests the MQTT alarm control panel component."""
import unittest
from unittest.mock import patch
from homeassistant.bootstrap import _setup_component
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
@ -25,37 +25,37 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
"""Stop down stuff we started."""
self.hass.stop()
@patch('homeassistant.components.alarm_control_panel.mqtt._LOGGER.error')
def test_fail_setup_without_state_topic(self, mock_error):
def test_fail_setup_without_state_topic(self):
"""Test for failing with no state topic."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert not _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'command_topic': 'alarm/command'
}}))
}
})
self.assertEqual(1, mock_error.call_count)
@patch('homeassistant.components.alarm_control_panel.mqtt._LOGGER.error')
def test_fail_setup_without_command_topic(self, mock_error):
def test_fail_setup_without_command_topic(self):
"""Test failing with no command topic."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert not _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'state_topic': 'alarm/state'
}}))
self.assertEqual(1, mock_error.call_count)
}
})
def test_update_state_via_state_topic(self):
"""Test updating with via state topic."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
}
})
entity_id = 'alarm_control_panel.test'
@ -71,13 +71,15 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
def test_ignore_update_state_if_unknown_via_state_topic(self):
"""Test ignoring updates via state topic."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
}
})
entity_id = 'alarm_control_panel.test'
@ -90,13 +92,15 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
def test_arm_home_publishes_mqtt(self):
"""Test publishing of MQTT messages while armed."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
}
})
alarm_control_panel.alarm_arm_home(self.hass)
self.hass.pool.block_till_done()
@ -105,14 +109,16 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
def test_arm_home_not_publishes_mqtt_with_invalid_code(self):
"""Test not publishing of MQTT messages with invalid code."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
'code': '1234'
}}))
}
})
call_count = self.mock_publish.call_count
alarm_control_panel.alarm_arm_home(self.hass, 'abcd')
@ -121,13 +127,15 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
def test_arm_away_publishes_mqtt(self):
"""Test publishing of MQTT messages while armed."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
}
})
alarm_control_panel.alarm_arm_away(self.hass)
self.hass.pool.block_till_done()
@ -136,14 +144,16 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
def test_arm_away_not_publishes_mqtt_with_invalid_code(self):
"""Test not publishing of MQTT messages with invalid code."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
'code': '1234'
}}))
}
})
call_count = self.mock_publish.call_count
alarm_control_panel.alarm_arm_away(self.hass, 'abcd')
@ -152,13 +162,15 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
def test_disarm_publishes_mqtt(self):
"""Test publishing of MQTT messages while disarmed."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
}
})
alarm_control_panel.alarm_disarm(self.hass)
self.hass.pool.block_till_done()
@ -167,14 +179,16 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
def test_disarm_not_publishes_mqtt_with_invalid_code(self):
"""Test not publishing of MQTT messages with invalid code."""
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
'code': '1234'
}}))
}
})
call_count = self.mock_publish.call_count
alarm_control_panel.alarm_disarm(self.hass, 'abcd')

View File

@ -1,6 +1,7 @@
"""The tests for the MQTT binary sensor platform."""
import unittest
from homeassistant.bootstrap import _setup_component
import homeassistant.components.binary_sensor as binary_sensor
from tests.common import mock_mqtt_component, fire_mqtt_message
from homeassistant.const import (STATE_OFF, STATE_ON)
@ -22,15 +23,16 @@ class TestSensorMQTT(unittest.TestCase):
def test_setting_sensor_value_via_mqtt_message(self):
"""Test the setting of the value via MQTT."""
self.assertTrue(binary_sensor.setup(self.hass, {
'binary_sensor': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, binary_sensor.DOMAIN, {
binary_sensor.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test-topic',
'payload_on': 'ON',
'payload_off': 'OFF',
}
}))
})
state = self.hass.states.get('binary_sensor.test')
self.assertEqual(STATE_OFF, state.state)
@ -47,28 +49,30 @@ class TestSensorMQTT(unittest.TestCase):
def test_valid_sensor_class(self):
"""Test the setting of a valid sensor class."""
self.assertTrue(binary_sensor.setup(self.hass, {
'binary_sensor': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, binary_sensor.DOMAIN, {
binary_sensor.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'sensor_class': 'motion',
'state_topic': 'test-topic',
}
}))
})
state = self.hass.states.get('binary_sensor.test')
self.assertEqual('motion', state.attributes.get('sensor_class'))
def test_invalid_sensor_class(self):
"""Test the setting of an invalid sensor class."""
self.assertTrue(binary_sensor.setup(self.hass, {
'binary_sensor': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, binary_sensor.DOMAIN, {
binary_sensor.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'sensor_class': 'abc123',
'state_topic': 'test-topic',
}
}))
})
state = self.hass.states.get('binary_sensor.test')
self.assertIsNone(state.attributes.get('sensor_class'))

View File

@ -197,3 +197,16 @@ class TestComponentsDeviceTracker(unittest.TestCase):
mock_see.assert_called_once_with(
mac=mac, dev_id=dev_id, host_name=host_name,
location_name=location_name, gps=gps)
def test_not_write_duplicate_yaml_keys(self):
"""Test that the device tracker will not generate invalid YAML."""
self.assertTrue(device_tracker.setup(self.hass, {}))
device_tracker.see(self.hass, 'mac_1', host_name='hello')
device_tracker.see(self.hass, 'mac_2', host_name='hello')
self.hass.pool.block_till_done()
config = device_tracker.load_config(self.yaml_devices, self.hass,
timedelta(seconds=0), 0)
assert len(config) == 2

View File

@ -2,6 +2,7 @@
import unittest
import os
from homeassistant.bootstrap import _setup_component
from homeassistant.components import device_tracker
from homeassistant.const import CONF_PLATFORM
@ -31,11 +32,13 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase):
topic = '/location/paulus'
location = 'work'
self.assertTrue(device_tracker.setup(self.hass, {
self.hass.config.components = ['mqtt', 'zone']
assert _setup_component(self.hass, device_tracker.DOMAIN, {
device_tracker.DOMAIN: {
CONF_PLATFORM: 'mqtt',
'devices': {dev_id: topic}
}}))
}
})
fire_mqtt_message(self.hass, topic, location)
self.hass.pool.block_till_done()
self.assertEqual(location, self.hass.states.get(enttiy_id).state)

View File

@ -0,0 +1,131 @@
"""The tests for the MQTT Garge door platform."""
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.const import STATE_OPEN, STATE_CLOSED, ATTR_ASSUMED_STATE
import homeassistant.components.garage_door as garage_door
from tests.common import (
mock_mqtt_component, fire_mqtt_message, get_test_home_assistant)
class TestGarageDoorMQTT(unittest.TestCase):
"""Test the MQTT Garage door."""
def setUp(self): # pylint: disable=invalid-name
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
def tearDown(self): # pylint: disable=invalid-name
""""Stop everything that was started."""
self.hass.stop()
def test_fail_setup_if_no_command_topic(self):
"""Test if command fails with command topic."""
self.hass.config.components = ['mqtt']
assert not _setup_component(self.hass, garage_door.DOMAIN, {
garage_door.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': '/home/garage_door/door'
}
})
self.assertIsNone(self.hass.states.get('garage_door.test'))
def test_controlling_state_via_topic(self):
"""Test the controlling state via topic."""
assert _setup_component(self.hass, garage_door.DOMAIN, {
garage_door.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'state_open': 1,
'state_closed': 0,
'service_open': 1,
'service_close': 0
}
})
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_CLOSED, state.state)
self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE))
fire_mqtt_message(self.hass, 'state-topic', '1')
self.hass.pool.block_till_done()
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_OPEN, state.state)
fire_mqtt_message(self.hass, 'state-topic', '0')
self.hass.pool.block_till_done()
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_CLOSED, state.state)
def test_sending_mqtt_commands_and_optimistic(self):
"""Test the sending MQTT commands in optimistic mode."""
assert _setup_component(self.hass, garage_door.DOMAIN, {
garage_door.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'command_topic': 'command-topic',
'state_open': 'beer state open',
'state_closed': 'beer state closed',
'service_open': 'beer open',
'service_close': 'beer close',
'qos': '2'
}
})
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_CLOSED, state.state)
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))
garage_door.open_door(self.hass, 'garage_door.test')
self.hass.pool.block_till_done()
self.assertEqual(('command-topic', 'beer open', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_OPEN, state.state)
garage_door.close_door(self.hass, 'garage_door.test')
self.hass.pool.block_till_done()
self.assertEqual(('command-topic', 'beer close', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_CLOSED, state.state)
def test_controlling_state_via_topic_and_json_message(self):
"""Test the controlling state via topic and JSON message."""
assert _setup_component(self.hass, garage_door.DOMAIN, {
garage_door.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'state_open': 'beer open',
'state_closed': 'beer closed',
'service_open': 'beer service open',
'service_close': 'beer service close',
'value_template': '{{ value_json.val }}'
}
})
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_CLOSED, state.state)
fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer open"}')
self.hass.pool.block_till_done()
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_OPEN, state.state)
fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer closed"}')
self.hass.pool.block_till_done()
state = self.hass.states.get('garage_door.test')
self.assertEqual(STATE_CLOSED, state.state)

View File

@ -58,6 +58,7 @@ light:
"""
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE
import homeassistant.components.light as light
from tests.common import (
@ -78,24 +79,26 @@ class TestLightMQTT(unittest.TestCase):
def test_fail_setup_if_no_command_topic(self):
"""Test if command fails with command topic."""
self.assertTrue(light.setup(self.hass, {
'light': {
self.hass.config.components = ['mqtt']
assert not _setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
}
}))
})
self.assertIsNone(self.hass.states.get('light.test'))
def test_no_color_or_brightness_if_no_topics(self):
"""Test if there is no color and brightness if no topic."""
self.assertTrue(light.setup(self.hass, {
'light': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test_light_rgb/status',
'command_topic': 'test_light_rgb/set',
}
}))
})
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
@ -112,8 +115,9 @@ class TestLightMQTT(unittest.TestCase):
def test_controlling_state_via_topic(self):
"""Test the controlling of the state via topic."""
self.assertTrue(light.setup(self.hass, {
'light': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test_light_rgb/status',
@ -126,7 +130,7 @@ class TestLightMQTT(unittest.TestCase):
'payload_on': 1,
'payload_off': 0
}
}))
})
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
@ -172,8 +176,9 @@ class TestLightMQTT(unittest.TestCase):
def test_controlling_scale(self):
"""Test the controlling scale."""
self.assertTrue(light.setup(self.hass, {
'light': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test_scale/status',
@ -185,7 +190,7 @@ class TestLightMQTT(unittest.TestCase):
'payload_on': 'on',
'payload_off': 'off'
}
}))
})
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
@ -218,8 +223,9 @@ class TestLightMQTT(unittest.TestCase):
def test_controlling_state_via_topic_with_templates(self):
"""Test the setting og the state with a template."""
self.assertTrue(light.setup(self.hass, {
'light': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test_light_rgb/status',
@ -230,7 +236,7 @@ class TestLightMQTT(unittest.TestCase):
'brightness_value_template': '{{ value_json.hello }}',
'rgb_value_template': '{{ value_json.hello | join(",") }}',
}
}))
})
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
@ -252,8 +258,9 @@ class TestLightMQTT(unittest.TestCase):
def test_sending_mqtt_commands_and_optimistic(self):
"""Test the sending of command in optimistic mode."""
self.assertTrue(light.setup(self.hass, {
'light': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'command_topic': 'test_light_rgb/set',
@ -263,7 +270,7 @@ class TestLightMQTT(unittest.TestCase):
'payload_on': 'on',
'payload_off': 'off'
}
}))
})
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
@ -310,15 +317,16 @@ class TestLightMQTT(unittest.TestCase):
def test_show_brightness_if_only_command_topic(self):
"""Test the brightness if only a command topic is present."""
self.assertTrue(light.setup(self.hass, {
'light': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'brightness_command_topic': 'test_light_rgb/brightness/set',
'command_topic': 'test_light_rgb/set',
'state_topic': 'test_light_rgb/status',
}
}))
})
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)

View File

@ -1,9 +1,8 @@
"""The tests for the Rfxtrx light platform."""
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.components import rfxtrx as rfxtrx_core
from homeassistant.components.light import rfxtrx
from unittest.mock import patch
from tests.common import get_test_home_assistant
@ -14,6 +13,7 @@ class TestLightRfxtrx(unittest.TestCase):
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant(0)
self.hass.config.components = ['rfxtrx']
def tearDown(self):
"""Stop everything that was started."""
@ -23,40 +23,60 @@ class TestLightRfxtrx(unittest.TestCase):
rfxtrx_core.RFXOBJECT.close_connection()
self.hass.stop()
def test_valid_config(self):
"""Test configuration."""
self.assertTrue(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'automatic_add': True,
'devices':
{'213c7f216': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51',
rfxtrx_core.ATTR_FIREEVENT: True}}}}))
self.assertTrue(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'automatic_add': True,
'devices':
{'213c7f216': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51',
'signal_repetitions': 3}}}}))
def test_invalid_config(self):
"""Test configuration."""
self.assertFalse(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'automatic_add': True,
'invalid_key': 'afda',
'devices':
{'213c7f216': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51',
rfxtrx_core.ATTR_FIREEVENT: True}}}}))
def test_default_config(self):
"""Test with 0 lights."""
config = {'devices': {}}
devices = []
"""Test with 0 switches."""
self.assertTrue(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'devices': {}}}))
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(0, len(devices))
def test_one_sensor(self):
def test_one_light(self):
"""Test with 1 light."""
config = {'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51'}}}
self.assertTrue(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51'}}}}))
import RFXtrx as rfxtrxmod
rfxtrx_core.RFXOBJECT =\
rfxtrxmod.Core("", transport_protocol=rfxtrxmod.DummyTransport)
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(1, len(devices))
entity = devices[0]
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
entity = rfxtrx_core.RFX_DEVICES['123efab1']
self.assertEqual('Test', entity.name)
self.assertEqual('off', entity.state)
self.assertTrue(entity.assumed_state)
@ -65,62 +85,47 @@ class TestLightRfxtrx(unittest.TestCase):
self.assertFalse(entity.should_poll)
self.assertFalse(entity.is_on)
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
entity.turn_on()
entity.turn_on()
self.assertTrue(entity.is_on)
self.assertEqual(entity.brightness, 255)
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
entity.turn_off()
entity.turn_off()
self.assertFalse(entity.is_on)
self.assertEqual(entity.brightness, 0)
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
entity.turn_on(brightness=100)
entity.turn_on(brightness=100)
self.assertTrue(entity.is_on)
self.assertEqual(entity.brightness, 100)
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
entity.turn_on(brightness=10)
entity.turn_on(brightness=10)
self.assertTrue(entity.is_on)
self.assertEqual(entity.brightness, 10)
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
entity.turn_on(brightness=255)
entity.turn_on(brightness=255)
self.assertTrue(entity.is_on)
self.assertEqual(entity.brightness, 255)
def test_several_lights(self):
"""Test with 3 lights."""
config = {'signal_repetitions': 3,
'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f230010f71'},
'118cdea2': {
'name': 'Bath',
'packetid': '0b1100100118cdea02010f70'},
'213c7f216': {
'name': 'Living',
'packetid': '2b1121cd1213c7f211111f71'}}}
devices = []
self.assertTrue(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'signal_repetitions': 3,
'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f230010f71'},
'118cdea2': {
'name': 'Bath',
'packetid': '0b1100100118cdea02010f70'},
'213c7f216': {
'name': 'Living',
'packetid': '0b1100100118cdea02010f70'}}}}))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(3, len(devices))
self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES))
device_num = 0
for entity in devices:
for id in rfxtrx_core.RFX_DEVICES:
entity = rfxtrx_core.RFX_DEVICES[id]
self.assertEqual(entity.signal_repetitions, 3)
if entity.name == 'Living':
device_num = device_num + 1
@ -139,47 +144,33 @@ class TestLightRfxtrx(unittest.TestCase):
def test_discover_light(self):
"""Test with discovery of lights."""
config = {'automatic_add': True, 'devices': {}}
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertTrue(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'automatic_add': True,
'devices': {}}}))
event = rfxtrx_core.get_rfx_object('0b11009e00e6116202020070')
event.data = bytearray(b'\x0b\x11\x00\x9e\x00\xe6\x11b\x02\x02\x00p')
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[0]
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = rfxtrx_core.RFX_DEVICES['0e611622']
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(1, len(devices))
self.assertEqual('<Entity 0e611622 : 0b11009e00e6116202020070: on>',
entity.__str__())
event = rfxtrx_core.get_rfx_object('0b11009e00e6116201010070')
event.data = bytearray(b'\x0b\x11\x00\x9e\x00\xe6\x11b\x01\x01\x00p')
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(1, len(devices))
event = rfxtrx_core.get_rfx_object('0b1100120118cdea02020070')
event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18,
0xcd, 0xea, 0x02, 0x02, 0x00, 0x70])
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[1]
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = rfxtrx_core.RFX_DEVICES['118cdea2']
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
self.assertEqual('<Entity 118cdea2 : 0b1100120118cdea02020070: on>',
entity.__str__())
@ -188,75 +179,53 @@ class TestLightRfxtrx(unittest.TestCase):
event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
# trying to add a swicth
event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70')
event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18,
0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70])
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
def test_discover_light_noautoadd(self):
"""Test with discover of light when auto add is False."""
config = {'automatic_add': False, 'devices': {}}
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertTrue(_setup_component(self.hass, 'light', {
'light': {'platform': 'rfxtrx',
'automatic_add': False,
'devices': {}}}))
event = rfxtrx_core.get_rfx_object('0b1100120118cdea02020070')
event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18,
0xcd, 0xea, 0x02, 0x02, 0x00, 0x70])
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
event = rfxtrx_core.get_rfx_object('0b1100120118cdea02010070')
event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18,
0xcd, 0xea, 0x02, 0x01, 0x00, 0x70])
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
event = rfxtrx_core.get_rfx_object('0b1100120118cdea02020070')
event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18,
0xcd, 0xea, 0x02, 0x02, 0x00, 0x70])
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
# Trying to add a sensor
event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279')
event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
# Trying to add a switch
event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70')
event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18,
0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70])
with patch('homeassistant.components.light.' +
'rfxtrx.RfxtrxLight.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))

View File

@ -1,6 +1,7 @@
"""The tests for the MQTT lock platform."""
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.const import (STATE_LOCKED, STATE_UNLOCKED,
ATTR_ASSUMED_STATE)
import homeassistant.components.lock as lock
@ -22,8 +23,9 @@ class TestLockMQTT(unittest.TestCase):
def test_controlling_state_via_topic(self):
"""Test the controlling state via topic."""
self.assertTrue(lock.setup(self.hass, {
'lock': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, lock.DOMAIN, {
lock.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
@ -31,7 +33,7 @@ class TestLockMQTT(unittest.TestCase):
'payload_lock': 'LOCK',
'payload_unlock': 'UNLOCK'
}
}))
})
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_UNLOCKED, state.state)
@ -51,8 +53,9 @@ class TestLockMQTT(unittest.TestCase):
def test_sending_mqtt_commands_and_optimistic(self):
"""Test the sending MQTT commands in optimistic mode."""
self.assertTrue(lock.setup(self.hass, {
'lock': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, lock.DOMAIN, {
lock.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'command_topic': 'command-topic',
@ -60,7 +63,7 @@ class TestLockMQTT(unittest.TestCase):
'payload_unlock': 'UNLOCK',
'qos': 2
}
}))
})
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_UNLOCKED, state.state)
@ -84,8 +87,9 @@ class TestLockMQTT(unittest.TestCase):
def test_controlling_state_via_topic_and_json_message(self):
"""Test the controlling state via topic and JSON message."""
self.assertTrue(lock.setup(self.hass, {
'lock': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, lock.DOMAIN, {
lock.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
@ -94,7 +98,7 @@ class TestLockMQTT(unittest.TestCase):
'payload_unlock': 'UNLOCK',
'value_template': '{{ value_json.val }}'
}
}))
})
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_UNLOCKED, state.state)

View File

@ -22,7 +22,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
def test_source_select(self):
"""Test the input source service."""
entity_id = 'media_player.receiver'
entity_id = 'media_player.lounge_room'
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
state = self.hass.states.get(entity_id)

View File

@ -4,6 +4,9 @@ import unittest
from unittest import mock
import socket
import voluptuous as vol
from homeassistant.bootstrap import _setup_component
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (
EVENT_CALL_SERVICE, ATTR_DOMAIN, ATTR_SERVICE, EVENT_HOMEASSISTANT_START,
@ -48,9 +51,23 @@ class TestMQTT(unittest.TestCase):
"""Test for setup failure if connection to broker is missing."""
with mock.patch('homeassistant.components.mqtt.MQTT',
side_effect=socket.error()):
self.assertFalse(mqtt.setup(self.hass, {mqtt.DOMAIN: {
mqtt.CONF_BROKER: 'test-broker',
}}))
self.hass.config.components = []
assert not _setup_component(self.hass, mqtt.DOMAIN, {
mqtt.DOMAIN: {
mqtt.CONF_BROKER: 'test-broker',
}
})
def test_setup_protocol_validation(self):
"""Test for setup failure if connection to broker is missing."""
with mock.patch('paho.mqtt.client.Client'):
self.hass.config.components = []
assert _setup_component(self.hass, mqtt.DOMAIN, {
mqtt.DOMAIN: {
mqtt.CONF_BROKER: 'test-broker',
mqtt.CONF_PROTOCOL: 3.1,
}
})
def test_publish_calls_service(self):
"""Test the publishing of call to services."""
@ -211,12 +228,12 @@ class TestMQTTCallbacks(unittest.TestCase):
# mock_mqtt_component(self.hass)
with mock.patch('paho.mqtt.client.Client'):
mqtt.setup(self.hass, {
self.hass.config.components = []
assert _setup_component(self.hass, mqtt.DOMAIN, {
mqtt.DOMAIN: {
mqtt.CONF_BROKER: 'mock-broker',
}
})
self.hass.config.components.append(mqtt.DOMAIN)
def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started."""
@ -302,3 +319,7 @@ class TestMQTTCallbacks(unittest.TestCase):
self.assertEqual({'test/topic': 1}, mqtt.MQTT_CLIENT.topics)
self.assertEqual({}, mqtt.MQTT_CLIENT.progress)
def test_invalid_mqtt_topics(self):
self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, 'bad+topic')
self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'bad\0one')

View File

@ -1,6 +1,7 @@
"""The tests for the MQTT component embedded server."""
from unittest.mock import MagicMock, patch
from homeassistant.bootstrap import _setup_component
import homeassistant.components.mqtt as mqtt
from tests.common import get_test_home_assistant
@ -27,15 +28,16 @@ class TestMQTT:
password = 'super_secret'
self.hass.config.api = MagicMock(api_password=password)
assert mqtt.setup(self.hass, {})
assert _setup_component(self.hass, mqtt.DOMAIN, {})
assert mock_mqtt.called
assert mock_mqtt.mock_calls[0][1][5] == 'homeassistant'
assert mock_mqtt.mock_calls[0][1][6] == password
mock_mqtt.reset_mock()
self.hass.config.components = ['http']
self.hass.config.api = MagicMock(api_password=None)
assert mqtt.setup(self.hass, {})
assert _setup_component(self.hass, mqtt.DOMAIN, {})
assert mock_mqtt.called
assert mock_mqtt.mock_calls[0][1][5] is None
assert mock_mqtt.mock_calls[0][1][6] is None
@ -50,6 +52,6 @@ class TestMQTT:
mock_gather.side_effect = BrokerException
self.hass.config.api = MagicMock(api_password=None)
assert not mqtt.setup(self.hass, {
'mqtt': {'embedded': {}}
assert not _setup_component(self.hass, mqtt.DOMAIN, {
mqtt.DOMAIN: {mqtt.CONF_EMBEDDED: {}}
})

View File

@ -1,6 +1,7 @@
"""The tests for the MQTT roller shutter platform."""
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN
import homeassistant.components.rollershutter as rollershutter
from tests.common import mock_mqtt_component, fire_mqtt_message
@ -22,8 +23,9 @@ class TestRollershutterMQTT(unittest.TestCase):
def test_controlling_state_via_topic(self):
"""Test the controlling state via topic."""
self.assertTrue(rollershutter.setup(self.hass, {
'rollershutter': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, rollershutter.DOMAIN, {
rollershutter.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
@ -33,7 +35,7 @@ class TestRollershutterMQTT(unittest.TestCase):
'payload_down': 'DOWN',
'payload_stop': 'STOP'
}
}))
})
state = self.hass.states.get('rollershutter.test')
self.assertEqual(STATE_UNKNOWN, state.state)
@ -58,15 +60,16 @@ class TestRollershutterMQTT(unittest.TestCase):
def test_send_move_up_command(self):
"""Test the sending of move_up."""
self.assertTrue(rollershutter.setup(self.hass, {
'rollershutter': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, rollershutter.DOMAIN, {
rollershutter.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'qos': 2
}
}))
})
state = self.hass.states.get('rollershutter.test')
self.assertEqual(STATE_UNKNOWN, state.state)
@ -81,15 +84,16 @@ class TestRollershutterMQTT(unittest.TestCase):
def test_send_move_down_command(self):
"""Test the sending of move_down."""
self.assertTrue(rollershutter.setup(self.hass, {
'rollershutter': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, rollershutter.DOMAIN, {
rollershutter.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'qos': 2
}
}))
})
state = self.hass.states.get('rollershutter.test')
self.assertEqual(STATE_UNKNOWN, state.state)
@ -104,15 +108,16 @@ class TestRollershutterMQTT(unittest.TestCase):
def test_send_stop_command(self):
"""Test the sending of stop."""
self.assertTrue(rollershutter.setup(self.hass, {
'rollershutter': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, rollershutter.DOMAIN, {
rollershutter.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'qos': 2
}
}))
})
state = self.hass.states.get('rollershutter.test')
self.assertEqual(STATE_UNKNOWN, state.state)
@ -127,8 +132,9 @@ class TestRollershutterMQTT(unittest.TestCase):
def test_state_attributes_current_position(self):
"""Test the current position."""
self.assertTrue(rollershutter.setup(self.hass, {
'rollershutter': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, rollershutter.DOMAIN, {
rollershutter.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
@ -137,7 +143,7 @@ class TestRollershutterMQTT(unittest.TestCase):
'payload_down': 'DOWN',
'payload_stop': 'STOP'
}
}))
})
state_attributes_dict = self.hass.states.get(
'rollershutter.test').attributes

View File

@ -1,6 +1,7 @@
"""The tests for the MQTT sensor platform."""
import unittest
from homeassistant.bootstrap import _setup_component
import homeassistant.components.sensor as sensor
from tests.common import mock_mqtt_component, fire_mqtt_message
@ -21,14 +22,15 @@ class TestSensorMQTT(unittest.TestCase):
def test_setting_sensor_value_via_mqtt_message(self):
"""Test the setting of the value via MQTT."""
self.assertTrue(sensor.setup(self.hass, {
'sensor': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, sensor.DOMAIN, {
sensor.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test-topic',
'unit_of_measurement': 'fav unit'
}
}))
})
fire_mqtt_message(self.hass, 'test-topic', '100')
self.hass.pool.block_till_done()
@ -40,15 +42,16 @@ class TestSensorMQTT(unittest.TestCase):
def test_setting_sensor_value_via_mqtt_json_message(self):
"""Test the setting of the value via MQTT with JSON playload."""
self.assertTrue(sensor.setup(self.hass, {
'sensor': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, sensor.DOMAIN, {
sensor.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test-topic',
'unit_of_measurement': 'fav unit',
'value_template': '{{ value_json.val }}'
}
}))
})
fire_mqtt_message(self.hass, 'test-topic', '{ "val": "100" }')
self.hass.pool.block_till_done()

View File

@ -1,8 +1,7 @@
"""The tests for the Rfxtrx sensor platform."""
import unittest
from homeassistant.components.sensor import rfxtrx
from homeassistant.bootstrap import _setup_component
from homeassistant.components import rfxtrx as rfxtrx_core
from homeassistant.const import TEMP_CELCIUS
@ -15,6 +14,7 @@ class TestSensorRfxtrx(unittest.TestCase):
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant(0)
self.hass.config.components = ['rfxtrx']
def tearDown(self):
"""Stop everything that was started."""
@ -24,35 +24,25 @@ class TestSensorRfxtrx(unittest.TestCase):
def test_default_config(self):
"""Test with 0 sensor."""
config = {'devices': {}}
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(0, len(devices))
self.assertTrue(_setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'rfxtrx',
'devices':
{}}}))
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
def test_one_sensor(self):
"""Test with 1 sensor."""
config = {'devices':
{'sensor_0502': {
'name': 'Test',
'packetid': '0a52080705020095220269',
'data_type': 'Temperature'}}}
devices = []
self.assertTrue(_setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'rfxtrx',
'devices':
{'sensor_0502': {
'name': 'Test',
'packetid': '0a52080705020095220269',
'data_type': 'Temperature'}}}}))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(1, len(devices))
entity = devices[0]
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
print(rfxtrx_core.RFX_DEVICES)
entity = rfxtrx_core.RFX_DEVICES['sensor_0502']
self.assertEqual('Test', entity.name)
self.assertEqual(TEMP_CELCIUS, entity.unit_of_measurement)
self.assertEqual(14.9, entity.state)
@ -64,31 +54,25 @@ class TestSensorRfxtrx(unittest.TestCase):
def test_several_sensors(self):
"""Test with 3 sensors."""
config = {'devices':
{'sensor_0502': {
'name': 'Test',
'packetid': '0a52080705020095220269',
'data_type': 'Temperature'},
'sensor_0601': {
'name': 'Bath_Humidity',
'packetid': '0a520802060100ff0e0269',
'data_type': 'Humidity'},
'sensor_0601 2': {
'name': 'Bath',
'packetid': '0a520802060100ff0e0269'}}}
devices = []
self.assertTrue(_setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'rfxtrx',
'devices':
{'sensor_0502': {
'name': 'Test',
'packetid': '0a52080705020095220269',
'data_type': 'Temperature'},
'sensor_0601': {
'name': 'Bath_Humidity',
'packetid': '0a520802060100ff0e0269',
'data_type': 'Humidity'},
'sensor_0601 2': {
'name': 'Bath',
'packetid': '0a520802060100ff0e0269'}}}}))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(3, len(devices))
self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES))
device_num = 0
for entity in devices:
for id in rfxtrx_core.RFX_DEVICES:
entity = rfxtrx_core.RFX_DEVICES[id]
if entity.name == 'Bath_Humidity':
device_num = device_num + 1
self.assertEqual('%', entity.unit_of_measurement)
@ -125,23 +109,17 @@ class TestSensorRfxtrx(unittest.TestCase):
def test_discover_sensor(self):
"""Test with discovery of sensor."""
config = {'devices': {}}
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertTrue(_setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'rfxtrx',
'automatic_add': True,
'devices': {}}}))
event = rfxtrx_core.get_rfx_object('0a520801070100b81b0279')
event.data = bytearray(b'\nR\x08\x01\x07\x01\x00\xb8\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[0]
entity = rfxtrx_core.RFX_DEVICES['sensor_0701']
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(1, len(devices))
self.assertEqual({'Humidity status': 'normal',
'Temperature': 18.4,
'Rssi numeric': 7, 'Humidity': 27,
@ -153,14 +131,12 @@ class TestSensorRfxtrx(unittest.TestCase):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(1, len(devices))
event = rfxtrx_core.get_rfx_object('0a52080405020095240279')
event.data = bytearray(b'\nR\x08\x04\x05\x02\x00\x95$\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[1]
entity = rfxtrx_core.RFX_DEVICES['sensor_0502']
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
self.assertEqual({'Humidity status': 'normal',
'Temperature': 14.9,
'Rssi numeric': 7, 'Humidity': 36,
@ -173,9 +149,8 @@ class TestSensorRfxtrx(unittest.TestCase):
event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279')
event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[0]
entity = rfxtrx_core.RFX_DEVICES['sensor_0701']
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
self.assertEqual({'Humidity status': 'normal',
'Temperature': 17.9,
'Rssi numeric': 7, 'Humidity': 27,
@ -189,71 +164,56 @@ class TestSensorRfxtrx(unittest.TestCase):
event = rfxtrx_core.get_rfx_object('0b1100cd0213c7f210010f70')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
def test_discover_sensor_noautoadd(self):
"""Test with discover of sensor when auto add is False."""
config = {'automatic_add': False, 'devices': {}}
devices = []
self.assertTrue(_setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'rfxtrx',
'automatic_add': False,
'devices': {}}}))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
event = rfxtrx_core.get_rfx_object('0a520801070100b81b0279')
event.data = bytearray(b'\nR\x08\x01\x07\x01\x00\xb8\x1b\x02y')
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
event = rfxtrx_core.get_rfx_object('0a52080405020095240279')
event.data = bytearray(b'\nR\x08\x04\x05\x02\x00\x95$\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279')
event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
def test_update_of_sensors(self):
"""Test with 3 sensors."""
config = {'devices':
{'sensor_0502': {
'name': 'Test',
'packetid': '0a52080705020095220269',
'data_type': 'Temperature'},
'sensor_0601': {
'name': 'Bath_Humidity',
'packetid': '0a520802060100ff0e0269',
'data_type': 'Humidity'},
'sensor_0601 2': {
'name': 'Bath',
'packetid': '0a520802060100ff0e0269'}}}
devices = []
self.assertTrue(_setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'rfxtrx',
'devices':
{'sensor_0502': {
'name': 'Test',
'packetid': '0a52080705020095220269',
'data_type': 'Temperature'},
'sensor_0601': {
'name': 'Bath_Humidity',
'packetid': '0a520802060100ff0e0269',
'data_type': 'Humidity'},
'sensor_0601 2': {
'name': 'Bath',
'packetid': '0a520802060100ff0e0269'}}}}))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(3, len(devices))
self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES))
device_num = 0
for entity in devices:
for id in rfxtrx_core.RFX_DEVICES:
entity = rfxtrx_core.RFX_DEVICES[id]
if entity.name == 'Bath_Humidity':
device_num = device_num + 1
self.assertEqual('%', entity.unit_of_measurement)
@ -291,18 +251,17 @@ class TestSensorRfxtrx(unittest.TestCase):
event = rfxtrx_core.get_rfx_object('0a520802060101ff0f0269')
event.data = bytearray(b'\nR\x08\x01\x07\x01\x00\xb8\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[0]
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
event = rfxtrx_core.get_rfx_object('0a52080705020085220269')
event.data = bytearray(b'\nR\x08\x04\x05\x02\x00\x95$\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(3, len(devices))
self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES))
device_num = 0
for entity in devices:
for id in rfxtrx_core.RFX_DEVICES:
entity = rfxtrx_core.RFX_DEVICES[id]
if entity.name == 'Bath_Humidity':
device_num = device_num + 1
self.assertEqual('%', entity.unit_of_measurement)
@ -337,5 +296,4 @@ class TestSensorRfxtrx(unittest.TestCase):
self.assertEqual(3, device_num)
self.assertEqual(3, len(devices))
self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES))

View File

@ -4,9 +4,8 @@ from unittest.mock import patch
import pytest
import homeassistant.components.sensor as sensor
from homeassistant.bootstrap import _setup_component
import homeassistant.util.dt as dt_util
from tests.common import get_test_home_assistant
@ -32,12 +31,9 @@ class TestSensorYr:
return_value=betamax_session):
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=now):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'yr',
'elevation': 0,
}
})
assert _setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'yr',
'elevation': 0}})
state = self.hass.states.get('sensor.yr_symbol')
@ -53,19 +49,15 @@ class TestSensorYr:
return_value=betamax_session):
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=now):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'yr',
'elevation': 0,
'monitored_conditions': {
'pressure',
'windDirection',
'humidity',
'fog',
'windSpeed'
}
}
})
assert _setup_component(self.hass, 'sensor', {
'sensor': {'platform': 'yr',
'elevation': 0,
'monitored_conditions': [
'pressure',
'windDirection',
'humidity',
'fog',
'windSpeed']}})
state = self.hass.states.get('sensor.yr_pressure')
assert 'hPa' == state.attributes.get('unit_of_measurement')

View File

@ -1,6 +1,7 @@
"""The tests for the MQTT switch platform."""
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE
import homeassistant.components.switch as switch
from tests.common import (
@ -21,8 +22,9 @@ class TestSensorMQTT(unittest.TestCase):
def test_controlling_state_via_topic(self):
"""Test the controlling state via topic."""
self.assertTrue(switch.setup(self.hass, {
'switch': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
@ -30,7 +32,7 @@ class TestSensorMQTT(unittest.TestCase):
'payload_on': 1,
'payload_off': 0
}
}))
})
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
@ -50,8 +52,9 @@ class TestSensorMQTT(unittest.TestCase):
def test_sending_mqtt_commands_and_optimistic(self):
"""Test the sending MQTT commands in optimistic mode."""
self.assertTrue(switch.setup(self.hass, {
'switch': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'command_topic': 'command-topic',
@ -59,7 +62,7 @@ class TestSensorMQTT(unittest.TestCase):
'payload_off': 'beer off',
'qos': '2'
}
}))
})
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
@ -83,8 +86,9 @@ class TestSensorMQTT(unittest.TestCase):
def test_controlling_state_via_topic_and_json_message(self):
"""Test the controlling state via topic and JSON message."""
self.assertTrue(switch.setup(self.hass, {
'switch': {
self.hass.config.components = ['mqtt']
assert _setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
@ -93,7 +97,7 @@ class TestSensorMQTT(unittest.TestCase):
'payload_off': 'beer off',
'value_template': '{{ value_json.val }}'
}
}))
})
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)

View File

@ -1,9 +1,8 @@
"""The tests for the Rfxtrx switch platform."""
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.components import rfxtrx as rfxtrx_core
from homeassistant.components.switch import rfxtrx
from unittest.mock import patch
from tests.common import get_test_home_assistant
@ -14,6 +13,7 @@ class TestSwitchRfxtrx(unittest.TestCase):
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant(0)
self.hass.config.components = ['rfxtrx']
def tearDown(self):
"""Stop everything that was started."""
@ -23,40 +23,98 @@ class TestSwitchRfxtrx(unittest.TestCase):
rfxtrx_core.RFXOBJECT.close_connection()
self.hass.stop()
def test_valid_config(self):
"""Test configuration."""
self.assertTrue(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': True,
'devices':
{'213c7f216': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51',
rfxtrx_core.ATTR_FIREEVENT: True}
}}}))
def test_invalid_config1(self):
self.assertFalse(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': True,
'devices':
{'2FF7f216': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51',
'signal_repetitions': 3}
}}}))
def test_invalid_config2(self):
"""Test configuration."""
self.assertFalse(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': True,
'invalid_key': 'afda',
'devices':
{'213c7f216': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51',
rfxtrx_core.ATTR_FIREEVENT: True}
}}}))
def test_invalid_config3(self):
self.assertFalse(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': True,
'devices':
{'213c7f216': {
'name': 'Test',
'packetid': 'AA1100cd0213c7f210010f51',
rfxtrx_core.ATTR_FIREEVENT: True}
}}}))
def test_invalid_config4(self):
self.assertFalse(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': True,
'devices':
{'AA3c7f216': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51',
rfxtrx_core.ATTR_FIREEVENT: True}
}}}))
def test_invalid_config5(self):
"""Test configuration."""
self.assertFalse(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': True,
'devices':
{'213c7f216': {
'name': 'Test',
rfxtrx_core.ATTR_FIREEVENT: True}
}}}))
def test_default_config(self):
"""Test with 0 switches."""
config = {'devices': {}}
devices = []
self.assertTrue(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'devices':
{}}}))
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(0, len(devices))
def test_one_sensor(self):
def test_one_switch(self):
"""Test with 1 switch."""
config = {'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51'}}}
self.assertTrue(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f210010f51'}}}}))
import RFXtrx as rfxtrxmod
rfxtrx_core.RFXOBJECT =\
rfxtrxmod.Core("", transport_protocol=rfxtrxmod.DummyTransport)
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(1, len(devices))
entity = devices[0]
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
entity = rfxtrx_core.RFX_DEVICES['123efab1']
self.assertEqual('Test', entity.name)
self.assertEqual('off', entity.state)
self.assertTrue(entity.assumed_state)
@ -65,42 +123,31 @@ class TestSwitchRfxtrx(unittest.TestCase):
self.assertFalse(entity.should_poll)
self.assertFalse(entity.is_on)
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
entity.turn_on()
entity.turn_on()
self.assertTrue(entity.is_on)
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
entity.turn_off()
entity.turn_off()
self.assertFalse(entity.is_on)
def test_several_switchs(self):
def test_several_switches(self):
"""Test with 3 switches."""
config = {'signal_repetitions': 3,
'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f230010f71'},
'118cdea2': {
'name': 'Bath',
'packetid': '0b1100100118cdea02010f70'},
'213c7f216': {
'name': 'Living',
'packetid': '2b1121cd1213c7f211111f71'}}}
devices = []
self.assertTrue(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'signal_repetitions': 3,
'devices':
{'123efab1': {
'name': 'Test',
'packetid': '0b1100cd0213c7f230010f71'},
'118cdea2': {
'name': 'Bath',
'packetid': '0b1100100118cdea02010f70'},
'213c7f216': {
'name': 'Living',
'packetid': '0b1100100118cdea02010f70'}}}}))
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertEqual(3, len(devices))
self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES))
device_num = 0
for entity in devices:
for id in rfxtrx_core.RFX_DEVICES:
entity = rfxtrx_core.RFX_DEVICES[id]
self.assertEqual(entity.signal_repetitions, 3)
if entity.name == 'Living':
device_num = device_num + 1
@ -119,46 +166,31 @@ class TestSwitchRfxtrx(unittest.TestCase):
def test_discover_switch(self):
"""Test with discovery of switches."""
config = {'automatic_add': True, 'devices': {}}
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertTrue(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': True,
'devices': {}}}))
event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70')
event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18,
0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70])
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[0]
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = rfxtrx_core.RFX_DEVICES['118cdea2']
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(1, len(devices))
self.assertEqual('<Entity 118cdea2 : 0b1100100118cdea01010f70: on>',
entity.__str__())
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(1, len(devices))
event = rfxtrx_core.get_rfx_object('0b1100100118cdeb02010f70')
event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18,
0xcd, 0xea, 0x02, 0x00, 0x00, 0x70])
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = devices[1]
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
entity = rfxtrx_core.RFX_DEVICES['118cdeb2']
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
self.assertEqual('<Entity 118cdeb2 : 0b1100120118cdea02000070: on>',
entity.__str__())
@ -167,72 +199,47 @@ class TestSwitchRfxtrx(unittest.TestCase):
event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
# Trying to add a light
event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70')
event.data = bytearray([0x0b, 0x11, 0x11, 0x10, 0x01, 0x18,
0xcd, 0xea, 0x01, 0x02, 0x0f, 0x70])
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(2, len(devices))
def test_discover_switch_noautoadd(self):
"""Test with discovery of switch when auto add is False."""
config = {'automatic_add': False, 'devices': {}}
devices = []
def add_dev_callback(devs):
"""Add a callback to add devices."""
for dev in devs:
devices.append(dev)
rfxtrx.setup_platform(self.hass, config, add_dev_callback)
self.assertTrue(_setup_component(self.hass, 'switch', {
'switch': {'platform': 'rfxtrx',
'automatic_add': False,
'devices': {}}}))
event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70')
event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18,
0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70])
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
event = rfxtrx_core.get_rfx_object('0b1100100118cdeb02010f70')
event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18,
0xcd, 0xea, 0x02, 0x00, 0x00, 0x70])
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
# Trying to add a sensor
event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279')
event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y')
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))
# Trying to add a light
event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70')
event.data = bytearray([0x0b, 0x11, 0x11, 0x10, 0x01,
0x18, 0xcd, 0xea, 0x01, 0x02, 0x0f, 0x70])
with patch('homeassistant.components.switch.' +
'rfxtrx.RfxtrxSwitch.update_ha_state',
return_value=None):
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event)
self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES))
self.assertEqual(0, len(devices))

View File

@ -226,6 +226,7 @@ class TestComponentsGroup(unittest.TestCase):
'view': True,
},
'test_group': 'hello.world,sensor.happy',
'empty_group': {'name': 'Empty Group', 'entities': None},
}
})

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