Release 0.18 #1854

0.18
This commit is contained in:
Paulus Schoutsen 2016-04-20 18:43:54 -07:00
commit 8f18cb34d6
171 changed files with 2937 additions and 1020 deletions

View file

@ -5,18 +5,18 @@ omit =
homeassistant/__main__.py homeassistant/__main__.py
# omit pieces of code that rely on external devices being present # omit pieces of code that rely on external devices being present
homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/apcupsd.py
homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/*/apcupsd.py
homeassistant/components/arduino.py homeassistant/components/arduino.py
homeassistant/components/*/arduino.py homeassistant/components/*/arduino.py
homeassistant/components/apcupsd.py
homeassistant/components/*/apcupsd.py
homeassistant/components/bloomsky.py homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py homeassistant/components/*/bloomsky.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/insteon_hub.py homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py homeassistant/components/*/insteon_hub.py
@ -26,33 +26,6 @@ omit =
homeassistant/components/modbus.py homeassistant/components/modbus.py
homeassistant/components/*/modbus.py homeassistant/components/*/modbus.py
homeassistant/components/tellstick.py
homeassistant/components/*/tellstick.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/verisure.py
homeassistant/components/*/verisure.py
homeassistant/components/wemo.py
homeassistant/components/*/wemo.py
homeassistant/components/wink.py
homeassistant/components/*/wink.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/mysensors.py homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py homeassistant/components/*/mysensors.py
@ -65,6 +38,36 @@ omit =
homeassistant/components/scsgate.py homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py homeassistant/components/*/scsgate.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
homeassistant/components/tellstick.py
homeassistant/components/*/tellstick.py
homeassistant/components/*/thinkingcleaner.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
homeassistant/components/verisure.py
homeassistant/components/*/verisure.py
homeassistant/components/*/webostv.py
homeassistant/components/wemo.py
homeassistant/components/*/wemo.py
homeassistant/components/wink.py
homeassistant/components/*/wink.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/binary_sensor/arest.py homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/rest.py homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py homeassistant/components/browser.py
@ -76,6 +79,7 @@ omit =
homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/icloud.py
@ -89,6 +93,7 @@ omit =
homeassistant/components/device_tracker/ubus.py homeassistant/components/device_tracker/ubus.py
homeassistant/components/discovery.py homeassistant/components/discovery.py
homeassistant/components/downloader.py homeassistant/components/downloader.py
homeassistant/components/feedreader.py
homeassistant/components/garage_door/wink.py homeassistant/components/garage_door/wink.py
homeassistant/components/ifttt.py homeassistant/components/ifttt.py
homeassistant/components/keyboard.py homeassistant/components/keyboard.py
@ -103,17 +108,17 @@ omit =
homeassistant/components/media_player/itunes.py homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/plex.py homeassistant/components/media_player/plex.py
homeassistant/components/media_player/samsungtv.py homeassistant/components/media_player/samsungtv.py
homeassistant/components/media_player/snapcast.py homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/yamaha.py homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/free_mobile.py homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/googlevoice.py
homeassistant/components/notify/gntp.py homeassistant/components/notify/gntp.py
homeassistant/components/notify/googlevoice.py
homeassistant/components/notify/instapush.py homeassistant/components/notify/instapush.py
homeassistant/components/notify/message_bird.py homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nma.py homeassistant/components/notify/nma.py
@ -140,10 +145,10 @@ omit =
homeassistant/components/sensor/forecast.py homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py homeassistant/components/sensor/glances.py
homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/netatmo.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/netatmo.py
homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/onewire.py homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py homeassistant/components/sensor/rest.py
@ -169,10 +174,13 @@ omit =
homeassistant/components/switch/rest.py homeassistant/components/switch/rest.py
homeassistant/components/switch/transmission.py homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py homeassistant/components/switch/wake_on_lan.py
homeassistant/components/thermostat/eq3btsmart.py
homeassistant/components/thermostat/heatmiser.py homeassistant/components/thermostat/heatmiser.py
homeassistant/components/thermostat/homematic.py homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/proliphix.py homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py homeassistant/components/thermostat/radiotherm.py
homeassistant/components/upnp.py
homeassistant/components/zeroconf.py
[report] [report]

View file

@ -23,6 +23,6 @@ If the code does not interact with devices:
[fork]: http://stackoverflow.com/a/7244456 [fork]: http://stackoverflow.com/a/7244456
[squash]: https://github.com/ginatrapani/todo.txt-android/wiki/Squash-All-Commits-Related-to-a-Single-Issue-into-a-Single-Commit [squash]: https://github.com/ginatrapani/todo.txt-android/wiki/Squash-All-Commits-Related-to-a-Single-Issue-into-a-Single-Commit
[ex-requir]: https://github.com/balloob/home-assistant/blob/dev/homeassistant/components/keyboard.py#L16 [ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L16
[ex-import]: https://github.com/balloob/home-assistant/blob/dev/homeassistant/components/keyboard.py#L51 [ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L51

1
.gitignore vendored
View file

@ -8,6 +8,7 @@ config/custom_components/*
!config/custom_components/hello_world.py !config/custom_components/hello_world.py
!config/custom_components/mqtt_example.py !config/custom_components/mqtt_example.py
tests/config/deps
tests/config/home-assistant.log tests/config/home-assistant.log
# Hide sublime text stuff # Hide sublime text stuff

2
.gitmodules vendored
View file

@ -1,3 +1,3 @@
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"] [submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
path = homeassistant/components/frontend/www_static/home-assistant-polymer path = homeassistant/components/frontend/www_static/home-assistant-polymer
url = https://github.com/balloob/home-assistant-polymer.git url = https://github.com/home-assistant/home-assistant-polymer.git

View file

@ -4,10 +4,10 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
The process is straight-forward. The process is straight-forward.
- Fork the Home Assistant [git repository](https://github.com/balloob/home-assistant). - Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
- Write the code for your device, notification service, sensor, or IoT thing. - Write the code for your device, notification service, sensor, or IoT thing.
- Ensure tests work. - Ensure tests work.
- Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant. - Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
Still interested? Then you should read the next sections and get more details. Still interested? Then you should read the next sections and get more details.
@ -20,20 +20,20 @@ After you finish adding support for your device:
- Check that all dependencies are included via the `REQUIREMENTS` variable in your platform/component and only imported inside functions that use them. - Check that all dependencies are included via the `REQUIREMENTS` variable in your platform/component and only imported inside functions that use them.
- Add any new dependencies to `requirements_all.txt` if needed. Use `script/gen_requirements_all.py`. - Add any new dependencies to `requirements_all.txt` if needed. Use `script/gen_requirements_all.py`.
- Update the `.coveragerc` file to exclude your platform if there are no tests available or your new code uses a 3rd party library for communication with the device/service/sensor. - Update the `.coveragerc` file to exclude your platform if there are no tests available or your new code uses a 3rd party library for communication with the device/service/sensor.
- Provide some documentation for [home-assistant.io](https://home-assistant.io/). It's OK to just add a docstring with configuration details (sample entry for `configuration.yaml` file and alike) to the file header as a start. Visit the [website documentation](https://home-assistant.io/developers/website/) for further information on contributing to [home-assistant.io](https://github.com/balloob/home-assistant.io). - Provide some documentation for [home-assistant.io](https://home-assistant.io/). It's OK to just add a docstring with configuration details (sample entry for `configuration.yaml` file and alike) to the file header as a start. Visit the [website documentation](https://home-assistant.io/developers/website/) for further information on contributing to [home-assistant.io](https://github.com/home-assistant/home-assistant.io).
- Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `tox` or `script/lint`. - Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `tox` or `script/lint`.
- Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant. - Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
- Check for comments and suggestions on your Pull Request and keep an eye on the [CI output](https://travis-ci.org/balloob/home-assistant/). - Check for comments and suggestions on your Pull Request and keep an eye on the [CI output](https://travis-ci.org/home-assistant/home-assistant/).
If you add a platform for an existing component, there is usually no need for updating the frontend. Only if you've added a new component that should show up in the frontend, there are more steps needed: If you add a platform for an existing component, there is usually no need for updating the frontend. Only if you've added a new component that should show up in the frontend, there are more steps needed:
- Update the file [`home-assistant-icons.html`](https://github.com/balloob/home-assistant/blob/master/homeassistant/components/frontend/www_static/polymer/resources/home-assistant-icons.html) with an icon for your domain ([pick one from this list](https://www.polymer-project.org/1.0/components/core-elements/demo.html#core-icon)). - Update the file [`home-assistant-icons.html`](https://github.com/home-assistant/home-assistant/blob/master/homeassistant/components/frontend/www_static/polymer/resources/home-assistant-icons.html) with an icon for your domain ([pick one from this list](https://www.polymer-project.org/1.0/components/core-elements/demo.html#core-icon)).
- Update the demo component with two states that it provides. - Update the demo component with two states that it provides.
- Add your component to `home-assistant.conf.example`. - Add your component to `home-assistant.conf.example`.
Since you've updated `home-assistant-icons.html`, you've made changes to the frontend: Since you've updated `home-assistant-icons.html`, you've made changes to the frontend:
- Run `build_frontend`. This will build a new version of the frontend. Make sure you add the changed files `frontend.py` and `frontend.html` to the commit. - Run `script/build_frontend`. This will build a new version of the frontend. Make sure you add the changed files `frontend.py` and `frontend.html` to the commit.
### Setting states ### Setting states
@ -46,11 +46,11 @@ A state can have several attributes that will help the frontend in displaying yo
- `unit_of_measurement`: this will be appended to the state in the interface - `unit_of_measurement`: this will be appended to the state in the interface
- `hidden`: This is a suggestion to the frontend on if the state should be hidden - `hidden`: This is a suggestion to the frontend on if the state should be hidden
These attributes are defined in [homeassistant.components](https://github.com/balloob/home-assistant/blob/master/homeassistant/components/__init__.py#L25). These attributes are defined in [homeassistant.components](https://github.com/home-assistant/home-assistant/blob/master/homeassistant/components/__init__.py#L25).
### Proper Visibility Handling ### Proper Visibility Handling
Generally, when creating a new entity for Home Assistant you will want it to be a class that inherits the [homeassistant.helpers.entity.Entity](https://github.com/balloob/home-assistant/blob/master/homeassistant/helpers/entity.py) class. If this is done, visibility will be handled for you. Generally, when creating a new entity for Home Assistant you will want it to be a class that inherits the [homeassistant.helpers.entity.Entity](https://github.com/home-assistant/home-assistant/blob/master/homeassistant/helpers/entity.py) class. If this is done, visibility will be handled for you.
You can set a suggestion for your entity's visibility by setting the hidden property by doing something similar to the following. You can set a suggestion for your entity's visibility by setting the hidden property by doing something similar to the following.
```python ```python

View file

@ -8,9 +8,9 @@ WORKDIR /usr/src/app
RUN pip3 install --no-cache-dir colorlog cython RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker # For the nmap tracker, bluetooth tracker, Z-Wave
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo && \ apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave COPY script/build_python_openzwave script/build_python_openzwave

View file

@ -1,4 +1,4 @@
Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/balloob/home-assistant| Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/home-assistant/home-assistant|
=========================================================================================================== ===========================================================================================================
Home Assistant is a home automation platform running on Python 3. The Home Assistant is a home automation platform running on Python 3. The
@ -88,11 +88,11 @@ If you run into issues while using Home Assistant or during development
of a component, check the `Home Assistant help of a component, check the `Home Assistant help
section <https://home-assistant.io/help/>`__ how to reach us. section <https://home-assistant.io/help/>`__ how to reach us.
.. |Build Status| image:: https://travis-ci.org/balloob/home-assistant.svg?branch=master .. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=master
:target: https://travis-ci.org/balloob/home-assistant :target: https://travis-ci.org/home-assistant/home-assistant
.. |Coverage Status| image:: https://img.shields.io/coveralls/balloob/home-assistant.svg .. |Coverage Status| image:: https://img.shields.io/coveralls/home-assistant/home-assistant.svg
:target: https://coveralls.io/r/balloob/home-assistant?branch=master :target: https://coveralls.io/r/home-assistant/home-assistant?branch=master
.. |Join the chat at https://gitter.im/balloob/home-assistant| image:: https://badges.gitter.im/Join%20Chat.svg .. |Join the chat at https://gitter.im/home-assistant/home-assistant| image:: https://badges.gitter.im/Join%20Chat.svg
:target: https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge :target: https://gitter.im/home-assistant/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |screenshot-states| image:: https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png .. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png
:target: https://home-assistant.io/demo/ :target: https://home-assistant.io/demo/

View file

@ -7,7 +7,7 @@ homeassistant:
latitude: 32.87336 latitude: 32.87336
longitude: 117.22743 longitude: 117.22743
# C for Celcius, F for Fahrenheit # C for Celsius, F for Fahrenheit
temperature_unit: C temperature_unit: C
# Pick yours from here: # Pick yours from here:

View file

@ -14,7 +14,7 @@ To use the mqtt_example component you will need to add the following to your
configuration.yaml file. configuration.yaml file.
mqtt_example: mqtt_example:
topic: home-assistant/mqtt_example topic: "home-assistant/mqtt_example"
""" """
import homeassistant.loader as loader import homeassistant.loader as loader
@ -29,7 +29,7 @@ DEFAULT_TOPIC = 'home-assistant/mqtt_example'
def setup(hass, config): def setup(hass, config):
"""Setup the MQTT example component.""" """Setup the MQTT example component."""
mqtt = loader.get_component('mqtt') mqtt = loader.get_component('mqtt')
topic = config[DOMAIN].get('topic', DEFAULT_TOPIC) topic = config[DOMAIN].get('topic', DEFAULT_TOPIC)
entity_id = 'mqtt_example.last_message' entity_id = 'mqtt_example.last_message'

View file

@ -31,7 +31,7 @@ def validate_python():
def ensure_config_path(config_dir): def ensure_config_path(config_dir):
"""Validate the configuration directory.""" """Validate the configuration directory."""
import homeassistant.config as config_util import homeassistant.config as config_util
lib_dir = os.path.join(config_dir, 'lib') lib_dir = os.path.join(config_dir, 'deps')
# Test if configuration directory exists # Test if configuration directory exists
if not os.path.isdir(config_dir): if not os.path.isdir(config_dir):

View file

@ -21,7 +21,7 @@ import homeassistant.util.package as pkg_util
from homeassistant.const import ( from homeassistant.const import (
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED, CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
TEMP_CELCIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__) TEMP_CELSIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import ( from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs) event_decorators, service, config_per_platform, extract_domain_configs)
@ -65,7 +65,7 @@ def _handle_requirements(hass, component, name):
return True return True
for req in component.REQUIREMENTS: for req in component.REQUIREMENTS:
if not pkg_util.install_package(req, target=hass.config.path('lib')): if not pkg_util.install_package(req, target=hass.config.path('deps')):
_LOGGER.error('Not initializing %s because could not install ' _LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req) 'dependency %s', name, req)
return False return False
@ -211,7 +211,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
def mount_local_lib_path(config_dir): def mount_local_lib_path(config_dir):
"""Add local library to Python Path.""" """Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'lib')) sys.path.insert(0, os.path.join(config_dir, 'deps'))
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments # pylint: disable=too-many-branches, too-many-statements, too-many-arguments
@ -372,10 +372,16 @@ def process_ha_config_upgrade(hass):
_LOGGER.info('Upgrading config directory from %s to %s', conf_version, _LOGGER.info('Upgrading config directory from %s to %s', conf_version,
__version__) __version__)
# This was where dependencies were installed before v0.18
# Probably should keep this around until ~v0.20.
lib_path = hass.config.path('lib') lib_path = hass.config.path('lib')
if os.path.isdir(lib_path): if os.path.isdir(lib_path):
shutil.rmtree(lib_path) shutil.rmtree(lib_path)
lib_path = hass.config.path('deps')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
with open(version_path, 'wt') as outp: with open(version_path, 'wt') as outp:
outp.write(__version__) outp.write(__version__)
@ -434,7 +440,7 @@ def process_ha_core_config(hass, config):
if info.use_fahrenheit: if info.use_fahrenheit:
hac.temperature_unit = TEMP_FAHRENHEIT hac.temperature_unit = TEMP_FAHRENHEIT
else: else:
hac.temperature_unit = TEMP_CELCIUS hac.temperature_unit = TEMP_CELSIUS
if hac.location_name is None: if hac.location_name is None:
hac.location_name = info.city hac.location_name = info.city

View file

@ -7,12 +7,15 @@ https://home-assistant.io/components/alarm_control_panel/
import logging import logging
import os import os
import voluptuous as vol
from homeassistant.components import verisure from homeassistant.components import verisure
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
@ -38,6 +41,11 @@ ATTR_TO_PROPERTY = [
ATTR_CODE_FORMAT ATTR_CODE_FORMAT
] ]
ALARM_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_CODE): cv.string,
})
def setup(hass, config): def setup(hass, config):
"""Track states and offer events for sensors.""" """Track states and offer events for sensors."""
@ -51,10 +59,7 @@ def setup(hass, config):
"""Map services to methods on Alarm.""" """Map services to methods on Alarm."""
target_alarms = component.extract_from_service(service) target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data: code = service.data.get(ATTR_CODE)
code = None
else:
code = service.data[ATTR_CODE]
method = SERVICE_TO_METHOD[service.service] method = SERVICE_TO_METHOD[service.service]
@ -68,8 +73,8 @@ def setup(hass, config):
for service in SERVICE_TO_METHOD: for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, alarm_service_handler, hass.services.register(DOMAIN, service, alarm_service_handler,
descriptions.get(service)) descriptions.get(service),
schema=ALARM_SERVICE_SCHEMA)
return True return True

View file

@ -51,8 +51,6 @@ def _platform_validator(method, schema):
if not hasattr(platform, schema): if not hasattr(platform, schema):
return config return config
print('validating config', method, config)
return getattr(platform, schema)(config) return getattr(platform, schema)(config)
return validator return validator

View file

@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action): def trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
if CONF_AFTER in config: if CONF_AFTER in config:
after = dt_util.parse_time_str(config[CONF_AFTER]) after = dt_util.parse_time(config[CONF_AFTER])
if after is None: if after is None:
_error_time(config[CONF_AFTER], CONF_AFTER) _error_time(config[CONF_AFTER], CONF_AFTER)
return False return False
@ -62,13 +62,13 @@ def if_action(hass, config):
return None return None
if before is not None: if before is not None:
before = dt_util.parse_time_str(before) before = dt_util.parse_time(before)
if before is None: if before is None:
_error_time(before, CONF_BEFORE) _error_time(before, CONF_BEFORE)
return None return None
if after is not None: if after is not None:
after = dt_util.parse_time_str(after) after = dt_util.parse_time(after)
if after is None: if after is None:
_error_time(after, CONF_AFTER) _error_time(after, CONF_AFTER)
return None return None

View file

@ -6,10 +6,9 @@ https://home-assistant.io/components/binary_sensor.mysensors/
""" """
import logging import logging
from homeassistant.const import ( from homeassistant.components.binary_sensor import (SENSOR_CLASSES,
ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON) BinarySensorDevice)
from homeassistant.components.binary_sensor import ( from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON
BinarySensorDevice, SENSOR_CLASSES)
from homeassistant.loader import get_component from homeassistant.loader import get_component
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -101,8 +100,13 @@ class MySensorsBinarySensor(BinarySensorDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return device specific state attributes.""" """Return device specific state attributes."""
address = getattr(self.gateway, 'server_address', None)
if address:
device = '{}:{}'.format(address[0], address[1])
else:
device = self.gateway.port
attr = { attr = {
self.mysensors.ATTR_PORT: self.gateway.port, self.mysensors.ATTR_DEVICE: device,
self.mysensors.ATTR_NODE_ID: self.node_id, self.mysensors.ATTR_NODE_ID: self.node_id,
self.mysensors.ATTR_CHILD_ID: self.child_id, self.mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level, ATTR_BATTERY_LEVEL: self.battery_level,

View file

@ -4,12 +4,14 @@ Support for Nest Thermostat Binary Sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.nest/ https://home-assistant.io/components/binary_sensor.nest/
""" """
import logging import voluptuous as vol
import socket
import homeassistant.components.nest as nest import homeassistant.components.nest as nest
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.nest import NestSensor from homeassistant.components.sensor.nest import NestSensor
from homeassistant.const import (
CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS
)
DEPENDENCIES = ['nest'] DEPENDENCIES = ['nest']
BINARY_TYPES = ['fan', BINARY_TYPES = ['fan',
@ -23,25 +25,19 @@ BINARY_TYPES = ['fan',
'hvac_emer_heat_state', 'hvac_emer_heat_state',
'online'] 'online']
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): nest.DOMAIN,
vol.Optional(CONF_SCAN_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Required(CONF_MONITORED_CONDITIONS): [vol.In(BINARY_TYPES)],
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Nest binary sensors.""" """Setup Nest binary sensors."""
logger = logging.getLogger(__name__) for structure, device in nest.devices():
try: add_devices([NestBinarySensor(structure, device, variable)
for structure in nest.NEST.structures: for variable in config[CONF_MONITORED_CONDITIONS]])
for device in structure.devices:
for variable in config['monitored_conditions']:
if variable in BINARY_TYPES:
add_devices([NestBinarySensor(structure,
device,
variable)])
else:
logger.error('Nest sensor type: "%s" does not exist',
variable)
except socket.error:
logger.error(
"Connection error logging into the nest web service."
)
class NestBinarySensor(NestSensor, BinarySensorDevice): class NestBinarySensor(NestSensor, BinarySensorDevice):

View file

@ -49,8 +49,7 @@ class VeraBinarySensor(VeraDevice, BinarySensorDevice):
last_tripped = self.vera_device.last_trip last_tripped = self.vera_device.last_trip
if last_tripped is not None: if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped)) utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
utc_time)
else: else:
attr[ATTR_LAST_TRIP_TIME] = None attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped tripped = self.vera_device.is_tripped

View file

@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['python-wink==0.6.4'] REQUIREMENTS = ['python-wink==0.7.4']
# These are the available sensors mapped to binary_sensor class # These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = { SENSOR_TYPES = {

View file

@ -8,11 +8,7 @@ import logging
import datetime import datetime
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID,
COMMAND_CLASS_SENSOR_BINARY, NETWORK,
ZWaveDeviceEntity, get_config_value)
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DOMAIN, DOMAIN,
BinarySensorDevice) BinarySensorDevice)
@ -36,11 +32,11 @@ DEVICE_MAPPINGS = {
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for sensors.""" """Setup the Z-Wave platform for sensors."""
if discovery_info is None or NETWORK is None: if discovery_info is None or zwave.NETWORK is None:
return return
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]] node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]] value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
value.set_change_verified(False) value.set_change_verified(False)
# Make sure that we have values for the key before converting to int # Make sure that we have values for the key before converting to int
@ -53,18 +49,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if specific_sensor_key in DEVICE_MAPPINGS: if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4 # Default the multiplier to 4
re_arm_multiplier = (get_config_value(value.node, 9) or 4) re_arm_multiplier = (zwave.get_config_value(value.node,
9) or 4)
add_devices([ add_devices([
ZWaveTriggerSensor(value, "motion", ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8) hass, re_arm_multiplier * 8)
]) ])
return return
if value.command_class == COMMAND_CLASS_SENSOR_BINARY: if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)]) add_devices([ZWaveBinarySensor(value, None)])
class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
"""Representation of a binary sensor within Z-Wave.""" """Representation of a binary sensor within Z-Wave."""
def __init__(self, value, sensor_class): def __init__(self, value, sensor_class):
@ -74,7 +71,7 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
from openzwave.network import ZWaveNetwork from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
dispatcher.connect( dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED) self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)

View file

@ -4,10 +4,18 @@ Provides functionality to launch a web browser on the host machine.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/browser/ https://home-assistant.io/components/browser/
""" """
import voluptuous as vol
DOMAIN = "browser" DOMAIN = "browser"
SERVICE_BROWSE_URL = "browse_url" SERVICE_BROWSE_URL = "browse_url"
ATTR_URL = 'url'
ATTR_URL_DEFAULT = 'https://www.google.com'
SERVICE_BROWSE_URL_SCHEMA = vol.Schema({
vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url,
})
def setup(hass, config): def setup(hass, config):
"""Listen for browse_url events.""" """Listen for browse_url events."""
@ -15,8 +23,7 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_BROWSE_URL, hass.services.register(DOMAIN, SERVICE_BROWSE_URL,
lambda service: lambda service:
webbrowser.open( webbrowser.open(service.data[ATTR_URL]),
service.data.get( schema=SERVICE_BROWSE_URL_SCHEMA)
'url', 'https://www.google.com')))
return True return True

View file

@ -8,9 +8,12 @@ import logging
import re import re
import warnings import warnings
import voluptuous as vol
from homeassistant import core from homeassistant import core
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON) ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
DOMAIN = "conversation" DOMAIN = "conversation"
@ -18,6 +21,10 @@ SERVICE_PROCESS = "process"
ATTR_TEXT = "text" ATTR_TEXT = "text"
SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)') REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
REQUIREMENTS = ['fuzzywuzzy==0.8.0'] REQUIREMENTS = ['fuzzywuzzy==0.8.0']
@ -32,11 +39,7 @@ def setup(hass, config):
def process(service): def process(service):
"""Parse text into commands.""" """Parse text into commands."""
if ATTR_TEXT not in service.data: text = service.data[ATTR_TEXT]
logger.error("Received process service call without a text")
return
text = service.data[ATTR_TEXT].lower()
match = REGEX_TURN_COMMAND.match(text) match = REGEX_TURN_COMMAND.match(text)
if not match: if not match:
@ -67,6 +70,6 @@ def setup(hass, config):
logger.error( logger.error(
'Got unsupported command %s from text %s', command, text) 'Got unsupported command %s from text %s', command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process) hass.services.register(DOMAIN, SERVICE_PROCESS, process,
schema=SERVICE_PROCESS_SCHEMA)
return True return True

View file

@ -94,7 +94,7 @@ def setup(hass, config):
yaml_path = hass.config.path(YAML_DEVICES) yaml_path = hass.config.path(YAML_DEVICES)
conf = config.get(DOMAIN, {}) conf = config.get(DOMAIN, {})
if isinstance(conf, list): if isinstance(conf, list) and len(conf) > 0:
conf = conf[0] conf = conf[0]
consider_home = timedelta( consider_home = timedelta(
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int, seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,

View file

@ -0,0 +1,91 @@
"""Tracking for bluetooth devices."""
import logging
from datetime import timedelta
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES,
CONF_TRACK_NEW,
CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
load_config,
)
import homeassistant.util as util
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pybluez==0.22']
BT_PREFIX = 'BT_'
def setup_scanner(hass, config, see):
"""Setup the Bluetooth Scanner."""
# pylint: disable=import-error
import bluetooth
def see_device(device):
"""Mark a device as seen."""
see(mac=BT_PREFIX + device[0], host_name=device[1])
def discover_devices():
"""Discover bluetooth devices."""
result = bluetooth.discover_devices(duration=8,
lookup_names=True,
flush_cache=True,
lookup_class=False)
_LOGGER.debug("Bluetooth devices discovered = " + str(len(result)))
return result
yaml_path = hass.config.path(YAML_DEVICES)
devs_to_track = []
devs_donot_track = []
# Load all known devices.
# We just need the devices so set consider_home and home range
# to 0
for device in load_config(yaml_path, hass, 0, 0):
# check if device is a valid bluetooth device
if device.mac and device.mac[:3].upper() == BT_PREFIX:
if device.track:
devs_to_track.append(device.mac[3:])
else:
devs_donot_track.append(device.mac[3:])
# if track new devices is true discover new devices
# on startup.
track_new = util.convert(config.get(CONF_TRACK_NEW), bool,
len(devs_to_track) == 0)
if track_new:
for dev in discover_devices():
if dev[0] not in devs_to_track and \
dev[0] not in devs_donot_track:
devs_to_track.append(dev[0])
see_device(dev)
if not devs_to_track:
_LOGGER.warning("No bluetooth devices to track!")
return False
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
DEFAULT_SCAN_INTERVAL)
def update_bluetooth(now):
"""Lookup bluetooth device and update status."""
try:
for mac in devs_to_track:
_LOGGER.debug("Scanning " + mac)
result = bluetooth.lookup_name(mac, timeout=5)
if not result:
# Could not lookup device name
continue
see_device((mac, result))
except bluetooth.BluetoothError:
_LOGGER.exception('Error looking up bluetooth device!')
track_point_in_utc_time(hass, update_bluetooth,
now + timedelta(seconds=interval))
update_bluetooth(dt_util.utcnow())
return True

View file

@ -12,7 +12,9 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.4.6'] REQUIREMENTS = ['https://github.com/deisi/fritzconnection/archive/'
'b5c14515e1c8e2652b06b6316a7f3913df942841.zip'
'#fritzconnection==0.4.6']
# Return cached results if last scan was less then this time ago. # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)

View file

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

View file

@ -34,21 +34,33 @@ def setup_scanner(hass, config, see):
"""Setup an OwnTracks tracker.""" """Setup an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
def owntracks_location_update(topic, payload, qos): def validate_payload(payload, data_type):
"""MQTT message received.""" """Validate OwnTracks payload."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
try: try:
data = json.loads(payload) data = json.loads(payload)
except ValueError: except ValueError:
# If invalid JSON # If invalid JSON
_LOGGER.error( _LOGGER.error('Unable to parse payload as JSON: %s', payload)
'Unable to parse payload as JSON: %s', payload) return None
return if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug('Skipping %s update for following data '
'because of missing or malformatted data: %s',
data_type, data)
return None
if max_gps_accuracy is not None and \
convert(data.get('acc'), float, 0.0) > max_gps_accuracy:
_LOGGER.debug('Skipping %s update because expected GPS '
'accuracy %s is not met: %s',
data_type, max_gps_accuracy, data)
return None
return data
if (not isinstance(data, dict) or data.get('_type') != 'location') or ( def owntracks_location_update(topic, payload, qos):
max_gps_accuracy is not None and """MQTT message received."""
convert(data.get('acc'), float, 0.0) > max_gps_accuracy): # Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
data = validate_payload(payload, 'location')
if not data:
return return
dev_id, kwargs = _parse_see_args(topic, data) dev_id, kwargs = _parse_see_args(topic, data)
@ -65,24 +77,16 @@ def setup_scanner(hass, config, see):
see_beacons(dev_id, kwargs) see_beacons(dev_id, kwargs)
def owntracks_event_update(topic, payload, qos): def owntracks_event_update(topic, payload, qos):
# pylint: disable=too-many-branches, too-many-statements
"""MQTT event (geofences) received.""" """MQTT event (geofences) received."""
# Docs on available data: # Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition # http://owntracks.org/booklet/tech/json/#_typetransition
try: data = validate_payload(payload, 'transition')
data = json.loads(payload) if not data:
except ValueError:
# If invalid JSON
_LOGGER.error(
'Unable to parse payload as JSON: %s', payload)
return
if not isinstance(data, dict) or data.get('_type') != 'transition':
return return
if data.get('desc') is None: if data.get('desc') is None:
_LOGGER.error( _LOGGER.error(
"Location missing from `enter/exit` message - " "Location missing from `Entering/Leaving` message - "
"please turn `Share` on in OwnTracks app") "please turn `Share` on in OwnTracks app")
return return
# OwnTracks uses - at the start of a beacon zone # OwnTracks uses - at the start of a beacon zone
@ -93,16 +97,16 @@ def setup_scanner(hass, config, see):
dev_id, kwargs = _parse_see_args(topic, data) dev_id, kwargs = _parse_see_args(topic, data)
if data['event'] == 'enter': def enter_event():
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(location)) zone = hass.states.get("zone.{}".format(location))
with LOCK: with LOCK:
if zone is None: if zone is None and data['t'] == 'b':
if data['t'] == 'b': # Not a HA zone, and a beacon so assume mobile
# Not a HA zone, and a beacon so assume mobile beacons = MOBILE_BEACONS_ACTIVE[dev_id]
beacons = MOBILE_BEACONS_ACTIVE[dev_id] if location not in beacons:
if location not in beacons: beacons.append(location)
beacons.append(location) _LOGGER.info("Added beacon %s", location)
_LOGGER.info("Added beacon %s", location)
else: else:
# Normal region # Normal region
regions = REGIONS_ENTERED[dev_id] regions = REGIONS_ENTERED[dev_id]
@ -114,7 +118,8 @@ def setup_scanner(hass, config, see):
see(**kwargs) see(**kwargs)
see_beacons(dev_id, kwargs) see_beacons(dev_id, kwargs)
elif data['event'] == 'leave': def leave_event():
"""Execute leave event."""
with LOCK: with LOCK:
regions = REGIONS_ENTERED[dev_id] regions = REGIONS_ENTERED[dev_id]
if location in regions: if location in regions:
@ -146,6 +151,10 @@ def setup_scanner(hass, config, see):
beacons.remove(location) beacons.remove(location)
_LOGGER.info("Remove beacon %s", location) _LOGGER.info("Remove beacon %s", location)
if data['event'] == 'enter':
enter_event()
elif data['event'] == 'leave':
leave_event()
else: else:
_LOGGER.error( _LOGGER.error(
'Misformatted mqtt msgs, _type=transition, event=%s', 'Misformatted mqtt msgs, _type=transition, event=%s',

View file

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

View file

@ -10,8 +10,10 @@ import re
import threading import threading
import requests import requests
import voluptuous as vol
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
import homeassistant.helpers.config_validation as cv
from homeassistant.util import sanitize_filename from homeassistant.util import sanitize_filename
DOMAIN = "downloader" DOMAIN = "downloader"
@ -21,6 +23,11 @@ SERVICE_DOWNLOAD_FILE = "download_file"
ATTR_URL = "url" ATTR_URL = "url"
ATTR_SUBDIR = "subdir" ATTR_SUBDIR = "subdir"
SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema({
vol.Required(ATTR_URL): vol.Url,
vol.Optional(ATTR_SUBDIR): cv.string,
})
CONF_DOWNLOAD_DIR = 'download_dir' CONF_DOWNLOAD_DIR = 'download_dir'
@ -48,10 +55,6 @@ def setup(hass, config):
def download_file(service): def download_file(service):
"""Start thread to download file specified in the URL.""" """Start thread to download file specified in the URL."""
if ATTR_URL not in service.data:
logger.error("Service called but 'url' parameter not specified.")
return
def do_download(): def do_download():
"""Download the file.""" """Download the file."""
try: try:
@ -127,7 +130,7 @@ def setup(hass, config):
threading.Thread(target=do_download).start() threading.Thread(target=do_download).start()
hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE, hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE, download_file,
download_file) schema=SERVICE_DOWNLOAD_FILE_SCHEMA)
return True return True

View file

@ -0,0 +1,80 @@
"""RSS/Atom feed reader for Home Assistant."""
from datetime import datetime
from logging import getLogger
import voluptuous as vol
from homeassistant.helpers.event import track_utc_time_change
REQUIREMENTS = ['feedparser==5.2.1']
_LOGGER = getLogger(__name__)
DOMAIN = "feedreader"
EVENT_FEEDREADER = "feedreader"
# pylint: disable=no-value-for-parameter
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
'urls': [vol.Url()],
}
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=too-few-public-methods
class FeedManager(object):
"""Abstraction over feedparser module."""
def __init__(self, url, hass):
"""Initialize the FeedManager object, poll every hour."""
self._url = url
self._feed = None
self._hass = hass
# Initialize last entry timestamp as epoch time
self._last_entry_timestamp = datetime.utcfromtimestamp(0).timetuple()
_LOGGER.debug('Loading feed %s', self._url)
self._update()
track_utc_time_change(hass, lambda now: self._update(),
minute=0, second=0)
def _log_no_entries(self):
"""Send no entries log at debug level."""
_LOGGER.debug('No new entries in feed %s', self._url)
def _update(self):
"""Update the feed and publish new entries in the event bus."""
import feedparser
_LOGGER.info('Fetching new data from feed %s', self._url)
self._feed = feedparser.parse(self._url,
etag=None if not self._feed
else self._feed.get('etag'),
modified=None if not self._feed
else self._feed.get('modified'))
if not self._feed:
_LOGGER.error('Error fetching feed data from %s', self._url)
else:
if self._feed.bozo != 0:
_LOGGER.error('Error parsing feed %s', self._url)
# Using etag and modified, if there's no new data available,
# the entries list will be empty
elif len(self._feed.entries) > 0:
_LOGGER.debug('Entries available in feed %s', self._url)
self._publish_new_entries()
self._last_entry_timestamp = \
self._feed.entries[0].published_parsed
else:
self._log_no_entries()
def _publish_new_entries(self):
"""Publish new entries to the event bus."""
new_entries = False
for entry in self._feed.entries:
# Consider only entries newer then the latest parsed one
if entry.published_parsed > self._last_entry_timestamp:
new_entries = True
entry.update({'feed_url': self._url})
self._hass.bus.fire(EVENT_FEEDREADER, entry)
if not new_entries:
self._log_no_entries()
def setup(hass, config):
"""Setup the feedreader component."""
urls = config.get(DOMAIN)['urls']
feeds = [FeedManager(url, hass) for url in urls]
return len(feeds) > 0

View file

@ -1,2 +1,2 @@
"""DO NOT MODIFY. Auto-generated by update_mdi script.""" """DO NOT MODIFY. Auto-generated by update_mdi script."""
VERSION = "df49e6b7c930eb39b42ff1909712e95e" VERSION = "af8a531f1c2e477c07c4b3394bd1ce13"

View file

@ -1,2 +1,2 @@
"""DO NOT MODIFY. Auto-generated by build_frontend script.""" """DO NOT MODIFY. Auto-generated by build_frontend script."""
VERSION = "c2932592a6946e955ddc46f31409b81f" VERSION = "ffd8a1bde5ba13f300c3d6ad32036526"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit ac311416a99f41abbe98142ccac5f84f77d88296 Subproject commit 11311809c1eba0ed3b7e26d07a0fdb81b7959e3a

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

View file

@ -7,10 +7,13 @@ at https://home-assistant.io/components/garage_door/
import logging import logging
import os import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN, STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN,
ATTR_ENTITY_ID) ATTR_ENTITY_ID)
@ -29,6 +32,10 @@ DISCOVERY_PLATFORMS = {
wink.DISCOVER_GARAGE_DOORS: 'wink' wink.DISCOVER_GARAGE_DOORS: 'wink'
} }
GARAGE_DOOR_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -73,10 +80,11 @@ def setup(hass, config):
descriptions = load_yaml_config_file( descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml')) os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_OPEN, handle_garage_door_service, hass.services.register(DOMAIN, SERVICE_OPEN, handle_garage_door_service,
descriptions.get(SERVICE_OPEN)) descriptions.get(SERVICE_OPEN),
schema=GARAGE_DOOR_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_CLOSE, handle_garage_door_service, hass.services.register(DOMAIN, SERVICE_CLOSE, handle_garage_door_service,
descriptions.get(SERVICE_CLOSE)) descriptions.get(SERVICE_CLOSE),
schema=GARAGE_DOOR_SERVICE_SCHEMA)
return True return True

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

@ -9,7 +9,7 @@ import logging
from homeassistant.components.garage_door import GarageDoorDevice from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.6.4'] REQUIREMENTS = ['python-wink==0.7.4']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View file

@ -182,7 +182,7 @@ def _api_history_period(handler, path_match, data):
one_day = timedelta(seconds=86400) one_day = timedelta(seconds=86400)
if date_str: if date_str:
start_date = dt_util.date_str_to_date(date_str) start_date = dt_util.parse_date(date_str)
if start_date is None: if start_date is None:
handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST) handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST)

View file

@ -5,6 +5,7 @@ For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api/ https://home-assistant.io/developers/api/
""" """
import gzip import gzip
import hmac
import json import json
import logging import logging
import ssl import ssl
@ -200,12 +201,22 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY) "Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return return
self.authenticated = (self.server.api_password is None or if self.verify_session():
self.headers.get(HTTP_HEADER_HA_AUTH) == # The user has a valid session already
self.server.api_password or self.authenticated = True
data.get(DATA_API_PASSWORD) == elif self.server.api_password is None:
self.server.api_password or # No password is set, so everyone is authenticated
self.verify_session()) self.authenticated = True
elif hmac.compare_digest(self.headers.get(HTTP_HEADER_HA_AUTH, ''),
self.server.api_password):
# A valid auth header has been set
self.authenticated = True
elif hmac.compare_digest(data.get(DATA_API_PASSWORD, ''),
self.server.api_password):
# A valid password has been specified
self.authenticated = True
else:
self.authenticated = False
# we really shouldn't need to forward the password from here # we really shouldn't need to forward the password from here
if url.path not in [URL_ROOT, URL_API_EVENT_FORWARD]: if url.path not in [URL_ROOT, URL_API_EVENT_FORWARD]:

View file

@ -7,8 +7,10 @@ https://home-assistant.io/components/ifttt/
import logging import logging
import requests import requests
import voluptuous as vol
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,6 +25,13 @@ ATTR_VALUE3 = 'value3'
REQUIREMENTS = ['pyfttt==0.3'] REQUIREMENTS = ['pyfttt==0.3']
SERVICE_TRIGGER_SCHEMA = vol.Schema({
vol.Required(ATTR_EVENT): cv.string,
vol.Optional(ATTR_VALUE1): cv.string,
vol.Optional(ATTR_VALUE2): cv.string,
vol.Optional(ATTR_VALUE3): cv.string,
})
def trigger(hass, event, value1=None, value2=None, value3=None): def trigger(hass, event, value1=None, value2=None, value3=None):
"""Trigger a Maker IFTTT recipe.""" """Trigger a Maker IFTTT recipe."""
@ -44,12 +53,10 @@ def setup(hass, config):
def trigger_service(call): def trigger_service(call):
"""Handle IFTTT trigger service calls.""" """Handle IFTTT trigger service calls."""
event = call.data.get(ATTR_EVENT) event = call.data[ATTR_EVENT]
value1 = call.data.get(ATTR_VALUE1) value1 = call.data.get(ATTR_VALUE1)
value2 = call.data.get(ATTR_VALUE2) value2 = call.data.get(ATTR_VALUE2)
value3 = call.data.get(ATTR_VALUE3) value3 = call.data.get(ATTR_VALUE3)
if event is None:
return
try: try:
import pyfttt as pyfttt import pyfttt as pyfttt
@ -57,6 +64,7 @@ def setup(hass, config):
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
_LOGGER.exception("Error communicating with IFTTT") _LOGGER.exception("Error communicating with IFTTT")
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service) hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service,
schema=SERVICE_TRIGGER_SCHEMA)
return True return True

View file

@ -7,7 +7,8 @@ https://home-assistant.io/components/influxdb/
import logging import logging
import homeassistant.util as util import homeassistant.util as util
from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNKNOWN from homeassistant.const import (EVENT_STATE_CHANGED, STATE_UNAVAILABLE,
STATE_UNKNOWN)
from homeassistant.helpers import state as state_helper from homeassistant.helpers import state as state_helper
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
@ -70,8 +71,9 @@ def setup(hass, config):
def influx_event_listener(event): def influx_event_listener(event):
"""Listen for new messages on the bus and sends them to Influx.""" """Listen for new messages on the bus and sends them to Influx."""
state = event.data.get('new_state') state = event.data.get('new_state')
if state is None or state.state in (STATE_UNKNOWN, '') \ if state is None or state.state in (
or state.entity_id in blacklist: STATE_UNKNOWN, '', STATE_UNAVAILABLE) or \
state.entity_id in blacklist:
return return
try: try:

View file

@ -6,8 +6,11 @@ at https://home-assistant.io/components/input_boolean/
""" """
import logging import logging
import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON) ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify from homeassistant.util import slugify
@ -22,6 +25,10 @@ CONF_NAME = "name"
CONF_INITIAL = "initial" CONF_INITIAL = "initial"
CONF_ICON = "icon" CONF_ICON = "icon"
TOGGLE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_on(hass, entity_id): def is_on(hass, entity_id):
"""Test if input_boolean is True.""" """Test if input_boolean is True."""
@ -75,8 +82,10 @@ def setup(hass, config):
else: else:
input_b.turn_off() input_b.turn_off()
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service) hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service,
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service) schema=TOGGLE_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service,
schema=TOGGLE_SERVICE_SCHEMA)
component.add_entities(entities) component.add_entities(entities)

View file

@ -6,7 +6,10 @@ at https://home-assistant.io/components/input_select/
""" """
import logging import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify from homeassistant.util import slugify
@ -25,6 +28,11 @@ ATTR_OPTIONS = 'options'
SERVICE_SELECT_OPTION = 'select_option' SERVICE_SELECT_OPTION = 'select_option'
SERVICE_SELECT_OPTION_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_OPTION): cv.string,
})
def select_option(hass, entity_id, option): def select_option(hass, entity_id, option):
"""Set input_select to False.""" """Set input_select to False."""
@ -79,10 +87,11 @@ def setup(hass, config):
target_inputs = component.extract_from_service(call) target_inputs = component.extract_from_service(call)
for input_select in target_inputs: for input_select in target_inputs:
input_select.select_option(call.data.get(ATTR_OPTION)) input_select.select_option(call.data[ATTR_OPTION])
hass.services.register(DOMAIN, SERVICE_SELECT_OPTION, hass.services.register(DOMAIN, SERVICE_SELECT_OPTION,
select_option_service) select_option_service,
schema=SERVICE_SELECT_OPTION_SCHEMA)
component.add_entities(entities) component.add_entities(entities)

View file

@ -6,7 +6,10 @@ at https://home-assistant.io/components/input_slider/
""" """
import logging import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify from homeassistant.util import slugify
@ -29,6 +32,11 @@ ATTR_STEP = 'step'
SERVICE_SELECT_VALUE = 'select_value' SERVICE_SELECT_VALUE = 'select_value'
SERVICE_SELECT_VALUE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_VALUE): vol.Coerce(int),
})
def select_value(hass, entity_id, value): def select_value(hass, entity_id, value):
"""Set input_slider to value.""" """Set input_slider to value."""
@ -81,10 +89,11 @@ def setup(hass, config):
target_inputs = component.extract_from_service(call) target_inputs = component.extract_from_service(call)
for input_slider in target_inputs: for input_slider in target_inputs:
input_slider.select_value(call.data.get(ATTR_VALUE)) input_slider.select_value(call.data[ATTR_VALUE])
hass.services.register(DOMAIN, SERVICE_SELECT_VALUE, hass.services.register(DOMAIN, SERVICE_SELECT_VALUE,
select_value_service) select_value_service,
schema=SERVICE_SELECT_VALUE_SCHEMA)
component.add_entities(entities) component.add_entities(entities)

View file

@ -4,6 +4,8 @@ Provides functionality to emulate keyboard presses on host machine.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/keyboard/ https://home-assistant.io/components/keyboard/
""" """
import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
@ -12,6 +14,8 @@ from homeassistant.const import (
DOMAIN = "keyboard" DOMAIN = "keyboard"
REQUIREMENTS = ['pyuserinput==0.1.9'] REQUIREMENTS = ['pyuserinput==0.1.9']
TAP_KEY_SCHEMA = vol.Schema({})
def volume_up(hass): def volume_up(hass):
"""Press the keyboard button for volume up.""" """Press the keyboard button for volume up."""
@ -52,26 +56,31 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_VOLUME_UP, hass.services.register(DOMAIN, SERVICE_VOLUME_UP,
lambda service: lambda service:
keyboard.tap_key(keyboard.volume_up_key)) keyboard.tap_key(keyboard.volume_up_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_VOLUME_DOWN, hass.services.register(DOMAIN, SERVICE_VOLUME_DOWN,
lambda service: lambda service:
keyboard.tap_key(keyboard.volume_down_key)) keyboard.tap_key(keyboard.volume_down_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE,
lambda service: lambda service:
keyboard.tap_key(keyboard.volume_mute_key)) keyboard.tap_key(keyboard.volume_mute_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE,
lambda service: lambda service:
keyboard.tap_key(keyboard.media_play_pause_key)) keyboard.tap_key(keyboard.media_play_pause_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, hass.services.register(DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
lambda service: lambda service:
keyboard.tap_key(keyboard.media_next_track_key)) keyboard.tap_key(keyboard.media_next_track_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, hass.services.register(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK,
lambda service: lambda service:
keyboard.tap_key(keyboard.media_prev_track_key)) keyboard.tap_key(keyboard.media_prev_track_key),
schema=TAP_KEY_SCHEMA)
return True return True

View file

@ -32,6 +32,9 @@ PHUE_CONFIG_FILE = "phue.conf"
_CONFIGURING = {} _CONFIGURING = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Track previously setup bridges
_CONFIGURED_BRIDGES = {}
def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE): def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
"""Attempt to detect host based on existing configuration.""" """Attempt to detect host based on existing configuration."""
@ -68,7 +71,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
return False return False
# Only act if we are not already configuring this host # Only act if we are not already configuring this host
if host in _CONFIGURING: if host in _CONFIGURING or \
socket.gethostbyname(host) in _CONFIGURED_BRIDGES:
return return
setup_bridge(host, hass, add_devices_callback, filename, allow_unreachable) setup_bridge(host, hass, add_devices_callback, filename, allow_unreachable)
@ -142,6 +146,7 @@ def setup_bridge(host, hass, add_devices_callback, filename,
if new_lights: if new_lights:
add_devices_callback(new_lights) add_devices_callback(new_lights)
_CONFIGURED_BRIDGES[socket.gethostbyname(host)] = True
update_lights() update_lights()

View file

@ -19,7 +19,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup a Hyperion server remote.""" """Setup a Hyperion server remote."""
host = config.get(CONF_HOST, None) host = config.get(CONF_HOST, None)
port = config.get("port", 19444) port = config.get("port", 19444)
device = Hyperion(host, port) device = Hyperion(config.get('name', host), host, port)
if device.setup(): if device.setup():
add_devices_callback([device]) add_devices_callback([device])
return True return True
@ -30,11 +30,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class Hyperion(Light): class Hyperion(Light):
"""Representation of a Hyperion remote.""" """Representation of a Hyperion remote."""
def __init__(self, host, port): def __init__(self, name, host, port):
"""Initialize the light.""" """Initialize the light."""
self._host = host self._host = host
self._port = port self._port = port
self._name = host self._name = name
self._is_available = True self._is_available = True
self._rgb_color = [255, 255, 255] self._rgb_color = [255, 255, 255]
@ -75,7 +75,8 @@ class Hyperion(Light):
"""Get the hostname of the remote.""" """Get the hostname of the remote."""
response = self.json_request({"command": "serverinfo"}) response = self.json_request({"command": "serverinfo"})
if response: if response:
self._name = response["info"]["hostname"] if self._name == self._host:
self._name = response["info"]["hostname"]
return True return True
return False return False

View file

@ -209,6 +209,8 @@ class LIFXLight(Light):
brightness = self._bri brightness = self._bri
if ATTR_COLOR_TEMP in kwargs: if ATTR_COLOR_TEMP in kwargs:
# pylint: disable=fixme
# TODO: Use color_temperature_mired_to_kelvin from util.color
kelvin = int(((TEMP_MAX - TEMP_MIN) * kelvin = int(((TEMP_MAX - TEMP_MIN) *
(kwargs[ATTR_COLOR_TEMP] - TEMP_MIN_HASS) / (kwargs[ATTR_COLOR_TEMP] - TEMP_MIN_HASS) /
(TEMP_MAX_HASS - TEMP_MIN_HASS)) + TEMP_MIN) (TEMP_MAX_HASS - TEMP_MIN_HASS)) + TEMP_MIN)

View file

@ -6,8 +6,8 @@ https://home-assistant.io/components/light.mysensors/
""" """
import logging import logging
from homeassistant.components.light import ( from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_RGB_COLOR,
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light) Light)
from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON
from homeassistant.loader import get_component from homeassistant.loader import get_component
from homeassistant.util.color import rgb_hex_to_rgb_list from homeassistant.util.color import rgb_hex_to_rgb_list
@ -100,15 +100,20 @@ class MySensorsLight(Light):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return device specific state attributes.""" """Return device specific state attributes."""
device_attr = { address = getattr(self.gateway, 'server_address', None)
self.mysensors.ATTR_PORT: self.gateway.port, if address:
device = '{}:{}'.format(address[0], address[1])
else:
device = self.gateway.port
attr = {
self.mysensors.ATTR_DEVICE: device,
self.mysensors.ATTR_NODE_ID: self.node_id, self.mysensors.ATTR_NODE_ID: self.node_id,
self.mysensors.ATTR_CHILD_ID: self.child_id, self.mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level, ATTR_BATTERY_LEVEL: self.battery_level,
} }
for value_type, value in self._values.items(): for value_type, value in self._values.items():
device_attr[self.gateway.const.SetReq(value_type).name] = value attr[self.gateway.const.SetReq(value_type).name] = value
return device_attr return attr
@property @property
def available(self): def available(self):

View file

@ -72,8 +72,7 @@ class VeraLight(VeraDevice, Light):
last_tripped = self.vera_device.last_trip last_tripped = self.vera_device.last_trip
if last_tripped is not None: if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped)) utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
utc_time)
else: else:
attr[ATTR_LAST_TRIP_TIME] = None attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped tripped = self.vera_device.is_tripped

View file

@ -6,10 +6,14 @@ https://home-assistant.io/components/light.wink/
""" """
import logging import logging
from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
Light, ATTR_RGB_COLOR
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import color as color_util
from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
REQUIREMENTS = ['python-wink==0.6.4'] REQUIREMENTS = ['python-wink==0.7.4']
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@ -35,7 +39,11 @@ class WinkLight(Light):
"""Representation of a Wink light.""" """Representation of a Wink light."""
def __init__(self, wink): def __init__(self, wink):
"""Initialize the light.""" """
Initialize the light.
:type wink: pywink.devices.standard.bulb.WinkBulb
"""
self.wink = wink self.wink = wink
@property @property
@ -63,15 +71,41 @@ class WinkLight(Light):
"""True if connection == True.""" """True if connection == True."""
return self.wink.available return self.wink.available
@property
def xy_color(self):
"""Current bulb color in CIE 1931 (XY) color space."""
if not self.wink.supports_xy_color():
return None
return self.wink.color_xy()
@property
def color_temp(self):
"""Current bulb color in degrees Kelvin."""
if not self.wink.supports_temperature():
return None
return color_util.color_temperature_kelvin_to_mired(
self.wink.color_temperature_kelvin())
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the switch on.""" """Turn the switch on."""
brightness = kwargs.get(ATTR_BRIGHTNESS) brightness = kwargs.get(ATTR_BRIGHTNESS)
rgb_color = kwargs.get(ATTR_RGB_COLOR)
color_temp_mired = kwargs.get(ATTR_COLOR_TEMP)
if brightness is not None: state_kwargs = {
self.wink.set_state(True, brightness=brightness / 255) }
else:
self.wink.set_state(True) if rgb_color:
state_kwargs['color_xy'] = color_util.color_RGB_to_xy(*rgb_color)
if color_temp_mired:
state_kwargs['color_kelvin'] = mired_to_kelvin(color_temp_mired)
if brightness:
state_kwargs['brightness'] = brightness / 255.0
self.wink.set_state(True, **state_kwargs)
def turn_off(self): def turn_off(self):
"""Turn the switch off.""" """Turn the switch off."""
@ -79,4 +113,4 @@ class WinkLight(Light):
def update(self): def update(self):
"""Update state of the light.""" """Update state of the light."""
self.wink.update_state() self.wink.update_state(require_desired_state_fulfilled=True)

View file

@ -9,25 +9,23 @@ https://home-assistant.io/components/light.zwave/
from threading import Timer from threading import Timer
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light
from homeassistant.components.zwave import ( from homeassistant.components import zwave
ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_SWITCH_MULTILEVEL, GENRE_USER,
NETWORK, TYPE_BYTE, ZWaveDeviceEntity)
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and add Z-Wave lights.""" """Find and add Z-Wave lights."""
if discovery_info is None or NETWORK is None: if discovery_info is None or zwave.NETWORK is None:
return return
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]] node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]] value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
if value.command_class != COMMAND_CLASS_SWITCH_MULTILEVEL: if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
return return
if value.type != TYPE_BYTE: if value.type != zwave.TYPE_BYTE:
return return
if value.genre != GENRE_USER: if value.genre != zwave.GENRE_USER:
return return
value.set_change_verified(False) value.set_change_verified(False)
@ -42,7 +40,7 @@ def brightness_state(value):
return 255, STATE_OFF return 255, STATE_OFF
class ZwaveDimmer(ZWaveDeviceEntity, Light): class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
"""Representation of a Z-Wave dimmer.""" """Representation of a Z-Wave dimmer."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
@ -51,7 +49,7 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light):
from openzwave.network import ZWaveNetwork from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._brightness, self._state = brightness_state(value) self._brightness, self._state = brightness_state(value)

View file

@ -8,10 +8,13 @@ from datetime import timedelta
import logging import logging
import os import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK) STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)
@ -33,6 +36,11 @@ DISCOVERY_PLATFORMS = {
verisure.DISCOVER_LOCKS: 'verisure' verisure.DISCOVER_LOCKS: 'verisure'
} }
LOCK_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_CODE): cv.string,
})
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -75,10 +83,7 @@ def setup(hass, config):
"""Handle calls to the lock services.""" """Handle calls to the lock services."""
target_locks = component.extract_from_service(service) target_locks = component.extract_from_service(service)
if ATTR_CODE not in service.data: code = service.data.get(ATTR_CODE)
code = None
else:
code = service.data[ATTR_CODE]
for item in target_locks: for item in target_locks:
if service.service == SERVICE_LOCK: if service.service == SERVICE_LOCK:
@ -92,10 +97,11 @@ def setup(hass, config):
descriptions = load_yaml_config_file( descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml')) os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_UNLOCK, handle_lock_service, hass.services.register(DOMAIN, SERVICE_UNLOCK, handle_lock_service,
descriptions.get(SERVICE_UNLOCK)) descriptions.get(SERVICE_UNLOCK),
schema=LOCK_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_LOCK, handle_lock_service, hass.services.register(DOMAIN, SERVICE_LOCK, handle_lock_service,
descriptions.get(SERVICE_LOCK)) descriptions.get(SERVICE_LOCK),
schema=LOCK_SERVICE_SCHEMA)
return True return True

View file

@ -9,7 +9,7 @@ import logging
from homeassistant.components.lock import LockDevice from homeassistant.components.lock import LockDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.6.4'] REQUIREMENTS = ['python-wink==0.7.4']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View file

@ -9,6 +9,8 @@ import re
from datetime import timedelta from datetime import timedelta
from itertools import groupby from itertools import groupby
import voluptuous as vol
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components import recorder, sun from homeassistant.components import recorder, sun
from homeassistant.const import ( from homeassistant.const import (
@ -18,6 +20,7 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.core import State from homeassistant.core import State
from homeassistant.helpers.entity import split_entity_id from homeassistant.helpers.entity import split_entity_id
from homeassistant.helpers import template from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
DOMAIN = "logbook" DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'http'] DEPENDENCIES = ['recorder', 'http']
@ -39,6 +42,13 @@ ATTR_MESSAGE = 'message'
ATTR_DOMAIN = 'domain' ATTR_DOMAIN = 'domain'
ATTR_ENTITY_ID = 'entity_id' ATTR_ENTITY_ID = 'entity_id'
LOG_MESSAGE_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_MESSAGE): cv.string,
vol.Optional(ATTR_DOMAIN): cv.slug,
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
})
def log_entry(hass, name, message, domain=None, entity_id=None): def log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook.""" """Add an entry to the logbook."""
@ -58,19 +68,17 @@ def setup(hass, config):
"""Listen for download events to download files.""" """Listen for download events to download files."""
def log_message(service): def log_message(service):
"""Handle sending notification message service calls.""" """Handle sending notification message service calls."""
message = service.data.get(ATTR_MESSAGE) message = service.data[ATTR_MESSAGE]
name = service.data.get(ATTR_NAME) name = service.data[ATTR_NAME]
domain = service.data.get(ATTR_DOMAIN, None) domain = service.data.get(ATTR_DOMAIN)
entity_id = service.data.get(ATTR_ENTITY_ID, None) entity_id = service.data.get(ATTR_ENTITY_ID)
if not message or not name:
return
message = template.render(hass, message) message = template.render(hass, message)
log_entry(hass, name, message, domain, entity_id) log_entry(hass, name, message, domain, entity_id)
hass.http.register_path('GET', URL_LOGBOOK, _handle_get_logbook) hass.http.register_path('GET', URL_LOGBOOK, _handle_get_logbook)
hass.services.register(DOMAIN, 'log', log_message) hass.services.register(DOMAIN, 'log', log_message,
schema=LOG_MESSAGE_SCHEMA)
return True return True
@ -79,7 +87,7 @@ def _handle_get_logbook(handler, path_match, data):
date_str = path_match.group('date') date_str = path_match.group('date')
if date_str: if date_str:
start_date = dt_util.date_str_to_date(date_str) start_date = dt_util.parse_date(date_str)
if start_date is None: if start_date is None:
handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST) handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST)
@ -114,7 +122,7 @@ class Entry(object):
def as_dict(self): def as_dict(self):
"""Convert entry to a dict to be used within JSON.""" """Convert entry to a dict to be used within JSON."""
return { return {
'when': dt_util.datetime_to_str(self.when), 'when': self.when,
'name': self.name, 'name': self.name,
'message': self.message, 'message': self.message,
'domain': self.domain, 'domain': self.domain,

View file

@ -61,6 +61,7 @@ ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name' ATTR_APP_NAME = 'app_name'
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands' ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
ATTR_INPUT_SOURCE = 'source' ATTR_INPUT_SOURCE = 'source'
ATTR_INPUT_SOURCE_LIST = 'source_list'
MEDIA_TYPE_MUSIC = 'music' MEDIA_TYPE_MUSIC = 'music'
MEDIA_TYPE_TVSHOW = 'tvshow' MEDIA_TYPE_TVSHOW = 'tvshow'
@ -94,6 +95,7 @@ SERVICE_TO_METHOD = {
SERVICE_MEDIA_PAUSE: 'media_pause', SERVICE_MEDIA_PAUSE: 'media_pause',
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track', SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track', SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
SERVICE_SELECT_SOURCE: 'select_source'
} }
ATTR_TO_PROPERTY = [ ATTR_TO_PROPERTY = [
@ -116,6 +118,7 @@ ATTR_TO_PROPERTY = [
ATTR_APP_NAME, ATTR_APP_NAME,
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_SUPPORTED_MEDIA_COMMANDS,
ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE,
ATTR_INPUT_SOURCE_LIST,
] ]
# Service call validation schemas # Service call validation schemas
@ -473,6 +476,11 @@ class MediaPlayerDevice(Entity):
"""Name of the current input source.""" """Name of the current input source."""
return None return None
@property
def source_list(self):
"""List of available input sources."""
return None
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag media commands that are supported.""" """Flag media commands that are supported."""

View file

@ -24,7 +24,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
]) ])
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 = \ YOUTUBE_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
@ -208,7 +208,8 @@ class DemoMusicPlayer(AbstractDemoPlayer):
@property @property
def media_image_url(self): def media_image_url(self):
"""Return the image url of current playing media.""" """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 @property
def media_title(self): def media_title(self):
@ -287,7 +288,7 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
@property @property
def media_image_url(self): def media_image_url(self):
"""Return the image url of current playing media.""" """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 @property
def media_title(self): def media_title(self):

View file

@ -10,14 +10,16 @@ import socket
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_SET, MediaPlayerDevice) SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_PLAYLIST,
MediaPlayerDevice)
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-mpd2==0.5.5'] REQUIREMENTS = ['python-mpd2==0.5.5']
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_PLAY_MEDIA
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -64,7 +66,7 @@ class MpdDevice(MediaPlayerDevice):
"""Representation of a MPD server.""" """Representation of a MPD server."""
# MPD confuses pylint # MPD confuses pylint
# pylint: disable=no-member, abstract-method # pylint: disable=no-member, too-many-public-methods, abstract-method
def __init__(self, server, port, location, password): def __init__(self, server, port, location, password):
"""Initialize the MPD device.""" """Initialize the MPD device."""
import mpd import mpd
@ -203,3 +205,14 @@ class MpdDevice(MediaPlayerDevice):
def media_previous_track(self): def media_previous_track(self):
"""Service to send the MPD the command for previous track.""" """Service to send the MPD the command for previous track."""
self.client.previous() self.client.previous()
def play_media(self, media_type, media_id):
"""Send the media player the command for playing a playlist."""
_LOGGER.info(str.format("Playing playlist: {0}", media_id))
if media_type == MEDIA_TYPE_PLAYLIST:
self.client.clear()
self.client.load(media_id)
self.client.play()
else:
_LOGGER.error(str.format("Invalid media type. Expected: {0}",
MEDIA_TYPE_PLAYLIST))

View file

@ -18,6 +18,7 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
STATE_UNKNOWN) STATE_UNKNOWN)
from homeassistant.loader import get_component from homeassistant.loader import get_component
from homeassistant.helpers.event import (track_utc_time_change)
REQUIREMENTS = ['plexapi==1.1.0'] REQUIREMENTS = ['plexapi==1.1.0']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
@ -113,6 +114,7 @@ def setup_plexserver(host, token, hass, add_devices_callback):
plex_clients = {} plex_clients = {}
plex_sessions = {} plex_sessions = {}
track_utc_time_change(hass, lambda now: update_devices(), second=30)
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_devices(): def update_devices():

View file

@ -6,6 +6,7 @@ https://home-assistant.io/components/media_player.sonos/
""" """
import datetime import datetime
import logging import logging
import socket
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
@ -13,7 +14,7 @@ from homeassistant.components.media_player import (
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice) MediaPlayerDevice)
from homeassistant.const import ( from homeassistant.const import (
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_OFF)
REQUIREMENTS = ['SoCo==0.11.1'] REQUIREMENTS = ['SoCo==0.11.1']
@ -36,11 +37,13 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Sonos platform.""" """Setup the Sonos platform."""
import soco import soco
import socket
if discovery_info: if discovery_info:
add_devices([SonosDevice(hass, soco.SoCo(discovery_info))]) player = soco.SoCo(discovery_info)
return True if player.is_visible:
add_devices([SonosDevice(hass, player)])
return True
return False
players = None players = None
hosts = config.get('hosts', None) hosts = config.get('hosts', None)
@ -138,9 +141,14 @@ class SonosDevice(MediaPlayerDevice):
"""Retrieve latest state.""" """Retrieve latest state."""
self._name = self._player.get_speaker_info()['zone_name'].replace( self._name = self._player.get_speaker_info()['zone_name'].replace(
' (R)', '').replace(' (L)', '') ' (R)', '').replace(' (L)', '')
self._status = self._player.get_current_transport_info().get(
'current_transport_state') if self.available:
self._trackinfo = self._player.get_current_track_info() self._status = self._player.get_current_transport_info().get(
'current_transport_state')
self._trackinfo = self._player.get_current_track_info()
else:
self._status = STATE_OFF
self._trackinfo = {}
@property @property
def volume_level(self): def volume_level(self):
@ -253,3 +261,15 @@ class SonosDevice(MediaPlayerDevice):
def play_media(self, media_type, media_id): def play_media(self, media_type, media_id):
"""Send the play_media command to the media player.""" """Send the play_media command to the media player."""
self._player.play_uri(media_id) self._player.play_uri(media_id)
@property
def available(self):
"""Return True if player is reachable, False otherwise."""
try:
sock = socket.create_connection(
address=(self._player.ip_address, 1443),
timeout=3)
sock.close()
return True
except socket.error:
return False

View file

@ -0,0 +1,262 @@
"""
Support for interface with an LG WebOS TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.webostv/
"""
import logging
from datetime import timedelta
from urllib.parse import urlparse
import homeassistant.util as util
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL,
MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
from homeassistant.loader import get_component
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/TheRealLink/pylgtv'
'/archive/v0.1.2.zip'
'#pylgtv==0.1.2']
SUPPORT_WEBOSTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | \
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY_MEDIA
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the LG WebOS TV platform."""
if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname
else:
host = config.get(CONF_HOST, None)
if host is None:
_LOGGER.error('No host found in configuration')
return False
# Only act if we are not already configuring this host
if host in _CONFIGURING:
return
setup_tv(host, hass, add_devices)
def setup_tv(host, hass, add_devices):
"""Setup a phue bridge based on host parameter."""
from pylgtv import WebOsClient
from pylgtv import PyLGTVPairException
client = WebOsClient(host)
if not client.is_registered():
if host in _CONFIGURING:
# Try to pair.
try:
client.register()
except PyLGTVPairException:
_LOGGER.warning(
'Connected to LG WebOS TV at %s but not paired.', host)
return
except ConnectionRefusedError:
_LOGGER.error('Unable to connect to host %s.', host)
return
else:
# Not registered, request configuration.
_LOGGER.warning('LG WebOS TV at %s needs to be paired.', host)
request_configuration(host, hass, add_devices)
return
# If we came here and configuring this host, mark as done.
if client.is_registered() and host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator.request_done(request_id)
add_devices([LgWebOSDevice(host)])
def request_configuration(host, hass, add_devices):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING[host], 'Failed to pair, please try again.')
return
# pylint: disable=unused-argument
def lgtv_configuration_callback(data):
"""The actions to do when our configuration callback is called."""
setup_tv(host, hass, add_devices)
_CONFIGURING[host] = configurator.request_config(
hass, 'LG WebOS TV', lgtv_configuration_callback,
description='Click start and accept the pairing request on your tv.',
description_image='/static/images/config_webos.png',
submit_caption='Start pairing request'
)
# pylint: disable=abstract-method
# pylint: disable=too-many-instance-attributes
class LgWebOSDevice(MediaPlayerDevice):
"""Representation of a LG WebOS TV."""
# pylint: disable=too-many-public-methods
def __init__(self, host):
"""Initialize the webos device."""
from pylgtv import WebOsClient
self._client = WebOsClient(host)
self._name = 'LG WebOS TV Remote'
# Assume that the TV is not muted
self._muted = False
# Assume that the TV is in Play mode
self._playing = True
self._volume = 0
self._current_source = None
self._current_source_id = None
self._source_list = None
self._source_label_list = None
self._state = STATE_UNKNOWN
self._app_list = None
self.update()
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update(self):
"""Retrieve the latest data."""
try:
self._state = STATE_PLAYING
self._muted = self._client.get_muted()
self._volume = self._client.get_volume()
self._current_source_id = self._client.get_input()
self._source_list = {}
self._source_label_list = []
self._app_list = {}
for app in self._client.get_apps():
self._app_list[app['id']] = app
for source in self._client.get_inputs():
self._source_list[source['label']] = source
self._app_list[source['appId']] = source
self._source_label_list.append(source['label'])
if source['appId'] == self._current_source_id:
self._current_source = source['label']
except ConnectionRefusedError:
self._state = STATE_OFF
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._muted
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume / 100.0
@property
def source(self):
"""Return the current input source."""
return self._current_source
@property
def source_list(self):
"""List of available input sources."""
return self._source_label_list
@property
def media_content_type(self):
"""Content type of current playing media."""
return MEDIA_TYPE_CHANNEL
@property
def media_image_url(self):
"""Image url of current playing media."""
return self._app_list[self._current_source_id]['icon']
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_WEBOSTV
def turn_off(self):
"""Turn off media player."""
self._client.power_off()
def volume_up(self):
"""Volume up the media player."""
self._client.volume_up()
def volume_down(self):
"""Volume down media player."""
self._client.volume_down()
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
tv_volume = volume * 100
self._client.set_volume(tv_volume)
def mute_volume(self, mute):
"""Send mute command."""
self._muted = mute
self._client.set_mute(mute)
def media_play_pause(self):
"""Simulate play pause media player."""
if self._playing:
self.media_pause()
else:
self.media_play()
def select_source(self, source):
"""Select input source."""
self._current_source_id = self._source_list[source]['appId']
self._current_source = self._source_list[source]['label']
self._client.set_input(self._source_list[source]['id'])
def media_play(self):
"""Send play command."""
self._playing = True
self._state = STATE_PLAYING
self._client.play()
def media_pause(self):
"""Send media pause command to media player."""
self._playing = False
self._state = STATE_PAUSED
self._client.pause()
def media_next_track(self):
"""Send next track command."""
self._client.fast_forward()
def media_previous_track(self):
"""Send the previous track command."""
self._client.rewind()

View file

@ -8,13 +8,15 @@ import logging
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice) SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
REQUIREMENTS = ['rxv==0.1.11'] REQUIREMENTS = ['rxv==0.1.11']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_YAMAHA = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_YAMAHA = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -34,6 +36,8 @@ class YamahaDevice(MediaPlayerDevice):
self._muted = False self._muted = False
self._volume = 0 self._volume = 0
self._pwstate = STATE_OFF self._pwstate = STATE_OFF
self._current_source = None
self._source_list = None
self.update() self.update()
self._name = name self._name = name
@ -45,6 +49,8 @@ class YamahaDevice(MediaPlayerDevice):
self._pwstate = STATE_OFF self._pwstate = STATE_OFF
self._muted = self._receiver.mute self._muted = self._receiver.mute
self._volume = (self._receiver.volume/100) + 1 self._volume = (self._receiver.volume/100) + 1
self._current_source = self._receiver.input
self._source_list = list(self._receiver.inputs().keys())
@property @property
def name(self): def name(self):
@ -66,6 +72,16 @@ class YamahaDevice(MediaPlayerDevice):
"""Boolean if volume is currently muted.""" """Boolean if volume is currently muted."""
return self._muted return self._muted
@property
def source(self):
"""Return the current input source."""
return self._current_source
@property
def source_list(self):
"""List of available input sources."""
return self._source_list
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag of media commands that are supported.""" """Flag of media commands that are supported."""
@ -89,3 +105,7 @@ class YamahaDevice(MediaPlayerDevice):
"""Turn the media player on.""" """Turn the media player on."""
self._receiver.on = True self._receiver.on = True
self._volume = (self._receiver.volume/100) + 1 self._volume = (self._receiver.volume/100) + 1
def select_source(self, source):
"""Select input source."""
self._receiver.input = source

View file

@ -1,5 +1,5 @@
""" """
Support for MQTT message handling.. Support for MQTT message handling.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt/ https://home-assistant.io/components/mqtt/
@ -102,13 +102,13 @@ MQTT_BASE_PLATFORM_SCHEMA = vol.Schema({
vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
}) })
# Sensor type platforms subscribe to mqtt events # Sensor type platforms subscribe to MQTT events
MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({ MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_STATE_TOPIC): valid_subscribe_topic, vol.Required(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}) })
# Switch type platforms publish to mqtt and may subscribe # Switch type platforms publish to MQTT and may subscribe
MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({ MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,

View file

@ -1,4 +1,9 @@
"""MQTT server.""" """
Support for a local MQTT broker.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt/#use-the-embedded-broker
"""
import asyncio import asyncio
import logging import logging
import tempfile import tempfile

View file

@ -5,33 +5,36 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/ https://home-assistant.io/components/sensor.mysensors/
""" """
import logging import logging
import socket
import homeassistant.bootstrap as bootstrap import homeassistant.bootstrap as bootstrap
from homeassistant.const import ( from homeassistant.const import (ATTR_DISCOVERED, ATTR_SERVICE,
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED, TEMP_CELCIUS, EVENT_HOMEASSISTANT_STOP,
CONF_OPTIMISTIC) EVENT_PLATFORM_DISCOVERED, TEMP_CELSIUS)
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
CONF_GATEWAYS = 'gateways' CONF_GATEWAYS = 'gateways'
CONF_PORT = 'port' CONF_DEVICE = 'device'
CONF_DEBUG = 'debug' CONF_DEBUG = 'debug'
CONF_PERSISTENCE = 'persistence' CONF_PERSISTENCE = 'persistence'
CONF_PERSISTENCE_FILE = 'persistence_file' CONF_PERSISTENCE_FILE = 'persistence_file'
CONF_VERSION = 'version' CONF_VERSION = 'version'
CONF_BAUD_RATE = 'baud_rate' CONF_BAUD_RATE = 'baud_rate'
CONF_TCP_PORT = 'tcp_port'
DEFAULT_VERSION = '1.4' DEFAULT_VERSION = '1.4'
DEFAULT_BAUD_RATE = 115200 DEFAULT_BAUD_RATE = 115200
DEFAULT_TCP_PORT = 5003
DOMAIN = 'mysensors' DOMAIN = 'mysensors'
DEPENDENCIES = [] DEPENDENCIES = []
REQUIREMENTS = [ REQUIREMENTS = [
'https://github.com/theolind/pymysensors/archive/' 'https://github.com/theolind/pymysensors/archive/'
'f0c928532167fb24823efa793ec21ca646fd37a6.zip#pymysensors==0.5'] 'cc5d0b325e13c2b623fa934f69eea7cd4555f110.zip#pymysensors==0.6']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_NODE_ID = 'node_id' ATTR_NODE_ID = 'node_id'
ATTR_CHILD_ID = 'child_id' ATTR_CHILD_ID = 'child_id'
ATTR_PORT = 'port' ATTR_DEVICE = 'device'
GATEWAYS = None GATEWAYS = None
@ -49,30 +52,39 @@ DISCOVERY_COMPONENTS = [
] ]
def setup(hass, config): def setup(hass, config): # pylint: disable=too-many-locals
"""Setup the MySensors component.""" """Setup the MySensors component."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_GATEWAYS]}, {DOMAIN: [CONF_GATEWAYS]},
_LOGGER): _LOGGER):
return False return False
if not all(CONF_PORT in gateway if not all(CONF_DEVICE in gateway
for gateway in config[DOMAIN][CONF_GATEWAYS]): for gateway in config[DOMAIN][CONF_GATEWAYS]):
_LOGGER.error('Missing required configuration items ' _LOGGER.error('Missing required configuration items '
'in %s: %s', DOMAIN, CONF_PORT) 'in %s: %s', DOMAIN, CONF_DEVICE)
return False return False
import mysensors.mysensors as mysensors import mysensors.mysensors as mysensors
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION)) version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS) is_metric = (hass.config.temperature_unit == TEMP_CELSIUS)
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
def setup_gateway(port, persistence, persistence_file, version, baud_rate): def setup_gateway(device, persistence_file, baud_rate, tcp_port):
"""Return gateway after setup of the gateway.""" """Return gateway after setup of the gateway."""
gateway = mysensors.SerialGateway(port, event_callback=None, try:
persistence=persistence, socket.inet_aton(device)
persistence_file=persistence_file, # valid ip address
protocol_version=version, gateway = mysensors.TCPGateway(
baud=baud_rate) device, event_callback=None, persistence=persistence,
persistence_file=persistence_file, protocol_version=version,
port=tcp_port)
except OSError:
# invalid ip address
gateway = mysensors.SerialGateway(
device, event_callback=None, persistence=persistence,
persistence_file=persistence_file, protocol_version=version,
baud=baud_rate)
gateway.metric = is_metric gateway.metric = is_metric
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False) gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
optimistic = config[DOMAIN].get(CONF_OPTIMISTIC, False) optimistic = config[DOMAIN].get(CONF_OPTIMISTIC, False)
@ -93,22 +105,22 @@ def setup(hass, config):
return gateway return gateway
# Setup all ports from config # Setup all devices from config
global GATEWAYS global GATEWAYS
GATEWAYS = {} GATEWAYS = {}
conf_gateways = config[DOMAIN][CONF_GATEWAYS] conf_gateways = config[DOMAIN][CONF_GATEWAYS]
if isinstance(conf_gateways, dict): if isinstance(conf_gateways, dict):
conf_gateways = [conf_gateways] conf_gateways = [conf_gateways]
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
for index, gway in enumerate(conf_gateways): for index, gway in enumerate(conf_gateways):
port = gway[CONF_PORT] device = gway[CONF_DEVICE]
persistence_file = gway.get( persistence_file = gway.get(
CONF_PERSISTENCE_FILE, CONF_PERSISTENCE_FILE,
hass.config.path('mysensors{}.pickle'.format(index + 1))) hass.config.path('mysensors{}.pickle'.format(index + 1)))
baud_rate = gway.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE) baud_rate = gway.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE)
GATEWAYS[port] = setup_gateway( tcp_port = gway.get(CONF_TCP_PORT, DEFAULT_TCP_PORT)
port, persistence, persistence_file, version, baud_rate) GATEWAYS[device] = setup_gateway(
device, persistence_file, baud_rate, tcp_port)
for (component, discovery_service) in DISCOVERY_COMPONENTS: for (component, discovery_service) in DISCOVERY_COMPONENTS:
# Ensure component is loaded # Ensure component is loaded
@ -139,7 +151,7 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
if key in devices: if key in devices:
devices[key].update_ha_state(True) devices[key].update_ha_state(True)
continue continue
name = '{} {}.{}'.format( name = '{} {} {}'.format(
gateway.sensors[node_id].sketch_name, node_id, child.id) gateway.sensors[node_id].sketch_name, node_id, child.id)
if isinstance(entity_class, dict): if isinstance(entity_class, dict):
device_class = entity_class[child.type] device_class = entity_class[child.type]

View file

@ -4,6 +4,9 @@ Support for Nest thermostats.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/thermostat.nest/ https://home-assistant.io/components/thermostat.nest/
""" """
import logging
import socket
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
@ -20,14 +23,27 @@ CONFIG_SCHEMA = vol.Schema({
}) })
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
def devices():
"""Generator returning list of devices and their location."""
try:
for structure in NEST.structures:
for device in structure.devices:
yield (structure, device)
except socket.error:
_LOGGER.error("Connection error logging into the nest web service.")
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup(hass, config): def setup(hass, config):
"""Setup the Nest thermostat component.""" """Setup the Nest thermostat component."""
global NEST global NEST
username = config[DOMAIN].get(CONF_USERNAME) conf = config[DOMAIN]
password = config[DOMAIN].get(CONF_PASSWORD) username = conf[CONF_USERNAME]
password = conf[CONF_PASSWORD]
import nest import nest

View file

@ -8,11 +8,13 @@ from functools import partial
import logging import logging
import os import os
import voluptuous as vol
import homeassistant.bootstrap as bootstrap import homeassistant.bootstrap as bootstrap
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_per_platform, template from homeassistant.helpers import config_per_platform, template
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
DOMAIN = "notify" DOMAIN = "notify"
@ -32,6 +34,13 @@ ATTR_DATA = 'data'
SERVICE_NOTIFY = "notify" SERVICE_NOTIFY = "notify"
NOTIFY_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE, default=ATTR_TITLE_DEFAULT): cv.string,
vol.Optional(ATTR_TARGET): cv.string,
vol.Optional(ATTR_DATA): dict, # nobody seems to be using this (yet)
})
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -71,13 +80,7 @@ def setup(hass, config):
def notify_message(notify_service, call): def notify_message(notify_service, call):
"""Handle sending notification message service calls.""" """Handle sending notification message service calls."""
message = call.data.get(ATTR_MESSAGE) message = call.data[ATTR_MESSAGE]
if message is None:
_LOGGER.error(
'Received call to %s without attribute %s',
call.service, ATTR_MESSAGE)
return
title = template.render( title = template.render(
hass, call.data.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)) hass, call.data.get(ATTR_TITLE, ATTR_TITLE_DEFAULT))
@ -91,7 +94,8 @@ def setup(hass, config):
service_call_handler = partial(notify_message, notify_service) service_call_handler = partial(notify_message, notify_service)
service_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY) service_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY)
hass.services.register(DOMAIN, service_notify, service_call_handler, hass.services.register(DOMAIN, service_notify, service_call_handler,
descriptions.get(SERVICE_NOTIFY)) descriptions.get(SERVICE_NOTIFY),
schema=NOTIFY_SERVICE_SCHEMA)
success = True success = True
return success return success

View file

@ -44,12 +44,12 @@ class FileNotificationService(BaseNotificationService):
if os.stat(self.filepath).st_size == 0: if os.stat(self.filepath).st_size == 0:
title = '{} notifications (Log started: {})\n{}\n'.format( title = '{} notifications (Log started: {})\n{}\n'.format(
kwargs.get(ATTR_TITLE), kwargs.get(ATTR_TITLE),
dt_util.strip_microseconds(dt_util.utcnow()), dt_util.utcnow().isoformat(),
'-' * 80) '-' * 80)
file.write(title) file.write(title)
if self.add_timestamp == 1: if self.add_timestamp == 1:
text = '{} {}\n'.format(dt_util.utcnow(), message) text = '{} {}\n'.format(dt_util.utcnow().isoformat(), message)
file.write(text) file.write(text)
else: else:
text = '{}\n'.format(message) text = '{}\n'.format(message)

View file

@ -0,0 +1,62 @@
"""
LG WebOS TV notification service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.webostv/
"""
import logging
from homeassistant.components.notify import (BaseNotificationService, DOMAIN)
from homeassistant.const import (CONF_HOST, CONF_NAME)
from homeassistant.helpers import validate_config
_LOGGER = logging.getLogger(__name__)
def get_service(hass, config):
"""Return the notify service."""
if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_HOST, CONF_NAME]},
_LOGGER):
return None
host = config.get(CONF_HOST, None)
if not host:
_LOGGER.error('No host provided.')
return None
from pylgtv import WebOsClient
from pylgtv import PyLGTVPairException
client = WebOsClient(host)
try:
client.register()
except PyLGTVPairException:
_LOGGER.error('Pairing failed.')
return None
except ConnectionRefusedError:
_LOGGER.error('Host unreachable.')
return None
return LgWebOSNotificationService(client)
# pylint: disable=too-few-public-methods
class LgWebOSNotificationService(BaseNotificationService):
"""Implement the notification service for LG WebOS TV."""
def __init__(self, client):
"""Initialize the service."""
self._client = client
def send_message(self, message="", **kwargs):
"""Send a message to the tv."""
from pylgtv import PyLGTVPairException
try:
self._client.send_message(message)
except PyLGTVPairException:
_LOGGER.error('Pairing failed.')
except ConnectionRefusedError:
_LOGGER.error('Host unreachable.')

View file

@ -10,7 +10,10 @@ from homeassistant.components.notify import (
ATTR_TITLE, DOMAIN, BaseNotificationService) ATTR_TITLE, DOMAIN, BaseNotificationService)
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0'] REQUIREMENTS = ['sleekxmpp==1.3.1',
'dnspython3==1.12.0',
'pyasn1==0.1.9',
'pyasn1-modules==0.0.8']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -22,20 +25,23 @@ def get_service(hass, config):
_LOGGER): _LOGGER):
return None return None
return XmppNotificationService(config['sender'], return XmppNotificationService(
config['password'], config.get('sender'),
config['recipient']) config.get('password'),
config.get('recipient'),
config.get('tls', True))
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
class XmppNotificationService(BaseNotificationService): class XmppNotificationService(BaseNotificationService):
"""Implement the notification service for Jabber (XMPP).""" """Implement the notification service for Jabber (XMPP)."""
def __init__(self, sender, password, recipient): def __init__(self, sender, password, recipient, tls):
"""Initialize the service.""" """Initialize the service."""
self._sender = sender self._sender = sender
self._password = password self._password = password
self._recipient = recipient self._recipient = recipient
self._tls = tls
def send_message(self, message="", **kwargs): def send_message(self, message="", **kwargs):
"""Send a message to a user.""" """Send a message to a user."""
@ -43,10 +49,10 @@ class XmppNotificationService(BaseNotificationService):
data = "{}: {}".format(title, message) if title else message data = "{}: {}".format(title, message) if title else message
send_message(self._sender + '/home-assistant', self._password, send_message(self._sender + '/home-assistant', self._password,
self._recipient, data) self._recipient, self._tls, data)
def send_message(sender, password, recipient, message): def send_message(sender, password, recipient, use_tls, message):
"""Send a message over XMPP.""" """Send a message over XMPP."""
import sleekxmpp import sleekxmpp
@ -59,11 +65,11 @@ def send_message(sender, password, recipient, message):
logging.basicConfig(level=logging.ERROR) logging.basicConfig(level=logging.ERROR)
self.use_tls = True self.use_tls = use_tls
self.use_ipv6 = False self.use_ipv6 = False
self.add_event_handler('failed_auth', self.check_credentials) self.add_event_handler('failed_auth', self.check_credentials)
self.add_event_handler('session_start', self.start) self.add_event_handler('session_start', self.start)
self.connect() self.connect(use_tls=self.use_tls, use_ssl=False)
self.process() self.process()
def start(self, event): def start(self, event):

View file

@ -478,7 +478,7 @@ class Recorder(threading.Thread):
def _adapt_datetime(datetimestamp): def _adapt_datetime(datetimestamp):
"""Turn a datetime into an integer for in the DB.""" """Turn a datetime into an integer for in the DB."""
return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp() return dt_util.as_utc(datetimestamp).timestamp()
def _verify_instance(): def _verify_instance():

View file

@ -38,16 +38,22 @@ _LOGGER = logging.getLogger(__name__)
RFXOBJECT = None RFXOBJECT = None
def _validate_packetid(value): def validate_packetid(value):
"""Validate that value is a valid packet id for rfxtrx."""
if get_rfx_object(value): if get_rfx_object(value):
return value return value
else: else:
raise vol.Invalid('invalid packet id for {}'.format(value)) 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({ DEVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): cv.string, vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_PACKETID): _validate_packetid, vol.Required(ATTR_PACKETID): validate_packetid,
vol.Optional(ATTR_FIREEVENT, default=False): cv.boolean, vol.Optional(ATTR_FIREEVENT, default=False): cv.boolean,
}) })
@ -182,7 +188,7 @@ def apply_received_command(event):
# Check if entity exists or previously added automatically # Check if entity exists or previously added automatically
if device_id in RFX_DEVICES: if device_id in RFX_DEVICES:
_LOGGER.debug( _LOGGER.debug(
"EntityID: %s light_update. Command: %s", "EntityID: %s device_update. Command: %s",
device_id, device_id,
event.values['Command'] event.values['Command']
) )
@ -251,7 +257,7 @@ class RfxtrxDevice(Entity):
@property @property
def is_on(self): def is_on(self):
"""Return true if light is on.""" """Return true if device is on."""
return self._state return self._state
@property @property
@ -260,7 +266,7 @@ class RfxtrxDevice(Entity):
return True return True
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the light off.""" """Turn the device off."""
self._send_command("turn_off") self._send_command("turn_off")
def _send_command(self, command, brightness=0): def _send_command(self, command, brightness=0):

View file

@ -7,10 +7,13 @@ https://home-assistant.io/components/rollershutter/
import os import os
import logging import logging
import voluptuous as vol
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.components import group from homeassistant.components import group
from homeassistant.const import ( from homeassistant.const import (
SERVICE_MOVE_UP, SERVICE_MOVE_DOWN, SERVICE_STOP, SERVICE_MOVE_UP, SERVICE_MOVE_DOWN, SERVICE_STOP,
@ -33,6 +36,10 @@ _LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_POSITION = 'current_position' ATTR_CURRENT_POSITION = 'current_position'
ROLLERSHUTTER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_open(hass, entity_id=None): def is_open(hass, entity_id=None):
"""Return if the roller shutter is open based on the statemachine.""" """Return if the roller shutter is open based on the statemachine."""
@ -85,14 +92,16 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_MOVE_UP, hass.services.register(DOMAIN, SERVICE_MOVE_UP,
handle_rollershutter_service, handle_rollershutter_service,
descriptions.get(SERVICE_MOVE_UP)) descriptions.get(SERVICE_MOVE_UP),
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MOVE_DOWN, hass.services.register(DOMAIN, SERVICE_MOVE_DOWN,
handle_rollershutter_service, handle_rollershutter_service,
descriptions.get(SERVICE_MOVE_DOWN)) descriptions.get(SERVICE_MOVE_DOWN),
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_STOP, hass.services.register(DOMAIN, SERVICE_STOP,
handle_rollershutter_service, handle_rollershutter_service,
descriptions.get(SERVICE_STOP)) descriptions.get(SERVICE_STOP),
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
return True return True

View file

@ -7,9 +7,12 @@ https://home-assistant.io/components/scene/
import logging import logging
from collections import namedtuple from collections import namedtuple
import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, CONF_PLATFORM) ATTR_ENTITY_ID, SERVICE_TURN_ON, CONF_PLATFORM)
from homeassistant.helpers import extract_domain_configs from homeassistant.helpers import extract_domain_configs
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
@ -19,6 +22,10 @@ STATE = 'scening'
CONF_ENTITIES = "entities" CONF_ENTITIES = "entities"
SCENE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
SceneConfig = namedtuple('SceneConfig', ['name', 'states']) SceneConfig = namedtuple('SceneConfig', ['name', 'states'])
@ -61,7 +68,8 @@ def setup(hass, config):
for scene in target_scenes: for scene in target_scenes:
scene.activate() scene.activate()
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service,
schema=SCENE_SERVICE_SCHEMA)
return True return True

View file

@ -109,6 +109,11 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(DOMAIN): {cv.slug: _SCRIPT_ENTRY_SCHEMA} vol.Required(DOMAIN): {cv.slug: _SCRIPT_ENTRY_SCHEMA}
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SCRIPT_SERVICE_SCHEMA = vol.Schema({})
SCRIPT_TURN_ONOFF_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_on(hass, entity_id): def is_on(hass, entity_id):
"""Return if the switch is on based on the statemachine.""" """Return if the switch is on based on the statemachine."""
@ -149,7 +154,8 @@ def setup(hass, config):
alias = cfg.get(CONF_ALIAS, object_id) alias = cfg.get(CONF_ALIAS, object_id)
script = Script(object_id, alias, cfg[CONF_SEQUENCE]) script = Script(object_id, alias, cfg[CONF_SEQUENCE])
component.add_entities((script,)) component.add_entities((script,))
hass.services.register(DOMAIN, object_id, service_handler) hass.services.register(DOMAIN, object_id, service_handler,
schema=SCRIPT_SERVICE_SCHEMA)
def turn_on_service(service): def turn_on_service(service):
"""Call a service to turn script on.""" """Call a service to turn script on."""
@ -168,10 +174,12 @@ def setup(hass, config):
for script in component.extract_from_service(service): for script in component.extract_from_service(service):
script.toggle() script.toggle()
hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on_service,
hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off_service) schema=SCRIPT_TURN_ONOFF_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service) hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
return True return True

View file

@ -98,12 +98,11 @@ class SCSGate:
from scsgate.tasks import GetStatusTask from scsgate.tasks import GetStatusTask
with self._devices_to_register_lock: with self._devices_to_register_lock:
if len(self._devices_to_register) == 0: while len(self._devices_to_register) != 0:
return _, device = self._devices_to_register.popitem()
_, device = self._devices_to_register.popitem() self._devices[device.scs_id] = device
self._devices[device.scs_id] = device self._device_being_registered = device.scs_id
self._device_being_registered = device.scs_id self._reactor.append_task(GetStatusTask(target=device.scs_id))
self._reactor.append_task(GetStatusTask(target=device.scs_id))
def is_device_registered(self, device_id): def is_device_registered(self, device_id):
"""Check whether a device is already registered or not.""" """Check whether a device is already registered or not."""

View file

@ -7,13 +7,13 @@ https://home-assistant.io/components/sensor.apcupsd/
import logging import logging
from homeassistant.components import apcupsd from homeassistant.components import apcupsd
from homeassistant.const import TEMP_CELCIUS from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
DEPENDENCIES = [apcupsd.DOMAIN] DEPENDENCIES = [apcupsd.DOMAIN]
DEFAULT_NAME = "UPS Status" DEFAULT_NAME = "UPS Status"
SPECIFIC_UNITS = { SPECIFIC_UNITS = {
"ITEMP": TEMP_CELCIUS "ITEMP": TEMP_CELSIUS
} }
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -4,7 +4,7 @@ Demo platform that has a couple of fake sensors.
For more details about this platform, please refer to the documentation For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/ https://home-assistant.io/components/demo/
""" """
from homeassistant.const import ATTR_BATTERY_LEVEL, TEMP_CELCIUS from homeassistant.const import ATTR_BATTERY_LEVEL, TEMP_CELSIUS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -12,7 +12,7 @@ from homeassistant.helpers.entity import Entity
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Demo sensors.""" """Setup the Demo sensors."""
add_devices([ add_devices([
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12), DemoSensor('Outside Temperature', 15.6, TEMP_CELSIUS, 12),
DemoSensor('Outside Humidity', 54, '%', None), DemoSensor('Outside Humidity', 54, '%', None),
]) ])

View file

@ -7,7 +7,7 @@ https://home-assistant.io/components/sensor.forecast/
import logging import logging
from datetime import timedelta from datetime import timedelta
from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS from homeassistant.const import CONF_API_KEY, TEMP_CELSIUS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
@ -64,7 +64,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if 'units' in config: if 'units' in config:
units = config['units'] units = config['units']
elif hass.config.temperature_unit == TEMP_CELCIUS: elif hass.config.temperature_unit == TEMP_CELSIUS:
units = 'si' units = 'si'
else: else:
units = 'us' units = 'us'

View file

@ -13,7 +13,7 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ["https://github.com/robbiet480/pygtfs/archive/" REQUIREMENTS = ["https://github.com/robbiet480/pygtfs/archive/"
"6b40d5fb30fd410cfaf637c901b5ed5a08c33e4c.zip#" "432414b720c580fb2667a0a48f539118a2d95969.zip#"
"pygtfs==0.1.2"] "pygtfs==0.1.2"]
ICON = "mdi:train" ICON = "mdi:train"

View file

@ -8,12 +8,13 @@ import logging
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.util import convert
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = "loopenergy" DOMAIN = "loopenergy"
REQUIREMENTS = ['pyloopenergy==0.0.7'] REQUIREMENTS = ['pyloopenergy==0.0.10']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -24,6 +25,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
elec_secret = config.get('electricity_secret') elec_secret = config.get('electricity_secret')
gas_serial = config.get('gas_serial') gas_serial = config.get('gas_serial')
gas_secret = config.get('gas_secret') gas_secret = config.get('gas_secret')
gas_type = config.get('gas_type', 'metric')
gas_calorific = convert(config.get('gas_calorific'), float, 39.11)
if not (elec_serial and elec_secret): if not (elec_serial and elec_secret):
_LOGGER.error( _LOGGER.error(
@ -39,11 +42,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"serial and secret tokens") "serial and secret tokens")
return None return None
if gas_type not in ['imperial', 'metric']:
_LOGGER.error(
"Configuration Error, 'gas_type' "
"can only be 'imperial' or 'metric' ")
return None
# pylint: disable=too-many-function-args
controller = pyloopenergy.LoopEnergy( controller = pyloopenergy.LoopEnergy(
elec_serial, elec_serial,
elec_secret, elec_secret,
gas_serial, gas_serial,
gas_secret gas_secret,
gas_type,
gas_calorific
) )
def stop_loopenergy(event): def stop_loopenergy(event):

View file

@ -9,7 +9,7 @@ import logging
import requests import requests
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, TEMP_CELCIUS from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -96,7 +96,7 @@ class MfiSensor(Entity):
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement of this entity, if any."""
if self._port.tag == 'temperature': if self._port.tag == 'temperature':
return TEMP_CELCIUS return TEMP_CELSIUS
elif self._port.tag == 'active_pwr': elif self._port.tag == 'active_pwr':
return 'Watts' return 'Watts'
elif self._port.model == 'Input Digital': elif self._port.model == 'Input Digital':

View file

@ -8,7 +8,7 @@ import logging
import homeassistant.components.modbus as modbus import homeassistant.components.modbus as modbus
from homeassistant.const import ( from homeassistant.const import (
STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -97,7 +97,7 @@ class ModbusSensor(Entity):
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement of this entity, if any."""
if self._unit == "C": if self._unit == "C":
return TEMP_CELCIUS return TEMP_CELSIUS
elif self._unit == "F": elif self._unit == "F":
return TEMP_FAHRENHEIT return TEMP_FAHRENHEIT
else: else:

View file

@ -6,8 +6,8 @@ https://home-assistant.io/components/sensor.mysensors/
""" """
import logging import logging
from homeassistant.const import ( from homeassistant.const import (ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON,
ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT) TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component from homeassistant.loader import get_component
@ -132,7 +132,7 @@ class MySensorsSensor(Entity):
"""Return the unit of measurement of this entity.""" """Return the unit of measurement of this entity."""
set_req = self.gateway.const.SetReq set_req = self.gateway.const.SetReq
unit_map = { unit_map = {
set_req.V_TEMP: (TEMP_CELCIUS set_req.V_TEMP: (TEMP_CELSIUS
if self.gateway.metric else TEMP_FAHRENHEIT), if self.gateway.metric else TEMP_FAHRENHEIT),
set_req.V_HUM: '%', set_req.V_HUM: '%',
set_req.V_DIMMER: '%', set_req.V_DIMMER: '%',
@ -157,8 +157,13 @@ class MySensorsSensor(Entity):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return device specific state attributes.""" """Return device specific state attributes."""
address = getattr(self.gateway, 'server_address', None)
if address:
device = '{}:{}'.format(address[0], address[1])
else:
device = self.gateway.port
attr = { attr = {
self.mysensors.ATTR_PORT: self.gateway.port, self.mysensors.ATTR_DEVICE: device,
self.mysensors.ATTR_NODE_ID: self.node_id, self.mysensors.ATTR_NODE_ID: self.node_id,
self.mysensors.ATTR_CHILD_ID: self.child_id, self.mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level, ATTR_BATTERY_LEVEL: self.battery_level,

View file

@ -4,12 +4,13 @@ Support for Nest Thermostat Sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.nest/ https://home-assistant.io/components/sensor.nest/
""" """
import logging import voluptuous as vol
import socket
import homeassistant.components.nest as nest import homeassistant.components.nest as nest
from homeassistant.const import TEMP_CELCIUS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import (
TEMP_CELSIUS, CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS
)
DEPENDENCIES = ['nest'] DEPENDENCIES = ['nest']
SENSOR_TYPES = ['humidity', SENSOR_TYPES = ['humidity',
@ -19,49 +20,42 @@ SENSOR_TYPES = ['humidity',
'last_connection', 'last_connection',
'battery_level'] 'battery_level']
WEATHER_VARIABLES = ['weather_condition', 'weather_temperature', WEATHER_VARS = {'weather_humidity': 'humidity',
'weather_humidity', 'weather_temperature': 'temperature',
'wind_speed', 'wind_direction'] 'weather_condition': 'condition',
'wind_speed': 'kph',
JSON_VARIABLE_NAMES = {'weather_humidity': 'humidity', 'wind_direction': 'direction'}
'weather_temperature': 'temperature',
'weather_condition': 'condition',
'wind_speed': 'kph',
'wind_direction': 'direction'}
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V', SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V',
'kph': 'kph', 'temperature': '°C'} 'kph': 'kph', 'temperature': '°C'}
SENSOR_TEMP_TYPES = ['temperature', 'target'] SENSOR_TEMP_TYPES = ['temperature', 'target']
_VALID_SENSOR_TYPES = SENSOR_TYPES + SENSOR_TEMP_TYPES + \
list(WEATHER_VARS.keys())
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): nest.DOMAIN,
vol.Optional(CONF_SCAN_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Required(CONF_MONITORED_CONDITIONS): [vol.In(_VALID_SENSOR_TYPES)],
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Nest Sensor.""" """Setup the Nest Sensor."""
logger = logging.getLogger(__name__) for structure, device in nest.devices():
try: sensors = [NestBasicSensor(structure, device, variable)
for structure in nest.NEST.structures: for variable in config[CONF_MONITORED_CONDITIONS]
for device in structure.devices: if variable in SENSOR_TYPES]
for variable in config['monitored_conditions']: sensors += [NestTempSensor(structure, device, variable)
if variable in SENSOR_TYPES: for variable in config[CONF_MONITORED_CONDITIONS]
add_devices([NestBasicSensor(structure, if variable in SENSOR_TEMP_TYPES]
device, sensors += [NestWeatherSensor(structure, device,
variable)]) WEATHER_VARS[variable])
elif variable in SENSOR_TEMP_TYPES: for variable in config[CONF_MONITORED_CONDITIONS]
add_devices([NestTempSensor(structure, if variable in WEATHER_VARS]
device, add_devices(sensors)
variable)])
elif variable in WEATHER_VARIABLES:
json_variable = JSON_VARIABLE_NAMES.get(variable, None)
add_devices([NestWeatherSensor(structure,
device,
json_variable)])
else:
logger.error('Nest sensor type: "%s" does not exist',
variable)
except socket.error:
logger.error(
"Connection error logging into the nest web service."
)
class NestSensor(Entity): class NestSensor(Entity):
@ -109,7 +103,7 @@ class NestTempSensor(NestSensor):
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit the value is expressed in.""" """Return the unit the value is expressed in."""
return TEMP_CELCIUS return TEMP_CELSIUS
@property @property
def state(self): def state(self):

View file

@ -9,7 +9,7 @@ from datetime import timedelta
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import DOMAIN
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, TEMP_CELCIUS) CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS)
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
@ -22,7 +22,7 @@ REQUIREMENTS = [
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = { SENSOR_TYPES = {
'temperature': ['Temperature', TEMP_CELCIUS, 'mdi:thermometer'], 'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
'co2': ['CO2', 'ppm', 'mdi:cloud'], 'co2': ['CO2', 'ppm', 'mdi:cloud'],
'pressure': ['Pressure', 'mbar', 'mdi:gauge'], 'pressure': ['Pressure', 'mbar', 'mdi:gauge'],
'noise': ['Noise', 'dB', 'mdi:volume-high'], 'noise': ['Noise', 'dB', 'mdi:volume-high'],

View file

@ -9,7 +9,7 @@ import os
import time import time
from glob import glob from glob import glob
from homeassistant.const import STATE_UNKNOWN, TEMP_CELCIUS from homeassistant.const import STATE_UNKNOWN, TEMP_CELSIUS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
BASE_DIR = '/sys/bus/w1/devices/' BASE_DIR = '/sys/bus/w1/devices/'
@ -84,7 +84,7 @@ class OneWire(Entity):
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit the value is expressed in.""" """Return the unit the value is expressed in."""
return TEMP_CELCIUS return TEMP_CELSIUS
def update(self): def update(self):
"""Get the latest data from the device.""" """Get the latest data from the device."""

View file

@ -7,7 +7,7 @@ https://home-assistant.io/components/sensor.openweathermap/
import logging import logging
from datetime import timedelta from datetime import timedelta
from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT from homeassistant.const import CONF_API_KEY, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
@ -106,7 +106,7 @@ class OpenWeatherMapSensor(Entity):
if self.type == 'weather': if self.type == 'weather':
self._state = data.get_detailed_status() self._state = data.get_detailed_status()
elif self.type == 'temperature': elif self.type == 'temperature':
if self.temp_unit == TEMP_CELCIUS: if self.temp_unit == TEMP_CELSIUS:
self._state = round(data.get_temperature('celsius')['temp'], self._state = round(data.get_temperature('celsius')['temp'],
1) 1)
elif self.temp_unit == TEMP_FAHRENHEIT: elif self.temp_unit == TEMP_FAHRENHEIT:

View file

@ -5,23 +5,18 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.rest/ https://home-assistant.io/components/sensor.rest/
""" """
import logging import logging
from datetime import timedelta
import requests import requests
from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template from homeassistant.helpers import template
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'REST Sensor' DEFAULT_NAME = 'REST Sensor'
DEFAULT_METHOD = 'GET' DEFAULT_METHOD = 'GET'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable # pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -96,7 +91,6 @@ class RestData(object):
self._verify_ssl = verify_ssl self._verify_ssl = verify_ssl
self.data = None self.data = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Get the latest data from REST service with GET method.""" """Get the latest data from REST service with GET method."""
try: try:

View file

@ -6,18 +6,21 @@ https://home-assistant.io/components/sensor.rfxtrx/
""" """
import logging import logging
from collections import OrderedDict from collections import OrderedDict
import voluptuous as vol
import homeassistant.components.rfxtrx as rfxtrx import homeassistant.components.rfxtrx as rfxtrx
from homeassistant.const import TEMP_CELCIUS from homeassistant.const import TEMP_CELSIUS
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify from homeassistant.util import slugify
from homeassistant.components.rfxtrx import ( 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'] DEPENDENCIES = ['rfxtrx']
DATA_TYPES = OrderedDict([ DATA_TYPES = OrderedDict([
('Temperature', TEMP_CELCIUS), ('Temperature', TEMP_CELSIUS),
('Humidity', '%'), ('Humidity', '%'),
('Barometer', ''), ('Barometer', ''),
('Wind direction', ''), ('Wind direction', ''),
@ -26,19 +29,48 @@ DATA_TYPES = OrderedDict([
('Total usage', 'W')]) ('Total usage', 'W')])
_LOGGER = logging.getLogger(__name__) _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): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the RFXtrx platform.""" """Setup the RFXtrx platform."""
from RFXtrx import SensorEvent from RFXtrx import SensorEvent
sensors = [] 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: if device_id in rfxtrx.RFX_DEVICES:
continue continue
_LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME]) _LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME])
event = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID]) event = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID])
new_sensor = RfxtrxSensor(event, entity_info[ATTR_NAME], 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 rfxtrx.RFX_DEVICES[slugify(device_id)] = new_sensor
sensors.append(new_sensor) sensors.append(new_sensor)
@ -62,7 +94,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
return return
# Add entity if not exist and the automatic_add is True # 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) pkt_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (device_id, pkt_id) entity_name = "%s : %s" % (device_id, pkt_id)
_LOGGER.info( _LOGGER.info(

View file

@ -23,6 +23,8 @@ ATTR_TARGET = 'Destination'
ATTR_REMAINING_TIME = 'Remaining time' ATTR_REMAINING_TIME = 'Remaining time'
ICON = 'mdi:bus' ICON = 'mdi:bus'
TIME_STR_FORMAT = "%H:%M"
# Return cached results if last scan was less then this time ago. # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
@ -126,10 +128,10 @@ class PublicTransportData(object):
try: try:
self.times = [ self.times = [
dt_util.datetime_to_time_str( dt_util.as_local(
dt_util.as_local(dt_util.utc_from_timestamp( dt_util.utc_from_timestamp(
item['from']['departureTimestamp'])) item['from']['departureTimestamp'])).strftime(
) TIME_STR_FORMAT)
for item in connections for item in connections
] ]
self.times.append( self.times.append(

View file

@ -131,9 +131,9 @@ class SystemMonitorSensor(Entity):
elif self.type == 'ipv6_address': elif self.type == 'ipv6_address':
self._state = psutil.net_if_addrs()[self.argument][1][1] self._state = psutil.net_if_addrs()[self.argument][1][1]
elif self.type == 'last_boot': elif self.type == 'last_boot':
self._state = dt_util.datetime_to_date_str( self._state = dt_util.as_local(
dt_util.as_local( dt_util.utc_from_timestamp(psutil.boot_time())
dt_util.utc_from_timestamp(psutil.boot_time()))) ).date().isoformat()
elif self.type == 'since_last_boot': elif self.type == 'since_last_boot':
self._state = dt_util.utcnow() - dt_util.utc_from_timestamp( self._state = dt_util.utcnow() - dt_util.utc_from_timestamp(
psutil.boot_time()) psutil.boot_time())

View file

@ -10,7 +10,7 @@ from datetime import datetime
from homeassistant.components import tellduslive from homeassistant.components import tellduslive
from homeassistant.const import ( from homeassistant.const import (
ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME, TEMP_CELCIUS) ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME, TEMP_CELSIUS)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
ATTR_LAST_UPDATED = "time_last_updated" ATTR_LAST_UPDATED = "time_last_updated"
@ -27,7 +27,7 @@ SENSOR_TYPE_WINDGUST = "wgust"
SENSOR_TYPE_WATT = "watt" SENSOR_TYPE_WATT = "watt"
SENSOR_TYPES = { SENSOR_TYPES = {
SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELCIUS, "mdi:thermometer"], SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELSIUS, "mdi:thermometer"],
SENSOR_TYPE_HUMIDITY: ['Humidity', '%', "mdi:water"], SENSOR_TYPE_HUMIDITY: ['Humidity', '%', "mdi:water"],
SENSOR_TYPE_RAINRATE: ['Rain rate', 'mm', "mdi:water"], SENSOR_TYPE_RAINRATE: ['Rain rate', 'mm', "mdi:water"],
SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', "mdi:water"], SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', "mdi:water"],

View file

@ -8,7 +8,7 @@ import logging
from collections import namedtuple from collections import namedtuple
import homeassistant.util as util import homeassistant.util as util
from homeassistant.const import TEMP_CELCIUS from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit']) DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
@ -25,7 +25,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_value_descriptions = { sensor_value_descriptions = {
tellcore_constants.TELLSTICK_TEMPERATURE: tellcore_constants.TELLSTICK_TEMPERATURE:
DatatypeDescription( DatatypeDescription(
'temperature', config.get('temperature_scale', TEMP_CELCIUS)), 'temperature', config.get('temperature_scale', TEMP_CELSIUS)),
tellcore_constants.TELLSTICK_HUMIDITY: tellcore_constants.TELLSTICK_HUMIDITY:
DatatypeDescription('humidity', '%'), DatatypeDescription('humidity', '%'),

View file

@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.temper/
""" """
import logging import logging
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME, TEMP_FAHRENHEIT
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -56,7 +56,9 @@ class TemperSensor(Entity):
def update(self): def update(self):
"""Retrieve latest state.""" """Retrieve latest state."""
try: try:
self.current_value = self.temper_device.get_temperature() format_str = ('fahrenheit' if self.temp_unit == TEMP_FAHRENHEIT
else 'celsius')
self.current_value = self.temper_device.get_temperature(format_str)
except IOError: except IOError:
_LOGGER.error('Failed to get temperature due to insufficient ' _LOGGER.error('Failed to get temperature due to insufficient '
'permissions. Try running with "sudo"') 'permissions. Try running with "sudo"')

View file

@ -0,0 +1,114 @@
"""Support for ThinkingCleaner."""
import logging
from datetime import timedelta
import homeassistant.util as util
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/TheRealLink/pythinkingcleaner'
'/archive/v0.0.2.zip'
'#pythinkingcleaner==0.0.2']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
SENSOR_TYPES = {
'battery': ['Battery', '%', 'mdi:battery'],
'state': ['State', None, None],
'capacity': ['Capacity', None, None],
}
STATES = {
'st_base': 'On homebase: Not Charging',
'st_base_recon': 'On homebase: Reconditioning Charging',
'st_base_full': 'On homebase: Full Charging',
'st_base_trickle': 'On homebase: Trickle Charging',
'st_base_wait': 'On homebase: Waiting',
'st_plug': 'Plugged in: Not Charging',
'st_plug_recon': 'Plugged in: Reconditioning Charging',
'st_plug_full': 'Plugged in: Full Charging',
'st_plug_trickle': 'Plugged in: Trickle Charging',
'st_plug_wait': 'Plugged in: Waiting',
'st_stopped': 'Stopped',
'st_clean': 'Cleaning',
'st_cleanstop': 'Stopped with cleaning',
'st_clean_spot': 'Spot cleaning',
'st_clean_max': 'Max cleaning',
'st_delayed': 'Delayed cleaning will start soon',
'st_dock': 'Searching Homebase',
'st_pickup': 'Roomba picked up',
'st_remote': 'Remote control driving',
'st_wait': 'Waiting for command',
'st_off': 'Off',
'st_error': 'Error',
'st_locate': 'Find me!',
'st_unknown': 'Unknown state',
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the ThinkingCleaner platform."""
from pythinkingcleaner import Discovery
discovery = Discovery()
devices = discovery.discover()
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_devices():
"""Update all devices."""
for device_object in devices:
device_object.update()
dev = []
for device in devices:
for type_name in SENSOR_TYPES.keys():
dev.append(ThinkingCleanerSensor(device, type_name,
update_devices))
add_devices(dev)
class ThinkingCleanerSensor(Entity):
"""ThinkingCleaner Sensor."""
def __init__(self, tc_object, sensor_type, update_devices):
"""Initialize the ThinkingCleaner."""
self.type = sensor_type
self._tc_object = tc_object
self._update_devices = update_devices
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._tc_object.name + ' ' + SENSOR_TYPES[self.type][0]
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return SENSOR_TYPES[self.type][2]
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
def update(self):
"""Update the sensor."""
self._update_devices()
if self.type == 'battery':
self._state = self._tc_object.battery
elif self.type == 'state':
self._state = STATES[self._tc_object.status]
elif self.type == 'capacity':
self._state = self._tc_object.capacity

View file

@ -19,6 +19,8 @@ OPTION_TYPES = {
'time_utc': 'Time (UTC)', 'time_utc': 'Time (UTC)',
} }
TIME_STR_FORMAT = "%H:%M"
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Time and Date sensor.""" """Setup the Time and Date sensor."""
@ -70,9 +72,9 @@ class TimeDateSensor(Entity):
def update(self): def update(self):
"""Get the latest data and updates the states.""" """Get the latest data and updates the states."""
time_date = dt_util.utcnow() time_date = dt_util.utcnow()
time = dt_util.datetime_to_time_str(dt_util.as_local(time_date)) time = dt_util.as_local(time_date).strftime(TIME_STR_FORMAT)
time_utc = dt_util.datetime_to_time_str(time_date) time_utc = time_date.strftime(TIME_STR_FORMAT)
date = dt_util.datetime_to_date_str(dt_util.as_local(time_date)) date = dt_util.as_local(time_date).date().isoformat()
# Calculate the beat (Swatch Internet Time) time without date. # Calculate the beat (Swatch Internet Time) time without date.
hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':') hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':')

View file

@ -9,7 +9,7 @@ import logging
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.const import ( from homeassistant.const import (
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
TEMP_CELCIUS, TEMP_FAHRENHEIT) TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.components.vera import ( from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER) VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
@ -65,8 +65,7 @@ class VeraSensor(VeraDevice, Entity):
last_tripped = self.vera_device.last_trip last_tripped = self.vera_device.last_trip
if last_tripped is not None: if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped)) utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
utc_time)
else: else:
attr[ATTR_LAST_TRIP_TIME] = None attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped tripped = self.vera_device.is_tripped
@ -85,7 +84,7 @@ class VeraSensor(VeraDevice, Entity):
if vera_temp_units == 'F': if vera_temp_units == 'F':
self._temperature_units = TEMP_FAHRENHEIT self._temperature_units = TEMP_FAHRENHEIT
else: else:
self._temperature_units = TEMP_CELCIUS self._temperature_units = TEMP_CELSIUS
if self.hass: if self.hass:
temp = self.hass.config.temperature( temp = self.hass.config.temperature(

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