commit
8f18cb34d6
171 changed files with 2937 additions and 1020 deletions
82
.coveragerc
82
.coveragerc
|
@ -5,18 +5,18 @@ omit =
|
|||
homeassistant/__main__.py
|
||||
|
||||
# omit pieces of code that rely on external devices being present
|
||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
homeassistant/components/apcupsd.py
|
||||
homeassistant/components/*/apcupsd.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/ecobee.py
|
||||
homeassistant/components/*/ecobee.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/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
|
||||
|
||||
|
@ -65,6 +38,36 @@ omit =
|
|||
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/rest.py
|
||||
homeassistant/components/browser.py
|
||||
|
@ -76,6 +79,7 @@ omit =
|
|||
homeassistant/components/device_tracker/actiontec.py
|
||||
homeassistant/components/device_tracker/aruba.py
|
||||
homeassistant/components/device_tracker/asuswrt.py
|
||||
homeassistant/components/device_tracker/bluetooth_tracker.py
|
||||
homeassistant/components/device_tracker/ddwrt.py
|
||||
homeassistant/components/device_tracker/fritz.py
|
||||
homeassistant/components/device_tracker/icloud.py
|
||||
|
@ -89,6 +93,7 @@ omit =
|
|||
homeassistant/components/device_tracker/ubus.py
|
||||
homeassistant/components/discovery.py
|
||||
homeassistant/components/downloader.py
|
||||
homeassistant/components/feedreader.py
|
||||
homeassistant/components/garage_door/wink.py
|
||||
homeassistant/components/ifttt.py
|
||||
homeassistant/components/keyboard.py
|
||||
|
@ -103,17 +108,17 @@ omit =
|
|||
homeassistant/components/media_player/itunes.py
|
||||
homeassistant/components/media_player/kodi.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/samsungtv.py
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
homeassistant/components/media_player/sonos.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/notify/free_mobile.py
|
||||
homeassistant/components/notify/googlevoice.py
|
||||
homeassistant/components/notify/gntp.py
|
||||
homeassistant/components/notify/googlevoice.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/message_bird.py
|
||||
homeassistant/components/notify/nma.py
|
||||
|
@ -140,10 +145,10 @@ omit =
|
|||
homeassistant/components/sensor/forecast.py
|
||||
homeassistant/components/sensor/glances.py
|
||||
homeassistant/components/sensor/gtfs.py
|
||||
homeassistant/components/sensor/netatmo.py
|
||||
homeassistant/components/sensor/nzbget.py
|
||||
homeassistant/components/sensor/loopenergy.py
|
||||
homeassistant/components/sensor/netatmo.py
|
||||
homeassistant/components/sensor/neurio_energy.py
|
||||
homeassistant/components/sensor/nzbget.py
|
||||
homeassistant/components/sensor/onewire.py
|
||||
homeassistant/components/sensor/openweathermap.py
|
||||
homeassistant/components/sensor/rest.py
|
||||
|
@ -169,10 +174,13 @@ omit =
|
|||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/transmission.py
|
||||
homeassistant/components/switch/wake_on_lan.py
|
||||
homeassistant/components/thermostat/eq3btsmart.py
|
||||
homeassistant/components/thermostat/heatmiser.py
|
||||
homeassistant/components/thermostat/homematic.py
|
||||
homeassistant/components/thermostat/proliphix.py
|
||||
homeassistant/components/thermostat/radiotherm.py
|
||||
homeassistant/components/upnp.py
|
||||
homeassistant/components/zeroconf.py
|
||||
|
||||
|
||||
[report]
|
||||
|
|
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -23,6 +23,6 @@ If the code does not interact with devices:
|
|||
|
||||
[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
|
||||
[ex-requir]: https://github.com/balloob/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-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L16
|
||||
[ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L51
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@ config/custom_components/*
|
|||
!config/custom_components/hello_world.py
|
||||
!config/custom_components/mqtt_example.py
|
||||
|
||||
tests/config/deps
|
||||
tests/config/home-assistant.log
|
||||
|
||||
# Hide sublime text stuff
|
||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "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
|
||||
|
|
|
@ -4,10 +4,10 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
|
|||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
|
@ -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.
|
||||
- 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.
|
||||
- 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`.
|
||||
- Create a Pull Request against the [**dev**](https://github.com/balloob/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/).
|
||||
- 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/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:
|
||||
|
||||
- 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.
|
||||
- Add your component to `home-assistant.conf.example`.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
- `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
|
||||
|
||||
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.
|
||||
|
||||
```python
|
||||
|
|
|
@ -8,9 +8,9 @@ WORKDIR /usr/src/app
|
|||
|
||||
RUN pip3 install --no-cache-dir colorlog cython
|
||||
|
||||
# For the nmap tracker
|
||||
# For the nmap tracker, bluetooth tracker, Z-Wave
|
||||
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/*
|
||||
|
||||
COPY script/build_python_openzwave script/build_python_openzwave
|
||||
|
|
16
README.rst
16
README.rst
|
@ -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
|
||||
|
@ -88,11 +88,11 @@ If you run into issues while using Home Assistant or during development
|
|||
of a component, check the `Home Assistant help
|
||||
section <https://home-assistant.io/help/>`__ how to reach us.
|
||||
|
||||
.. |Build Status| image:: https://travis-ci.org/balloob/home-assistant.svg?branch=master
|
||||
:target: https://travis-ci.org/balloob/home-assistant
|
||||
.. |Coverage Status| image:: https://img.shields.io/coveralls/balloob/home-assistant.svg
|
||||
:target: https://coveralls.io/r/balloob/home-assistant?branch=master
|
||||
.. |Join the chat at https://gitter.im/balloob/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
|
||||
.. |screenshot-states| image:: https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png
|
||||
.. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=master
|
||||
:target: https://travis-ci.org/home-assistant/home-assistant
|
||||
.. |Coverage Status| image:: https://img.shields.io/coveralls/home-assistant/home-assistant.svg
|
||||
:target: https://coveralls.io/r/home-assistant/home-assistant?branch=master
|
||||
.. |Join the chat at https://gitter.im/home-assistant/home-assistant| image:: https://badges.gitter.im/Join%20Chat.svg
|
||||
: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/home-assistant/home-assistant/master/docs/screenshots.png
|
||||
:target: https://home-assistant.io/demo/
|
||||
|
|
|
@ -7,7 +7,7 @@ homeassistant:
|
|||
latitude: 32.87336
|
||||
longitude: 117.22743
|
||||
|
||||
# C for Celcius, F for Fahrenheit
|
||||
# C for Celsius, F for Fahrenheit
|
||||
temperature_unit: C
|
||||
|
||||
# Pick yours from here:
|
||||
|
|
|
@ -14,7 +14,7 @@ To use the mqtt_example component you will need to add the following to your
|
|||
configuration.yaml file.
|
||||
|
||||
mqtt_example:
|
||||
topic: home-assistant/mqtt_example
|
||||
topic: "home-assistant/mqtt_example"
|
||||
"""
|
||||
import homeassistant.loader as loader
|
||||
|
||||
|
@ -29,7 +29,7 @@ DEFAULT_TOPIC = 'home-assistant/mqtt_example'
|
|||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the MQTT example component."""
|
||||
"""Setup the MQTT example component."""
|
||||
mqtt = loader.get_component('mqtt')
|
||||
topic = config[DOMAIN].get('topic', DEFAULT_TOPIC)
|
||||
entity_id = 'mqtt_example.last_message'
|
||||
|
|
|
@ -31,7 +31,7 @@ def validate_python():
|
|||
def ensure_config_path(config_dir):
|
||||
"""Validate the configuration directory."""
|
||||
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
|
||||
if not os.path.isdir(config_dir):
|
||||
|
|
|
@ -21,7 +21,7 @@ import homeassistant.util.package as pkg_util
|
|||
from homeassistant.const import (
|
||||
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
|
||||
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
|
||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
event_decorators, service, config_per_platform, extract_domain_configs)
|
||||
|
@ -65,7 +65,7 @@ def _handle_requirements(hass, component, name):
|
|||
return True
|
||||
|
||||
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 '
|
||||
'dependency %s', name, req)
|
||||
return False
|
||||
|
@ -211,7 +211,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
|
|||
|
||||
def mount_local_lib_path(config_dir):
|
||||
"""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
|
||||
|
@ -372,10 +372,16 @@ def process_ha_config_upgrade(hass):
|
|||
_LOGGER.info('Upgrading config directory from %s to %s', conf_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')
|
||||
if os.path.isdir(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:
|
||||
outp.write(__version__)
|
||||
|
||||
|
@ -434,7 +440,7 @@ def process_ha_core_config(hass, config):
|
|||
if info.use_fahrenheit:
|
||||
hac.temperature_unit = TEMP_FAHRENHEIT
|
||||
else:
|
||||
hac.temperature_unit = TEMP_CELCIUS
|
||||
hac.temperature_unit = TEMP_CELSIUS
|
||||
|
||||
if hac.location_name is None:
|
||||
hac.location_name = info.city
|
||||
|
|
|
@ -7,12 +7,15 @@ https://home-assistant.io/components/alarm_control_panel/
|
|||
import logging
|
||||
import os
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import verisure
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
|
@ -38,6 +41,11 @@ ATTR_TO_PROPERTY = [
|
|||
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):
|
||||
"""Track states and offer events for sensors."""
|
||||
|
@ -51,10 +59,7 @@ def setup(hass, config):
|
|||
"""Map services to methods on Alarm."""
|
||||
target_alarms = component.extract_from_service(service)
|
||||
|
||||
if ATTR_CODE not in service.data:
|
||||
code = None
|
||||
else:
|
||||
code = service.data[ATTR_CODE]
|
||||
code = service.data.get(ATTR_CODE)
|
||||
|
||||
method = SERVICE_TO_METHOD[service.service]
|
||||
|
||||
|
@ -68,8 +73,8 @@ def setup(hass, config):
|
|||
|
||||
for service in SERVICE_TO_METHOD:
|
||||
hass.services.register(DOMAIN, service, alarm_service_handler,
|
||||
descriptions.get(service))
|
||||
|
||||
descriptions.get(service),
|
||||
schema=ALARM_SERVICE_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -51,8 +51,6 @@ def _platform_validator(method, schema):
|
|||
if not hasattr(platform, schema):
|
||||
return config
|
||||
|
||||
print('validating config', method, config)
|
||||
|
||||
return getattr(platform, schema)(config)
|
||||
|
||||
return validator
|
||||
|
|
|
@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
def trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
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:
|
||||
_error_time(config[CONF_AFTER], CONF_AFTER)
|
||||
return False
|
||||
|
@ -62,13 +62,13 @@ def if_action(hass, config):
|
|||
return None
|
||||
|
||||
if before is not None:
|
||||
before = dt_util.parse_time_str(before)
|
||||
before = dt_util.parse_time(before)
|
||||
if before is None:
|
||||
_error_time(before, CONF_BEFORE)
|
||||
return None
|
||||
|
||||
if after is not None:
|
||||
after = dt_util.parse_time_str(after)
|
||||
after = dt_util.parse_time(after)
|
||||
if after is None:
|
||||
_error_time(after, CONF_AFTER)
|
||||
return None
|
||||
|
|
|
@ -6,10 +6,9 @@ https://home-assistant.io/components/binary_sensor.mysensors/
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON)
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, SENSOR_CLASSES)
|
||||
from homeassistant.components.binary_sensor import (SENSOR_CLASSES,
|
||||
BinarySensorDevice)
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -101,8 +100,13 @@ class MySensorsBinarySensor(BinarySensorDevice):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""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 = {
|
||||
self.mysensors.ATTR_PORT: self.gateway.port,
|
||||
self.mysensors.ATTR_DEVICE: device,
|
||||
self.mysensors.ATTR_NODE_ID: self.node_id,
|
||||
self.mysensors.ATTR_CHILD_ID: self.child_id,
|
||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||
|
|
|
@ -4,12 +4,14 @@ Support for Nest Thermostat Binary Sensors.
|
|||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.nest/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.nest as nest
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.sensor.nest import NestSensor
|
||||
from homeassistant.const import (
|
||||
CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['nest']
|
||||
BINARY_TYPES = ['fan',
|
||||
|
@ -23,25 +25,19 @@ BINARY_TYPES = ['fan',
|
|||
'hvac_emer_heat_state',
|
||||
'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):
|
||||
"""Setup Nest binary sensors."""
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
for structure in nest.NEST.structures:
|
||||
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."
|
||||
)
|
||||
for structure, device in nest.devices():
|
||||
add_devices([NestBinarySensor(structure, device, variable)
|
||||
for variable in config[CONF_MONITORED_CONDITIONS]])
|
||||
|
||||
|
||||
class NestBinarySensor(NestSensor, BinarySensorDevice):
|
||||
|
|
|
@ -49,8 +49,7 @@ class VeraBinarySensor(VeraDevice, BinarySensorDevice):
|
|||
last_tripped = self.vera_device.last_trip
|
||||
if last_tripped is not None:
|
||||
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
|
||||
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
|
||||
utc_time)
|
||||
attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
|
||||
else:
|
||||
attr[ATTR_LAST_TRIP_TIME] = None
|
||||
tripped = self.vera_device.is_tripped
|
||||
|
|
|
@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
|
|||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
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
|
||||
SENSOR_TYPES = {
|
||||
|
|
|
@ -8,11 +8,7 @@ import logging
|
|||
import datetime
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
|
||||
from homeassistant.components.zwave import (
|
||||
ATTR_NODE_ID, ATTR_VALUE_ID,
|
||||
COMMAND_CLASS_SENSOR_BINARY, NETWORK,
|
||||
ZWaveDeviceEntity, get_config_value)
|
||||
from homeassistant.components import zwave
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DOMAIN,
|
||||
BinarySensorDevice)
|
||||
|
@ -36,11 +32,11 @@ DEVICE_MAPPINGS = {
|
|||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""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
|
||||
|
||||
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
||||
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
||||
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
|
||||
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
|
||||
value.set_change_verified(False)
|
||||
|
||||
# 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 DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT:
|
||||
# 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([
|
||||
ZWaveTriggerSensor(value, "motion",
|
||||
hass, re_arm_multiplier * 8)
|
||||
])
|
||||
return
|
||||
|
||||
if value.command_class == COMMAND_CLASS_SENSOR_BINARY:
|
||||
if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
|
||||
add_devices([ZWaveBinarySensor(value, None)])
|
||||
|
||||
|
||||
class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
|
||||
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
|
||||
"""Representation of a binary sensor within Z-Wave."""
|
||||
|
||||
def __init__(self, value, sensor_class):
|
||||
|
@ -74,7 +71,7 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
|
|||
from openzwave.network import ZWaveNetwork
|
||||
from pydispatch import dispatcher
|
||||
|
||||
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||
|
||||
dispatcher.connect(
|
||||
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
||||
|
|
|
@ -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
|
||||
https://home-assistant.io/components/browser/
|
||||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
DOMAIN = "browser"
|
||||
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):
|
||||
"""Listen for browse_url events."""
|
||||
|
@ -15,8 +23,7 @@ def setup(hass, config):
|
|||
|
||||
hass.services.register(DOMAIN, SERVICE_BROWSE_URL,
|
||||
lambda service:
|
||||
webbrowser.open(
|
||||
service.data.get(
|
||||
'url', 'https://www.google.com')))
|
||||
webbrowser.open(service.data[ATTR_URL]),
|
||||
schema=SERVICE_BROWSE_URL_SCHEMA)
|
||||
|
||||
return True
|
||||
|
|
|
@ -8,9 +8,12 @@ import logging
|
|||
import re
|
||||
import warnings
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import core
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DOMAIN = "conversation"
|
||||
|
||||
|
@ -18,6 +21,10 @@ SERVICE_PROCESS = "process"
|
|||
|
||||
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+)')
|
||||
|
||||
REQUIREMENTS = ['fuzzywuzzy==0.8.0']
|
||||
|
@ -32,11 +39,7 @@ def setup(hass, config):
|
|||
|
||||
def process(service):
|
||||
"""Parse text into commands."""
|
||||
if ATTR_TEXT not in service.data:
|
||||
logger.error("Received process service call without a text")
|
||||
return
|
||||
|
||||
text = service.data[ATTR_TEXT].lower()
|
||||
text = service.data[ATTR_TEXT]
|
||||
match = REGEX_TURN_COMMAND.match(text)
|
||||
|
||||
if not match:
|
||||
|
@ -67,6 +70,6 @@ def setup(hass, config):
|
|||
logger.error(
|
||||
'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
|
||||
|
|
|
@ -94,7 +94,7 @@ def setup(hass, config):
|
|||
yaml_path = hass.config.path(YAML_DEVICES)
|
||||
|
||||
conf = config.get(DOMAIN, {})
|
||||
if isinstance(conf, list):
|
||||
if isinstance(conf, list) and len(conf) > 0:
|
||||
conf = conf[0]
|
||||
consider_home = timedelta(
|
||||
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
|
||||
|
|
91
homeassistant/components/device_tracker/bluetooth_tracker.py
Normal file
91
homeassistant/components/device_tracker/bluetooth_tracker.py
Normal 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
|
|
@ -12,7 +12,9 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
|||
from homeassistant.helpers import validate_config
|
||||
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.
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||
|
|
|
@ -9,14 +9,15 @@ import threading
|
|||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.device_tracker import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
|
||||
CONF_PORT
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
# Return cached results if last scan was less then this time ago.
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['pynetgear==0.3.2']
|
||||
REQUIREMENTS = ['pynetgear==0.3.3']
|
||||
|
||||
|
||||
def get_scanner(hass, config):
|
||||
|
@ -25,12 +26,13 @@ def get_scanner(hass, config):
|
|||
host = info.get(CONF_HOST)
|
||||
username = info.get(CONF_USERNAME)
|
||||
password = info.get(CONF_PASSWORD)
|
||||
port = info.get(CONF_PORT)
|
||||
|
||||
if password is not None and host is None:
|
||||
_LOGGER.warning('Found username or password but no host')
|
||||
return None
|
||||
|
||||
scanner = NetgearDeviceScanner(host, username, password)
|
||||
scanner = NetgearDeviceScanner(host, username, password, port)
|
||||
|
||||
return scanner if scanner.success_init else None
|
||||
|
||||
|
@ -38,7 +40,7 @@ def get_scanner(hass, config):
|
|||
class NetgearDeviceScanner(object):
|
||||
"""Queries a Netgear wireless router using the SOAP-API."""
|
||||
|
||||
def __init__(self, host, username, password):
|
||||
def __init__(self, host, username, password, port):
|
||||
"""Initialize the scanner."""
|
||||
import pynetgear
|
||||
|
||||
|
@ -49,8 +51,10 @@ class NetgearDeviceScanner(object):
|
|||
self._api = pynetgear.Netgear()
|
||||
elif username is None:
|
||||
self._api = pynetgear.Netgear(password, host)
|
||||
else:
|
||||
elif port is None:
|
||||
self._api = pynetgear.Netgear(password, host, username)
|
||||
else:
|
||||
self._api = pynetgear.Netgear(password, host, username, port)
|
||||
|
||||
_LOGGER.info("Logging in")
|
||||
|
||||
|
|
|
@ -34,21 +34,33 @@ def setup_scanner(hass, config, see):
|
|||
"""Setup an OwnTracks tracker."""
|
||||
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
|
||||
|
||||
def owntracks_location_update(topic, payload, qos):
|
||||
"""MQTT message received."""
|
||||
# Docs on available data:
|
||||
# http://owntracks.org/booklet/tech/json/#_typelocation
|
||||
def validate_payload(payload, data_type):
|
||||
"""Validate OwnTracks payload."""
|
||||
try:
|
||||
data = json.loads(payload)
|
||||
except ValueError:
|
||||
# If invalid JSON
|
||||
_LOGGER.error(
|
||||
'Unable to parse payload as JSON: %s', payload)
|
||||
return
|
||||
_LOGGER.error('Unable to parse payload as JSON: %s', payload)
|
||||
return None
|
||||
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 (
|
||||
max_gps_accuracy is not None and
|
||||
convert(data.get('acc'), float, 0.0) > max_gps_accuracy):
|
||||
def owntracks_location_update(topic, payload, qos):
|
||||
"""MQTT message received."""
|
||||
# Docs on available data:
|
||||
# http://owntracks.org/booklet/tech/json/#_typelocation
|
||||
data = validate_payload(payload, 'location')
|
||||
if not data:
|
||||
return
|
||||
|
||||
dev_id, kwargs = _parse_see_args(topic, data)
|
||||
|
@ -65,24 +77,16 @@ def setup_scanner(hass, config, see):
|
|||
see_beacons(dev_id, kwargs)
|
||||
|
||||
def owntracks_event_update(topic, payload, qos):
|
||||
# pylint: disable=too-many-branches, too-many-statements
|
||||
"""MQTT event (geofences) received."""
|
||||
# Docs on available data:
|
||||
# http://owntracks.org/booklet/tech/json/#_typetransition
|
||||
try:
|
||||
data = json.loads(payload)
|
||||
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':
|
||||
data = validate_payload(payload, 'transition')
|
||||
if not data:
|
||||
return
|
||||
|
||||
if data.get('desc') is None:
|
||||
_LOGGER.error(
|
||||
"Location missing from `enter/exit` message - "
|
||||
"Location missing from `Entering/Leaving` message - "
|
||||
"please turn `Share` on in OwnTracks app")
|
||||
return
|
||||
# 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)
|
||||
|
||||
if data['event'] == 'enter':
|
||||
def enter_event():
|
||||
"""Execute enter event."""
|
||||
zone = hass.states.get("zone.{}".format(location))
|
||||
with LOCK:
|
||||
if zone is None:
|
||||
if data['t'] == 'b':
|
||||
# Not a HA zone, and a beacon so assume mobile
|
||||
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
|
||||
if location not in beacons:
|
||||
beacons.append(location)
|
||||
_LOGGER.info("Added beacon %s", location)
|
||||
if zone is None and data['t'] == 'b':
|
||||
# Not a HA zone, and a beacon so assume mobile
|
||||
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
|
||||
if location not in beacons:
|
||||
beacons.append(location)
|
||||
_LOGGER.info("Added beacon %s", location)
|
||||
else:
|
||||
# Normal region
|
||||
regions = REGIONS_ENTERED[dev_id]
|
||||
|
@ -114,7 +118,8 @@ def setup_scanner(hass, config, see):
|
|||
see(**kwargs)
|
||||
see_beacons(dev_id, kwargs)
|
||||
|
||||
elif data['event'] == 'leave':
|
||||
def leave_event():
|
||||
"""Execute leave event."""
|
||||
with LOCK:
|
||||
regions = REGIONS_ENTERED[dev_id]
|
||||
if location in regions:
|
||||
|
@ -146,6 +151,10 @@ def setup_scanner(hass, config, see):
|
|||
beacons.remove(location)
|
||||
_LOGGER.info("Remove beacon %s", location)
|
||||
|
||||
if data['event'] == 'enter':
|
||||
enter_event()
|
||||
elif data['event'] == 'leave':
|
||||
leave_event()
|
||||
else:
|
||||
_LOGGER.error(
|
||||
'Misformatted mqtt msgs, _type=transition, event=%s',
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||
EVENT_PLATFORM_DISCOVERED)
|
||||
|
||||
DOMAIN = "discovery"
|
||||
REQUIREMENTS = ['netdisco==0.6.1']
|
||||
REQUIREMENTS = ['netdisco==0.6.4']
|
||||
|
||||
SCAN_INTERVAL = 300 # seconds
|
||||
|
||||
|
|
|
@ -10,8 +10,10 @@ import re
|
|||
import threading
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers import validate_config
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import sanitize_filename
|
||||
|
||||
DOMAIN = "downloader"
|
||||
|
@ -21,6 +23,11 @@ SERVICE_DOWNLOAD_FILE = "download_file"
|
|||
ATTR_URL = "url"
|
||||
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'
|
||||
|
||||
|
||||
|
@ -48,10 +55,6 @@ def setup(hass, config):
|
|||
|
||||
def download_file(service):
|
||||
"""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():
|
||||
"""Download the file."""
|
||||
try:
|
||||
|
@ -127,7 +130,7 @@ def setup(hass, config):
|
|||
|
||||
threading.Thread(target=do_download).start()
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE,
|
||||
download_file)
|
||||
hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE, download_file,
|
||||
schema=SERVICE_DOWNLOAD_FILE_SCHEMA)
|
||||
|
||||
return True
|
||||
|
|
80
homeassistant/components/feedreader.py
Normal file
80
homeassistant/components/feedreader.py
Normal 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
|
|
@ -1,2 +1,2 @@
|
|||
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
|
||||
VERSION = "df49e6b7c930eb39b42ff1909712e95e"
|
||||
VERSION = "af8a531f1c2e477c07c4b3394bd1ce13"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
"""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
|
@ -7,10 +7,13 @@ at https://home-assistant.io/components/garage_door/
|
|||
import logging
|
||||
import os
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN,
|
||||
ATTR_ENTITY_ID)
|
||||
|
@ -29,6 +32,10 @@ DISCOVERY_PLATFORMS = {
|
|||
wink.DISCOVER_GARAGE_DOORS: 'wink'
|
||||
}
|
||||
|
||||
GARAGE_DOOR_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
})
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -73,10 +80,11 @@ def setup(hass, config):
|
|||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
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,
|
||||
descriptions.get(SERVICE_CLOSE))
|
||||
|
||||
descriptions.get(SERVICE_CLOSE),
|
||||
schema=GARAGE_DOOR_SERVICE_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
|
|
139
homeassistant/components/garage_door/mqtt.py
Normal file
139
homeassistant/components/garage_door/mqtt.py
Normal 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()
|
|
@ -9,7 +9,7 @@ import logging
|
|||
from homeassistant.components.garage_door import GarageDoorDevice
|
||||
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):
|
||||
|
|
|
@ -182,7 +182,7 @@ def _api_history_period(handler, path_match, data):
|
|||
one_day = timedelta(seconds=86400)
|
||||
|
||||
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:
|
||||
handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST)
|
||||
|
|
|
@ -5,6 +5,7 @@ For more details about the RESTful API, please refer to the documentation at
|
|||
https://home-assistant.io/developers/api/
|
||||
"""
|
||||
import gzip
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
import ssl
|
||||
|
@ -200,12 +201,22 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
|
||||
return
|
||||
|
||||
self.authenticated = (self.server.api_password is None or
|
||||
self.headers.get(HTTP_HEADER_HA_AUTH) ==
|
||||
self.server.api_password or
|
||||
data.get(DATA_API_PASSWORD) ==
|
||||
self.server.api_password or
|
||||
self.verify_session())
|
||||
if self.verify_session():
|
||||
# The user has a valid session already
|
||||
self.authenticated = True
|
||||
elif self.server.api_password is None:
|
||||
# No password is set, so everyone is authenticated
|
||||
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
|
||||
if url.path not in [URL_ROOT, URL_API_EVENT_FORWARD]:
|
||||
|
|
|
@ -7,8 +7,10 @@ https://home-assistant.io/components/ifttt/
|
|||
import logging
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers import validate_config
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -23,6 +25,13 @@ ATTR_VALUE3 = 'value3'
|
|||
|
||||
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):
|
||||
"""Trigger a Maker IFTTT recipe."""
|
||||
|
@ -44,12 +53,10 @@ def setup(hass, config):
|
|||
|
||||
def trigger_service(call):
|
||||
"""Handle IFTTT trigger service calls."""
|
||||
event = call.data.get(ATTR_EVENT)
|
||||
event = call.data[ATTR_EVENT]
|
||||
value1 = call.data.get(ATTR_VALUE1)
|
||||
value2 = call.data.get(ATTR_VALUE2)
|
||||
value3 = call.data.get(ATTR_VALUE3)
|
||||
if event is None:
|
||||
return
|
||||
|
||||
try:
|
||||
import pyfttt as pyfttt
|
||||
|
@ -57,6 +64,7 @@ def setup(hass, config):
|
|||
except requests.exceptions.RequestException:
|
||||
_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
|
||||
|
|
|
@ -7,7 +7,8 @@ https://home-assistant.io/components/influxdb/
|
|||
import logging
|
||||
|
||||
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 validate_config
|
||||
|
||||
|
@ -70,8 +71,9 @@ def setup(hass, config):
|
|||
def influx_event_listener(event):
|
||||
"""Listen for new messages on the bus and sends them to Influx."""
|
||||
state = event.data.get('new_state')
|
||||
if state is None or state.state in (STATE_UNKNOWN, '') \
|
||||
or state.entity_id in blacklist:
|
||||
if state is None or state.state in (
|
||||
STATE_UNKNOWN, '', STATE_UNAVAILABLE) or \
|
||||
state.entity_id in blacklist:
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
|
@ -6,8 +6,11 @@ at https://home-assistant.io/components/input_boolean/
|
|||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
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_component import EntityComponent
|
||||
from homeassistant.util import slugify
|
||||
|
@ -22,6 +25,10 @@ CONF_NAME = "name"
|
|||
CONF_INITIAL = "initial"
|
||||
CONF_ICON = "icon"
|
||||
|
||||
TOGGLE_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
})
|
||||
|
||||
|
||||
def is_on(hass, entity_id):
|
||||
"""Test if input_boolean is True."""
|
||||
|
@ -75,8 +82,10 @@ def setup(hass, config):
|
|||
else:
|
||||
input_b.turn_off()
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service)
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service)
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service,
|
||||
schema=TOGGLE_SERVICE_SCHEMA)
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service,
|
||||
schema=TOGGLE_SERVICE_SCHEMA)
|
||||
|
||||
component.add_entities(entities)
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@ at https://home-assistant.io/components/input_select/
|
|||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.util import slugify
|
||||
|
@ -25,6 +28,11 @@ ATTR_OPTIONS = 'options'
|
|||
|
||||
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):
|
||||
"""Set input_select to False."""
|
||||
|
@ -79,10 +87,11 @@ def setup(hass, config):
|
|||
target_inputs = component.extract_from_service(call)
|
||||
|
||||
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,
|
||||
select_option_service)
|
||||
select_option_service,
|
||||
schema=SERVICE_SELECT_OPTION_SCHEMA)
|
||||
|
||||
component.add_entities(entities)
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@ at https://home-assistant.io/components/input_slider/
|
|||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.util import slugify
|
||||
|
@ -29,6 +32,11 @@ ATTR_STEP = 'step'
|
|||
|
||||
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):
|
||||
"""Set input_slider to value."""
|
||||
|
@ -81,10 +89,11 @@ def setup(hass, config):
|
|||
target_inputs = component.extract_from_service(call)
|
||||
|
||||
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,
|
||||
select_value_service)
|
||||
select_value_service,
|
||||
schema=SERVICE_SELECT_VALUE_SCHEMA)
|
||||
|
||||
component.add_entities(entities)
|
||||
|
||||
|
|
|
@ -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
|
||||
https://home-assistant.io/components/keyboard/
|
||||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE,
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
|
||||
|
@ -12,6 +14,8 @@ from homeassistant.const import (
|
|||
DOMAIN = "keyboard"
|
||||
REQUIREMENTS = ['pyuserinput==0.1.9']
|
||||
|
||||
TAP_KEY_SCHEMA = vol.Schema({})
|
||||
|
||||
|
||||
def volume_up(hass):
|
||||
"""Press the keyboard button for volume up."""
|
||||
|
@ -52,26 +56,31 @@ def setup(hass, config):
|
|||
|
||||
hass.services.register(DOMAIN, SERVICE_VOLUME_UP,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.media_prev_track_key))
|
||||
|
||||
keyboard.tap_key(keyboard.media_prev_track_key),
|
||||
schema=TAP_KEY_SCHEMA)
|
||||
return True
|
||||
|
|
|
@ -32,6 +32,9 @@ PHUE_CONFIG_FILE = "phue.conf"
|
|||
_CONFIGURING = {}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Track previously setup bridges
|
||||
_CONFIGURED_BRIDGES = {}
|
||||
|
||||
|
||||
def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
|
||||
"""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
|
||||
|
||||
# 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
|
||||
|
||||
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:
|
||||
add_devices_callback(new_lights)
|
||||
|
||||
_CONFIGURED_BRIDGES[socket.gethostbyname(host)] = True
|
||||
update_lights()
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
"""Setup a Hyperion server remote."""
|
||||
host = config.get(CONF_HOST, None)
|
||||
port = config.get("port", 19444)
|
||||
device = Hyperion(host, port)
|
||||
device = Hyperion(config.get('name', host), host, port)
|
||||
if device.setup():
|
||||
add_devices_callback([device])
|
||||
return True
|
||||
|
@ -30,11 +30,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
class Hyperion(Light):
|
||||
"""Representation of a Hyperion remote."""
|
||||
|
||||
def __init__(self, host, port):
|
||||
def __init__(self, name, host, port):
|
||||
"""Initialize the light."""
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._name = host
|
||||
self._name = name
|
||||
self._is_available = True
|
||||
self._rgb_color = [255, 255, 255]
|
||||
|
||||
|
@ -75,7 +75,8 @@ class Hyperion(Light):
|
|||
"""Get the hostname of the remote."""
|
||||
response = self.json_request({"command": "serverinfo"})
|
||||
if response:
|
||||
self._name = response["info"]["hostname"]
|
||||
if self._name == self._host:
|
||||
self._name = response["info"]["hostname"]
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
|
@ -209,6 +209,8 @@ class LIFXLight(Light):
|
|||
brightness = self._bri
|
||||
|
||||
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) *
|
||||
(kwargs[ATTR_COLOR_TEMP] - TEMP_MIN_HASS) /
|
||||
(TEMP_MAX_HASS - TEMP_MIN_HASS)) + TEMP_MIN)
|
||||
|
|
|
@ -6,8 +6,8 @@ https://home-assistant.io/components/light.mysensors/
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, Light)
|
||||
from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_RGB_COLOR,
|
||||
Light)
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.util.color import rgb_hex_to_rgb_list
|
||||
|
@ -100,15 +100,20 @@ class MySensorsLight(Light):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
device_attr = {
|
||||
self.mysensors.ATTR_PORT: self.gateway.port,
|
||||
address = getattr(self.gateway, 'server_address', None)
|
||||
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_CHILD_ID: self.child_id,
|
||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||
}
|
||||
for value_type, value in self._values.items():
|
||||
device_attr[self.gateway.const.SetReq(value_type).name] = value
|
||||
return device_attr
|
||||
attr[self.gateway.const.SetReq(value_type).name] = value
|
||||
return attr
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
|
|
|
@ -72,8 +72,7 @@ class VeraLight(VeraDevice, Light):
|
|||
last_tripped = self.vera_device.last_trip
|
||||
if last_tripped is not None:
|
||||
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
|
||||
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
|
||||
utc_time)
|
||||
attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
|
||||
else:
|
||||
attr[ATTR_LAST_TRIP_TIME] = None
|
||||
tripped = self.vera_device.is_tripped
|
||||
|
|
|
@ -6,10 +6,14 @@ https://home-assistant.io/components/light.wink/
|
|||
"""
|
||||
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.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):
|
||||
|
@ -35,7 +39,11 @@ class WinkLight(Light):
|
|||
"""Representation of a Wink light."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the light."""
|
||||
"""
|
||||
Initialize the light.
|
||||
|
||||
:type wink: pywink.devices.standard.bulb.WinkBulb
|
||||
"""
|
||||
self.wink = wink
|
||||
|
||||
@property
|
||||
|
@ -63,15 +71,41 @@ class WinkLight(Light):
|
|||
"""True if connection == True."""
|
||||
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
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
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:
|
||||
self.wink.set_state(True, brightness=brightness / 255)
|
||||
else:
|
||||
self.wink.set_state(True)
|
||||
state_kwargs = {
|
||||
}
|
||||
|
||||
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):
|
||||
"""Turn the switch off."""
|
||||
|
@ -79,4 +113,4 @@ class WinkLight(Light):
|
|||
|
||||
def update(self):
|
||||
"""Update state of the light."""
|
||||
self.wink.update_state()
|
||||
self.wink.update_state(require_desired_state_fulfilled=True)
|
||||
|
|
|
@ -9,25 +9,23 @@ https://home-assistant.io/components/light.zwave/
|
|||
from threading import Timer
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light
|
||||
from homeassistant.components.zwave import (
|
||||
ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_SWITCH_MULTILEVEL, GENRE_USER,
|
||||
NETWORK, TYPE_BYTE, ZWaveDeviceEntity)
|
||||
from homeassistant.components import zwave
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""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
|
||||
|
||||
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
||||
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
||||
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_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
|
||||
if value.type != TYPE_BYTE:
|
||||
if value.type != zwave.TYPE_BYTE:
|
||||
return
|
||||
if value.genre != GENRE_USER:
|
||||
if value.genre != zwave.GENRE_USER:
|
||||
return
|
||||
|
||||
value.set_change_verified(False)
|
||||
|
@ -42,7 +40,7 @@ def brightness_state(value):
|
|||
return 255, STATE_OFF
|
||||
|
||||
|
||||
class ZwaveDimmer(ZWaveDeviceEntity, Light):
|
||||
class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
||||
"""Representation of a Z-Wave dimmer."""
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
|
@ -51,7 +49,7 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light):
|
|||
from openzwave.network import ZWaveNetwork
|
||||
from pydispatch import dispatcher
|
||||
|
||||
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||
|
||||
self._brightness, self._state = brightness_state(value)
|
||||
|
||||
|
|
|
@ -8,10 +8,13 @@ from datetime import timedelta
|
|||
import logging
|
||||
import os
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
|
||||
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)
|
||||
|
@ -33,6 +36,11 @@ DISCOVERY_PLATFORMS = {
|
|||
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__)
|
||||
|
||||
|
||||
|
@ -75,10 +83,7 @@ def setup(hass, config):
|
|||
"""Handle calls to the lock services."""
|
||||
target_locks = component.extract_from_service(service)
|
||||
|
||||
if ATTR_CODE not in service.data:
|
||||
code = None
|
||||
else:
|
||||
code = service.data[ATTR_CODE]
|
||||
code = service.data.get(ATTR_CODE)
|
||||
|
||||
for item in target_locks:
|
||||
if service.service == SERVICE_LOCK:
|
||||
|
@ -92,10 +97,11 @@ def setup(hass, config):
|
|||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
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,
|
||||
descriptions.get(SERVICE_LOCK))
|
||||
|
||||
descriptions.get(SERVICE_LOCK),
|
||||
schema=LOCK_SERVICE_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import logging
|
|||
from homeassistant.components.lock import LockDevice
|
||||
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):
|
||||
|
|
|
@ -9,6 +9,8 @@ import re
|
|||
from datetime import timedelta
|
||||
from itertools import groupby
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components import recorder, sun
|
||||
from homeassistant.const import (
|
||||
|
@ -18,6 +20,7 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
|
|||
from homeassistant.core import State
|
||||
from homeassistant.helpers.entity import split_entity_id
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DOMAIN = "logbook"
|
||||
DEPENDENCIES = ['recorder', 'http']
|
||||
|
@ -39,6 +42,13 @@ ATTR_MESSAGE = 'message'
|
|||
ATTR_DOMAIN = 'domain'
|
||||
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):
|
||||
"""Add an entry to the logbook."""
|
||||
|
@ -58,19 +68,17 @@ def setup(hass, config):
|
|||
"""Listen for download events to download files."""
|
||||
def log_message(service):
|
||||
"""Handle sending notification message service calls."""
|
||||
message = service.data.get(ATTR_MESSAGE)
|
||||
name = service.data.get(ATTR_NAME)
|
||||
domain = service.data.get(ATTR_DOMAIN, None)
|
||||
entity_id = service.data.get(ATTR_ENTITY_ID, None)
|
||||
|
||||
if not message or not name:
|
||||
return
|
||||
message = service.data[ATTR_MESSAGE]
|
||||
name = service.data[ATTR_NAME]
|
||||
domain = service.data.get(ATTR_DOMAIN)
|
||||
entity_id = service.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
message = template.render(hass, message)
|
||||
log_entry(hass, name, message, domain, entity_id)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -79,7 +87,7 @@ def _handle_get_logbook(handler, path_match, data):
|
|||
date_str = path_match.group('date')
|
||||
|
||||
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:
|
||||
handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST)
|
||||
|
@ -114,7 +122,7 @@ class Entry(object):
|
|||
def as_dict(self):
|
||||
"""Convert entry to a dict to be used within JSON."""
|
||||
return {
|
||||
'when': dt_util.datetime_to_str(self.when),
|
||||
'when': self.when,
|
||||
'name': self.name,
|
||||
'message': self.message,
|
||||
'domain': self.domain,
|
||||
|
|
|
@ -61,6 +61,7 @@ ATTR_APP_ID = 'app_id'
|
|||
ATTR_APP_NAME = 'app_name'
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
|
||||
ATTR_INPUT_SOURCE = 'source'
|
||||
ATTR_INPUT_SOURCE_LIST = 'source_list'
|
||||
|
||||
MEDIA_TYPE_MUSIC = 'music'
|
||||
MEDIA_TYPE_TVSHOW = 'tvshow'
|
||||
|
@ -94,6 +95,7 @@ SERVICE_TO_METHOD = {
|
|||
SERVICE_MEDIA_PAUSE: 'media_pause',
|
||||
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
|
||||
SERVICE_SELECT_SOURCE: 'select_source'
|
||||
}
|
||||
|
||||
ATTR_TO_PROPERTY = [
|
||||
|
@ -116,6 +118,7 @@ ATTR_TO_PROPERTY = [
|
|||
ATTR_APP_NAME,
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS,
|
||||
ATTR_INPUT_SOURCE,
|
||||
ATTR_INPUT_SOURCE_LIST,
|
||||
]
|
||||
|
||||
# Service call validation schemas
|
||||
|
@ -473,6 +476,11 @@ class MediaPlayerDevice(Entity):
|
|||
"""Name of the current input source."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def source_list(self):
|
||||
"""List of available input sources."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
"""Flag media commands that are supported."""
|
||||
|
|
|
@ -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 = \
|
||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
|
@ -208,7 +208,8 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
|||
@property
|
||||
def media_image_url(self):
|
||||
"""Return the image url of current playing media."""
|
||||
return 'https://graph.facebook.com/107771475912710/picture'
|
||||
return 'https://graph.facebook.com/v2.5/107771475912710/' \
|
||||
'picture?type=large'
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
|
@ -287,7 +288,7 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
|
|||
@property
|
||||
def media_image_url(self):
|
||||
"""Return the image url of current playing media."""
|
||||
return 'https://graph.facebook.com/HouseofCards/picture'
|
||||
return 'https://graph.facebook.com/v2.5/HouseofCards/picture?width=400'
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
|
|
|
@ -10,14 +10,16 @@ import socket
|
|||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['python-mpd2==0.5.5']
|
||||
|
||||
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
|
||||
|
@ -64,7 +66,7 @@ class MpdDevice(MediaPlayerDevice):
|
|||
"""Representation of a MPD server."""
|
||||
|
||||
# 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):
|
||||
"""Initialize the MPD device."""
|
||||
import mpd
|
||||
|
@ -203,3 +205,14 @@ class MpdDevice(MediaPlayerDevice):
|
|||
def media_previous_track(self):
|
||||
"""Service to send the MPD the command for previous track."""
|
||||
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))
|
||||
|
|
|
@ -18,6 +18,7 @@ from homeassistant.const import (
|
|||
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
||||
STATE_UNKNOWN)
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.helpers.event import (track_utc_time_change)
|
||||
|
||||
REQUIREMENTS = ['plexapi==1.1.0']
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
@ -113,6 +114,7 @@ def setup_plexserver(host, token, hass, add_devices_callback):
|
|||
|
||||
plex_clients = {}
|
||||
plex_sessions = {}
|
||||
track_utc_time_change(hass, lambda now: update_devices(), second=30)
|
||||
|
||||
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
||||
def update_devices():
|
||||
|
|
|
@ -6,6 +6,7 @@ https://home-assistant.io/components/media_player.sonos/
|
|||
"""
|
||||
import datetime
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||
|
@ -13,7 +14,7 @@ from homeassistant.components.media_player import (
|
|||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
MediaPlayerDevice)
|
||||
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']
|
||||
|
||||
|
@ -36,11 +37,13 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
|||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Sonos platform."""
|
||||
import soco
|
||||
import socket
|
||||
|
||||
if discovery_info:
|
||||
add_devices([SonosDevice(hass, soco.SoCo(discovery_info))])
|
||||
return True
|
||||
player = soco.SoCo(discovery_info)
|
||||
if player.is_visible:
|
||||
add_devices([SonosDevice(hass, player)])
|
||||
return True
|
||||
return False
|
||||
|
||||
players = None
|
||||
hosts = config.get('hosts', None)
|
||||
|
@ -138,9 +141,14 @@ class SonosDevice(MediaPlayerDevice):
|
|||
"""Retrieve latest state."""
|
||||
self._name = self._player.get_speaker_info()['zone_name'].replace(
|
||||
' (R)', '').replace(' (L)', '')
|
||||
self._status = self._player.get_current_transport_info().get(
|
||||
'current_transport_state')
|
||||
self._trackinfo = self._player.get_current_track_info()
|
||||
|
||||
if self.available:
|
||||
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
|
||||
def volume_level(self):
|
||||
|
@ -253,3 +261,15 @@ class SonosDevice(MediaPlayerDevice):
|
|||
def play_media(self, media_type, media_id):
|
||||
"""Send the play_media command to the media player."""
|
||||
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
|
||||
|
|
262
homeassistant/components/media_player/webostv.py
Normal file
262
homeassistant/components/media_player/webostv.py
Normal 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()
|
|
@ -8,13 +8,15 @@ import logging
|
|||
|
||||
from homeassistant.components.media_player import (
|
||||
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
|
||||
|
||||
REQUIREMENTS = ['rxv==0.1.11']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
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):
|
||||
|
@ -34,6 +36,8 @@ class YamahaDevice(MediaPlayerDevice):
|
|||
self._muted = False
|
||||
self._volume = 0
|
||||
self._pwstate = STATE_OFF
|
||||
self._current_source = None
|
||||
self._source_list = None
|
||||
self.update()
|
||||
self._name = name
|
||||
|
||||
|
@ -45,6 +49,8 @@ class YamahaDevice(MediaPlayerDevice):
|
|||
self._pwstate = STATE_OFF
|
||||
self._muted = self._receiver.mute
|
||||
self._volume = (self._receiver.volume/100) + 1
|
||||
self._current_source = self._receiver.input
|
||||
self._source_list = list(self._receiver.inputs().keys())
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -66,6 +72,16 @@ class YamahaDevice(MediaPlayerDevice):
|
|||
"""Boolean if volume is currently 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
|
||||
def supported_media_commands(self):
|
||||
"""Flag of media commands that are supported."""
|
||||
|
@ -89,3 +105,7 @@ class YamahaDevice(MediaPlayerDevice):
|
|||
"""Turn the media player on."""
|
||||
self._receiver.on = True
|
||||
self._volume = (self._receiver.volume/100) + 1
|
||||
|
||||
def select_source(self, source):
|
||||
"""Select input source."""
|
||||
self._receiver.input = source
|
||||
|
|
|
@ -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
|
||||
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,
|
||||
})
|
||||
|
||||
# Sensor type platforms subscribe to mqtt events
|
||||
# Sensor type platforms subscribe to MQTT events
|
||||
MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_STATE_TOPIC): valid_subscribe_topic,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
})
|
||||
|
||||
# Switch type platforms publish to mqtt and may subscribe
|
||||
# Switch type platforms publish to MQTT and may subscribe
|
||||
MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic,
|
||||
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
|
||||
|
|
|
@ -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 logging
|
||||
import tempfile
|
||||
|
|
|
@ -5,33 +5,36 @@ For more details about this platform, please refer to the documentation at
|
|||
https://home-assistant.io/components/sensor.mysensors/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
from homeassistant.const import (
|
||||
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START,
|
||||
EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED, TEMP_CELCIUS,
|
||||
CONF_OPTIMISTIC)
|
||||
from homeassistant.const import (ATTR_DISCOVERED, ATTR_SERVICE,
|
||||
CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
EVENT_PLATFORM_DISCOVERED, TEMP_CELSIUS)
|
||||
from homeassistant.helpers import validate_config
|
||||
|
||||
CONF_GATEWAYS = 'gateways'
|
||||
CONF_PORT = 'port'
|
||||
CONF_DEVICE = 'device'
|
||||
CONF_DEBUG = 'debug'
|
||||
CONF_PERSISTENCE = 'persistence'
|
||||
CONF_PERSISTENCE_FILE = 'persistence_file'
|
||||
CONF_VERSION = 'version'
|
||||
CONF_BAUD_RATE = 'baud_rate'
|
||||
CONF_TCP_PORT = 'tcp_port'
|
||||
DEFAULT_VERSION = '1.4'
|
||||
DEFAULT_BAUD_RATE = 115200
|
||||
DEFAULT_TCP_PORT = 5003
|
||||
|
||||
DOMAIN = 'mysensors'
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = [
|
||||
'https://github.com/theolind/pymysensors/archive/'
|
||||
'f0c928532167fb24823efa793ec21ca646fd37a6.zip#pymysensors==0.5']
|
||||
'cc5d0b325e13c2b623fa934f69eea7cd4555f110.zip#pymysensors==0.6']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ATTR_NODE_ID = 'node_id'
|
||||
ATTR_CHILD_ID = 'child_id'
|
||||
ATTR_PORT = 'port'
|
||||
ATTR_DEVICE = 'device'
|
||||
|
||||
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."""
|
||||
if not validate_config(config,
|
||||
{DOMAIN: [CONF_GATEWAYS]},
|
||||
_LOGGER):
|
||||
return False
|
||||
if not all(CONF_PORT in gateway
|
||||
if not all(CONF_DEVICE in gateway
|
||||
for gateway in config[DOMAIN][CONF_GATEWAYS]):
|
||||
_LOGGER.error('Missing required configuration items '
|
||||
'in %s: %s', DOMAIN, CONF_PORT)
|
||||
'in %s: %s', DOMAIN, CONF_DEVICE)
|
||||
return False
|
||||
|
||||
import mysensors.mysensors as mysensors
|
||||
|
||||
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."""
|
||||
gateway = mysensors.SerialGateway(port, event_callback=None,
|
||||
persistence=persistence,
|
||||
persistence_file=persistence_file,
|
||||
protocol_version=version,
|
||||
baud=baud_rate)
|
||||
try:
|
||||
socket.inet_aton(device)
|
||||
# valid ip address
|
||||
gateway = mysensors.TCPGateway(
|
||||
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.debug = config[DOMAIN].get(CONF_DEBUG, False)
|
||||
optimistic = config[DOMAIN].get(CONF_OPTIMISTIC, False)
|
||||
|
@ -93,22 +105,22 @@ def setup(hass, config):
|
|||
|
||||
return gateway
|
||||
|
||||
# Setup all ports from config
|
||||
# Setup all devices from config
|
||||
global GATEWAYS
|
||||
GATEWAYS = {}
|
||||
conf_gateways = config[DOMAIN][CONF_GATEWAYS]
|
||||
if isinstance(conf_gateways, dict):
|
||||
conf_gateways = [conf_gateways]
|
||||
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
|
||||
|
||||
for index, gway in enumerate(conf_gateways):
|
||||
port = gway[CONF_PORT]
|
||||
device = gway[CONF_DEVICE]
|
||||
persistence_file = gway.get(
|
||||
CONF_PERSISTENCE_FILE,
|
||||
hass.config.path('mysensors{}.pickle'.format(index + 1)))
|
||||
baud_rate = gway.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE)
|
||||
GATEWAYS[port] = setup_gateway(
|
||||
port, persistence, persistence_file, version, baud_rate)
|
||||
tcp_port = gway.get(CONF_TCP_PORT, DEFAULT_TCP_PORT)
|
||||
GATEWAYS[device] = setup_gateway(
|
||||
device, persistence_file, baud_rate, tcp_port)
|
||||
|
||||
for (component, discovery_service) in DISCOVERY_COMPONENTS:
|
||||
# Ensure component is loaded
|
||||
|
@ -139,7 +151,7 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
|
|||
if key in devices:
|
||||
devices[key].update_ha_state(True)
|
||||
continue
|
||||
name = '{} {}.{}'.format(
|
||||
name = '{} {} {}'.format(
|
||||
gateway.sensors[node_id].sketch_name, node_id, child.id)
|
||||
if isinstance(entity_class, dict):
|
||||
device_class = entity_class[child.type]
|
||||
|
|
|
@ -4,6 +4,9 @@ Support for Nest thermostats.
|
|||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/thermostat.nest/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
|
@ -20,14 +23,27 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
})
|
||||
}, 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
|
||||
def setup(hass, config):
|
||||
"""Setup the Nest thermostat component."""
|
||||
global NEST
|
||||
|
||||
username = config[DOMAIN].get(CONF_USERNAME)
|
||||
password = config[DOMAIN].get(CONF_PASSWORD)
|
||||
conf = config[DOMAIN]
|
||||
username = conf[CONF_USERNAME]
|
||||
password = conf[CONF_PASSWORD]
|
||||
|
||||
import nest
|
||||
|
||||
|
|
|
@ -8,11 +8,13 @@ from functools import partial
|
|||
import logging
|
||||
import os
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers import config_per_platform, template
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import CONF_NAME
|
||||
|
||||
DOMAIN = "notify"
|
||||
|
@ -32,6 +34,13 @@ ATTR_DATA = 'data'
|
|||
|
||||
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__)
|
||||
|
||||
|
||||
|
@ -71,13 +80,7 @@ def setup(hass, config):
|
|||
|
||||
def notify_message(notify_service, call):
|
||||
"""Handle sending notification message service calls."""
|
||||
message = call.data.get(ATTR_MESSAGE)
|
||||
|
||||
if message is None:
|
||||
_LOGGER.error(
|
||||
'Received call to %s without attribute %s',
|
||||
call.service, ATTR_MESSAGE)
|
||||
return
|
||||
message = call.data[ATTR_MESSAGE]
|
||||
|
||||
title = template.render(
|
||||
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_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY)
|
||||
hass.services.register(DOMAIN, service_notify, service_call_handler,
|
||||
descriptions.get(SERVICE_NOTIFY))
|
||||
descriptions.get(SERVICE_NOTIFY),
|
||||
schema=NOTIFY_SERVICE_SCHEMA)
|
||||
success = True
|
||||
|
||||
return success
|
||||
|
|
|
@ -44,12 +44,12 @@ class FileNotificationService(BaseNotificationService):
|
|||
if os.stat(self.filepath).st_size == 0:
|
||||
title = '{} notifications (Log started: {})\n{}\n'.format(
|
||||
kwargs.get(ATTR_TITLE),
|
||||
dt_util.strip_microseconds(dt_util.utcnow()),
|
||||
dt_util.utcnow().isoformat(),
|
||||
'-' * 80)
|
||||
file.write(title)
|
||||
|
||||
if self.add_timestamp == 1:
|
||||
text = '{} {}\n'.format(dt_util.utcnow(), message)
|
||||
text = '{} {}\n'.format(dt_util.utcnow().isoformat(), message)
|
||||
file.write(text)
|
||||
else:
|
||||
text = '{}\n'.format(message)
|
||||
|
|
62
homeassistant/components/notify/webostv.py
Normal file
62
homeassistant/components/notify/webostv.py
Normal 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.')
|
|
@ -10,7 +10,10 @@ from homeassistant.components.notify import (
|
|||
ATTR_TITLE, DOMAIN, BaseNotificationService)
|
||||
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__)
|
||||
|
||||
|
@ -22,20 +25,23 @@ def get_service(hass, config):
|
|||
_LOGGER):
|
||||
return None
|
||||
|
||||
return XmppNotificationService(config['sender'],
|
||||
config['password'],
|
||||
config['recipient'])
|
||||
return XmppNotificationService(
|
||||
config.get('sender'),
|
||||
config.get('password'),
|
||||
config.get('recipient'),
|
||||
config.get('tls', True))
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class XmppNotificationService(BaseNotificationService):
|
||||
"""Implement the notification service for Jabber (XMPP)."""
|
||||
|
||||
def __init__(self, sender, password, recipient):
|
||||
def __init__(self, sender, password, recipient, tls):
|
||||
"""Initialize the service."""
|
||||
self._sender = sender
|
||||
self._password = password
|
||||
self._recipient = recipient
|
||||
self._tls = tls
|
||||
|
||||
def send_message(self, message="", **kwargs):
|
||||
"""Send a message to a user."""
|
||||
|
@ -43,10 +49,10 @@ class XmppNotificationService(BaseNotificationService):
|
|||
data = "{}: {}".format(title, message) if title else message
|
||||
|
||||
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."""
|
||||
import sleekxmpp
|
||||
|
||||
|
@ -59,11 +65,11 @@ def send_message(sender, password, recipient, message):
|
|||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
self.use_tls = True
|
||||
self.use_tls = use_tls
|
||||
self.use_ipv6 = False
|
||||
self.add_event_handler('failed_auth', self.check_credentials)
|
||||
self.add_event_handler('session_start', self.start)
|
||||
self.connect()
|
||||
self.connect(use_tls=self.use_tls, use_ssl=False)
|
||||
self.process()
|
||||
|
||||
def start(self, event):
|
||||
|
|
|
@ -478,7 +478,7 @@ class Recorder(threading.Thread):
|
|||
|
||||
def _adapt_datetime(datetimestamp):
|
||||
"""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():
|
||||
|
|
|
@ -38,16 +38,22 @@ _LOGGER = logging.getLogger(__name__)
|
|||
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):
|
||||
return value
|
||||
else:
|
||||
raise vol.Invalid('invalid packet id for {}'.format(value))
|
||||
|
||||
# Share between rfxtrx platforms
|
||||
VALID_DEVICE_ID = vol.All(cv.string, vol.Lower)
|
||||
VALID_SENSOR_DEVICE_ID = vol.All(VALID_DEVICE_ID,
|
||||
vol.truth(lambda val:
|
||||
val.startswith('sensor_')))
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_NAME): cv.string,
|
||||
vol.Required(ATTR_PACKETID): _validate_packetid,
|
||||
vol.Required(ATTR_PACKETID): validate_packetid,
|
||||
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
|
||||
if device_id in RFX_DEVICES:
|
||||
_LOGGER.debug(
|
||||
"EntityID: %s light_update. Command: %s",
|
||||
"EntityID: %s device_update. Command: %s",
|
||||
device_id,
|
||||
event.values['Command']
|
||||
)
|
||||
|
@ -251,7 +257,7 @@ class RfxtrxDevice(Entity):
|
|||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if light is on."""
|
||||
"""Return true if device is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
|
@ -260,7 +266,7 @@ class RfxtrxDevice(Entity):
|
|||
return True
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the light off."""
|
||||
"""Turn the device off."""
|
||||
self._send_command("turn_off")
|
||||
|
||||
def _send_command(self, command, brightness=0):
|
||||
|
|
|
@ -7,10 +7,13 @@ https://home-assistant.io/components/rollershutter/
|
|||
import os
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components import group
|
||||
from homeassistant.const import (
|
||||
SERVICE_MOVE_UP, SERVICE_MOVE_DOWN, SERVICE_STOP,
|
||||
|
@ -33,6 +36,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
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):
|
||||
"""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,
|
||||
handle_rollershutter_service,
|
||||
descriptions.get(SERVICE_MOVE_UP))
|
||||
descriptions.get(SERVICE_MOVE_UP),
|
||||
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
|
||||
hass.services.register(DOMAIN, SERVICE_MOVE_DOWN,
|
||||
handle_rollershutter_service,
|
||||
descriptions.get(SERVICE_MOVE_DOWN))
|
||||
descriptions.get(SERVICE_MOVE_DOWN),
|
||||
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
|
||||
hass.services.register(DOMAIN, SERVICE_STOP,
|
||||
handle_rollershutter_service,
|
||||
descriptions.get(SERVICE_STOP))
|
||||
|
||||
descriptions.get(SERVICE_STOP),
|
||||
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -7,9 +7,12 @@ https://home-assistant.io/components/scene/
|
|||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, CONF_PLATFORM)
|
||||
from homeassistant.helpers import extract_domain_configs
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
|
@ -19,6 +22,10 @@ STATE = 'scening'
|
|||
|
||||
CONF_ENTITIES = "entities"
|
||||
|
||||
SCENE_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
})
|
||||
|
||||
SceneConfig = namedtuple('SceneConfig', ['name', 'states'])
|
||||
|
||||
|
||||
|
@ -61,7 +68,8 @@ def setup(hass, config):
|
|||
for scene in target_scenes:
|
||||
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
|
||||
|
||||
|
|
|
@ -109,6 +109,11 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
vol.Required(DOMAIN): {cv.slug: _SCRIPT_ENTRY_SCHEMA}
|
||||
}, 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):
|
||||
"""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)
|
||||
script = Script(object_id, alias, cfg[CONF_SEQUENCE])
|
||||
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):
|
||||
"""Call a service to turn script on."""
|
||||
|
@ -168,10 +174,12 @@ def setup(hass, config):
|
|||
for script in component.extract_from_service(service):
|
||||
script.toggle()
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on_service)
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off_service)
|
||||
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on_service,
|
||||
schema=SCRIPT_TURN_ONOFF_SCHEMA)
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -98,12 +98,11 @@ class SCSGate:
|
|||
from scsgate.tasks import GetStatusTask
|
||||
|
||||
with self._devices_to_register_lock:
|
||||
if len(self._devices_to_register) == 0:
|
||||
return
|
||||
_, device = self._devices_to_register.popitem()
|
||||
self._devices[device.scs_id] = device
|
||||
self._device_being_registered = device.scs_id
|
||||
self._reactor.append_task(GetStatusTask(target=device.scs_id))
|
||||
while len(self._devices_to_register) != 0:
|
||||
_, device = self._devices_to_register.popitem()
|
||||
self._devices[device.scs_id] = device
|
||||
self._device_being_registered = device.scs_id
|
||||
self._reactor.append_task(GetStatusTask(target=device.scs_id))
|
||||
|
||||
def is_device_registered(self, device_id):
|
||||
"""Check whether a device is already registered or not."""
|
||||
|
|
|
@ -7,13 +7,13 @@ https://home-assistant.io/components/sensor.apcupsd/
|
|||
import logging
|
||||
|
||||
from homeassistant.components import apcupsd
|
||||
from homeassistant.const import TEMP_CELCIUS
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
DEPENDENCIES = [apcupsd.DOMAIN]
|
||||
DEFAULT_NAME = "UPS Status"
|
||||
SPECIFIC_UNITS = {
|
||||
"ITEMP": TEMP_CELCIUS
|
||||
"ITEMP": TEMP_CELSIUS
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
|
@ -4,7 +4,7 @@ Demo platform that has a couple of fake sensors.
|
|||
For more details about this platform, please refer to the documentation
|
||||
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
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@ from homeassistant.helpers.entity import Entity
|
|||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Demo sensors."""
|
||||
add_devices([
|
||||
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12),
|
||||
DemoSensor('Outside Temperature', 15.6, TEMP_CELSIUS, 12),
|
||||
DemoSensor('Outside Humidity', 54, '%', None),
|
||||
])
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ https://home-assistant.io/components/sensor.forecast/
|
|||
import logging
|
||||
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.util import Throttle
|
||||
|
||||
|
@ -64,7 +64,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
if 'units' in config:
|
||||
units = config['units']
|
||||
elif hass.config.temperature_unit == TEMP_CELCIUS:
|
||||
elif hass.config.temperature_unit == TEMP_CELSIUS:
|
||||
units = 'si'
|
||||
else:
|
||||
units = 'us'
|
||||
|
|
|
@ -13,7 +13,7 @@ from homeassistant.helpers.entity import Entity
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ["https://github.com/robbiet480/pygtfs/archive/"
|
||||
"6b40d5fb30fd410cfaf637c901b5ed5a08c33e4c.zip#"
|
||||
"432414b720c580fb2667a0a48f539118a2d95969.zip#"
|
||||
"pygtfs==0.1.2"]
|
||||
|
||||
ICON = "mdi:train"
|
||||
|
|
|
@ -8,12 +8,13 @@ import logging
|
|||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.util import convert
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "loopenergy"
|
||||
|
||||
REQUIREMENTS = ['pyloopenergy==0.0.7']
|
||||
REQUIREMENTS = ['pyloopenergy==0.0.10']
|
||||
|
||||
|
||||
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')
|
||||
gas_serial = config.get('gas_serial')
|
||||
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):
|
||||
_LOGGER.error(
|
||||
|
@ -39,11 +42,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
"serial and secret tokens")
|
||||
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(
|
||||
elec_serial,
|
||||
elec_secret,
|
||||
gas_serial,
|
||||
gas_secret
|
||||
gas_secret,
|
||||
gas_type,
|
||||
gas_calorific
|
||||
)
|
||||
|
||||
def stop_loopenergy(event):
|
||||
|
|
|
@ -9,7 +9,7 @@ import logging
|
|||
import requests
|
||||
|
||||
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.entity import Entity
|
||||
|
||||
|
@ -96,7 +96,7 @@ class MfiSensor(Entity):
|
|||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
if self._port.tag == 'temperature':
|
||||
return TEMP_CELCIUS
|
||||
return TEMP_CELSIUS
|
||||
elif self._port.tag == 'active_pwr':
|
||||
return 'Watts'
|
||||
elif self._port.model == 'Input Digital':
|
||||
|
|
|
@ -8,7 +8,7 @@ import logging
|
|||
|
||||
import homeassistant.components.modbus as modbus
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -97,7 +97,7 @@ class ModbusSensor(Entity):
|
|||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
if self._unit == "C":
|
||||
return TEMP_CELCIUS
|
||||
return TEMP_CELSIUS
|
||||
elif self._unit == "F":
|
||||
return TEMP_FAHRENHEIT
|
||||
else:
|
||||
|
|
|
@ -6,8 +6,8 @@ https://home-assistant.io/components/sensor.mysensors/
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.const import (ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
|
@ -132,7 +132,7 @@ class MySensorsSensor(Entity):
|
|||
"""Return the unit of measurement of this entity."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
unit_map = {
|
||||
set_req.V_TEMP: (TEMP_CELCIUS
|
||||
set_req.V_TEMP: (TEMP_CELSIUS
|
||||
if self.gateway.metric else TEMP_FAHRENHEIT),
|
||||
set_req.V_HUM: '%',
|
||||
set_req.V_DIMMER: '%',
|
||||
|
@ -157,8 +157,13 @@ class MySensorsSensor(Entity):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""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 = {
|
||||
self.mysensors.ATTR_PORT: self.gateway.port,
|
||||
self.mysensors.ATTR_DEVICE: device,
|
||||
self.mysensors.ATTR_NODE_ID: self.node_id,
|
||||
self.mysensors.ATTR_CHILD_ID: self.child_id,
|
||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||
|
|
|
@ -4,12 +4,13 @@ Support for Nest Thermostat Sensors.
|
|||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.nest/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.nest as nest
|
||||
from homeassistant.const import TEMP_CELCIUS
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (
|
||||
TEMP_CELSIUS, CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['nest']
|
||||
SENSOR_TYPES = ['humidity',
|
||||
|
@ -19,49 +20,42 @@ SENSOR_TYPES = ['humidity',
|
|||
'last_connection',
|
||||
'battery_level']
|
||||
|
||||
WEATHER_VARIABLES = ['weather_condition', 'weather_temperature',
|
||||
'weather_humidity',
|
||||
'wind_speed', 'wind_direction']
|
||||
|
||||
JSON_VARIABLE_NAMES = {'weather_humidity': 'humidity',
|
||||
'weather_temperature': 'temperature',
|
||||
'weather_condition': 'condition',
|
||||
'wind_speed': 'kph',
|
||||
'wind_direction': 'direction'}
|
||||
WEATHER_VARS = {'weather_humidity': 'humidity',
|
||||
'weather_temperature': 'temperature',
|
||||
'weather_condition': 'condition',
|
||||
'wind_speed': 'kph',
|
||||
'wind_direction': 'direction'}
|
||||
|
||||
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V',
|
||||
'kph': 'kph', 'temperature': '°C'}
|
||||
|
||||
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):
|
||||
"""Setup the Nest Sensor."""
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
for structure in nest.NEST.structures:
|
||||
for device in structure.devices:
|
||||
for variable in config['monitored_conditions']:
|
||||
if variable in SENSOR_TYPES:
|
||||
add_devices([NestBasicSensor(structure,
|
||||
device,
|
||||
variable)])
|
||||
elif variable in SENSOR_TEMP_TYPES:
|
||||
add_devices([NestTempSensor(structure,
|
||||
device,
|
||||
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."
|
||||
)
|
||||
for structure, device in nest.devices():
|
||||
sensors = [NestBasicSensor(structure, device, variable)
|
||||
for variable in config[CONF_MONITORED_CONDITIONS]
|
||||
if variable in SENSOR_TYPES]
|
||||
sensors += [NestTempSensor(structure, device, variable)
|
||||
for variable in config[CONF_MONITORED_CONDITIONS]
|
||||
if variable in SENSOR_TEMP_TYPES]
|
||||
sensors += [NestWeatherSensor(structure, device,
|
||||
WEATHER_VARS[variable])
|
||||
for variable in config[CONF_MONITORED_CONDITIONS]
|
||||
if variable in WEATHER_VARS]
|
||||
add_devices(sensors)
|
||||
|
||||
|
||||
class NestSensor(Entity):
|
||||
|
@ -109,7 +103,7 @@ class NestTempSensor(NestSensor):
|
|||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
return TEMP_CELCIUS
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
|
|
@ -9,7 +9,7 @@ from datetime import timedelta
|
|||
|
||||
from homeassistant.components.sensor import DOMAIN
|
||||
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.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
@ -22,7 +22,7 @@ REQUIREMENTS = [
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSOR_TYPES = {
|
||||
'temperature': ['Temperature', TEMP_CELCIUS, 'mdi:thermometer'],
|
||||
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
|
||||
'co2': ['CO2', 'ppm', 'mdi:cloud'],
|
||||
'pressure': ['Pressure', 'mbar', 'mdi:gauge'],
|
||||
'noise': ['Noise', 'dB', 'mdi:volume-high'],
|
||||
|
|
|
@ -9,7 +9,7 @@ import os
|
|||
import time
|
||||
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
|
||||
|
||||
BASE_DIR = '/sys/bus/w1/devices/'
|
||||
|
@ -84,7 +84,7 @@ class OneWire(Entity):
|
|||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
return TEMP_CELCIUS
|
||||
return TEMP_CELSIUS
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data from the device."""
|
||||
|
|
|
@ -7,7 +7,7 @@ https://home-assistant.io/components/sensor.openweathermap/
|
|||
import logging
|
||||
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.util import Throttle
|
||||
|
||||
|
@ -106,7 +106,7 @@ class OpenWeatherMapSensor(Entity):
|
|||
if self.type == 'weather':
|
||||
self._state = data.get_detailed_status()
|
||||
elif self.type == 'temperature':
|
||||
if self.temp_unit == TEMP_CELCIUS:
|
||||
if self.temp_unit == TEMP_CELSIUS:
|
||||
self._state = round(data.get_temperature('celsius')['temp'],
|
||||
1)
|
||||
elif self.temp_unit == TEMP_FAHRENHEIT:
|
||||
|
|
|
@ -5,23 +5,18 @@ For more details about this platform, please refer to the documentation at
|
|||
https://home-assistant.io/components/sensor.rest/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'REST Sensor'
|
||||
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
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -96,7 +91,6 @@ class RestData(object):
|
|||
self._verify_ssl = verify_ssl
|
||||
self.data = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Get the latest data from REST service with GET method."""
|
||||
try:
|
||||
|
|
|
@ -6,18 +6,21 @@ https://home-assistant.io/components/sensor.rfxtrx/
|
|||
"""
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.rfxtrx as rfxtrx
|
||||
from homeassistant.const import TEMP_CELCIUS
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.components.rfxtrx import (
|
||||
ATTR_PACKETID, ATTR_NAME, ATTR_DATA_TYPE)
|
||||
ATTR_AUTOMATIC_ADD, ATTR_PACKETID, ATTR_NAME,
|
||||
CONF_DEVICES, ATTR_DATA_TYPE)
|
||||
|
||||
DEPENDENCIES = ['rfxtrx']
|
||||
|
||||
DATA_TYPES = OrderedDict([
|
||||
('Temperature', TEMP_CELCIUS),
|
||||
('Temperature', TEMP_CELSIUS),
|
||||
('Humidity', '%'),
|
||||
('Barometer', ''),
|
||||
('Wind direction', ''),
|
||||
|
@ -26,19 +29,48 @@ DATA_TYPES = OrderedDict([
|
|||
('Total usage', 'W')])
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_NAME, default=None): cv.string,
|
||||
vol.Required(ATTR_PACKETID): rfxtrx.validate_packetid,
|
||||
vol.Optional(ATTR_DATA_TYPE, default=None):
|
||||
vol.In(list(DATA_TYPES.keys())),
|
||||
})
|
||||
|
||||
|
||||
def _valid_device(value):
|
||||
"""Validate a dictionary of devices definitions."""
|
||||
config = OrderedDict()
|
||||
for key, device in value.items():
|
||||
try:
|
||||
key = rfxtrx.VALID_SENSOR_DEVICE_ID(key)
|
||||
config[key] = DEVICE_SCHEMA(device)
|
||||
if not config[key][ATTR_NAME]:
|
||||
config[key][ATTR_NAME] = key
|
||||
except vol.MultipleInvalid as ex:
|
||||
raise vol.Invalid('Rfxtrx sensor {} is invalid: {}'
|
||||
.format(key, ex))
|
||||
return config
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required("platform"): rfxtrx.DOMAIN,
|
||||
vol.Required(CONF_DEVICES): vol.All(dict, _valid_device),
|
||||
vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean,
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Setup the RFXtrx platform."""
|
||||
from RFXtrx import SensorEvent
|
||||
|
||||
sensors = []
|
||||
for device_id, entity_info in config.get('devices', {}).items():
|
||||
for device_id, entity_info in config['devices'].items():
|
||||
if device_id in rfxtrx.RFX_DEVICES:
|
||||
continue
|
||||
_LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME])
|
||||
event = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID])
|
||||
new_sensor = RfxtrxSensor(event, entity_info[ATTR_NAME],
|
||||
entity_info.get(ATTR_DATA_TYPE, None))
|
||||
entity_info[ATTR_DATA_TYPE])
|
||||
rfxtrx.RFX_DEVICES[slugify(device_id)] = new_sensor
|
||||
sensors.append(new_sensor)
|
||||
|
||||
|
@ -62,7 +94,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
return
|
||||
|
||||
# Add entity if not exist and the automatic_add is True
|
||||
if config.get('automatic_add', True):
|
||||
if config[ATTR_AUTOMATIC_ADD]:
|
||||
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
|
||||
entity_name = "%s : %s" % (device_id, pkt_id)
|
||||
_LOGGER.info(
|
||||
|
|
|
@ -23,6 +23,8 @@ ATTR_TARGET = 'Destination'
|
|||
ATTR_REMAINING_TIME = 'Remaining time'
|
||||
ICON = 'mdi:bus'
|
||||
|
||||
TIME_STR_FORMAT = "%H:%M"
|
||||
|
||||
# Return cached results if last scan was less then this time ago.
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||
|
||||
|
@ -126,10 +128,10 @@ class PublicTransportData(object):
|
|||
|
||||
try:
|
||||
self.times = [
|
||||
dt_util.datetime_to_time_str(
|
||||
dt_util.as_local(dt_util.utc_from_timestamp(
|
||||
item['from']['departureTimestamp']))
|
||||
)
|
||||
dt_util.as_local(
|
||||
dt_util.utc_from_timestamp(
|
||||
item['from']['departureTimestamp'])).strftime(
|
||||
TIME_STR_FORMAT)
|
||||
for item in connections
|
||||
]
|
||||
self.times.append(
|
||||
|
|
|
@ -131,9 +131,9 @@ class SystemMonitorSensor(Entity):
|
|||
elif self.type == 'ipv6_address':
|
||||
self._state = psutil.net_if_addrs()[self.argument][1][1]
|
||||
elif self.type == 'last_boot':
|
||||
self._state = dt_util.datetime_to_date_str(
|
||||
dt_util.as_local(
|
||||
dt_util.utc_from_timestamp(psutil.boot_time())))
|
||||
self._state = dt_util.as_local(
|
||||
dt_util.utc_from_timestamp(psutil.boot_time())
|
||||
).date().isoformat()
|
||||
elif self.type == 'since_last_boot':
|
||||
self._state = dt_util.utcnow() - dt_util.utc_from_timestamp(
|
||||
psutil.boot_time())
|
||||
|
|
|
@ -10,7 +10,7 @@ from datetime import datetime
|
|||
|
||||
from homeassistant.components import tellduslive
|
||||
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
|
||||
|
||||
ATTR_LAST_UPDATED = "time_last_updated"
|
||||
|
@ -27,7 +27,7 @@ SENSOR_TYPE_WINDGUST = "wgust"
|
|||
SENSOR_TYPE_WATT = "watt"
|
||||
|
||||
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_RAINRATE: ['Rain rate', 'mm', "mdi:water"],
|
||||
SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', "mdi:water"],
|
||||
|
|
|
@ -8,7 +8,7 @@ import logging
|
|||
from collections import namedtuple
|
||||
|
||||
import homeassistant.util as util
|
||||
from homeassistant.const import TEMP_CELCIUS
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
|
||||
|
@ -25,7 +25,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
sensor_value_descriptions = {
|
||||
tellcore_constants.TELLSTICK_TEMPERATURE:
|
||||
DatatypeDescription(
|
||||
'temperature', config.get('temperature_scale', TEMP_CELCIUS)),
|
||||
'temperature', config.get('temperature_scale', TEMP_CELSIUS)),
|
||||
|
||||
tellcore_constants.TELLSTICK_HUMIDITY:
|
||||
DatatypeDescription('humidity', '%'),
|
||||
|
|
|
@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.temper/
|
|||
"""
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -56,7 +56,9 @@ class TemperSensor(Entity):
|
|||
def update(self):
|
||||
"""Retrieve latest state."""
|
||||
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:
|
||||
_LOGGER.error('Failed to get temperature due to insufficient '
|
||||
'permissions. Try running with "sudo"')
|
||||
|
|
114
homeassistant/components/sensor/thinkingcleaner.py
Normal file
114
homeassistant/components/sensor/thinkingcleaner.py
Normal 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
|
|
@ -19,6 +19,8 @@ OPTION_TYPES = {
|
|||
'time_utc': 'Time (UTC)',
|
||||
}
|
||||
|
||||
TIME_STR_FORMAT = "%H:%M"
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Time and Date sensor."""
|
||||
|
@ -70,9 +72,9 @@ class TimeDateSensor(Entity):
|
|||
def update(self):
|
||||
"""Get the latest data and updates the states."""
|
||||
time_date = dt_util.utcnow()
|
||||
time = dt_util.datetime_to_time_str(dt_util.as_local(time_date))
|
||||
time_utc = dt_util.datetime_to_time_str(time_date)
|
||||
date = dt_util.datetime_to_date_str(dt_util.as_local(time_date))
|
||||
time = dt_util.as_local(time_date).strftime(TIME_STR_FORMAT)
|
||||
time_utc = time_date.strftime(TIME_STR_FORMAT)
|
||||
date = dt_util.as_local(time_date).date().isoformat()
|
||||
|
||||
# Calculate the beat (Swatch Internet Time) time without date.
|
||||
hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':')
|
||||
|
|
|
@ -9,7 +9,7 @@ import logging
|
|||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
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.components.vera import (
|
||||
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
|
||||
|
@ -65,8 +65,7 @@ class VeraSensor(VeraDevice, Entity):
|
|||
last_tripped = self.vera_device.last_trip
|
||||
if last_tripped is not None:
|
||||
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
|
||||
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
|
||||
utc_time)
|
||||
attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
|
||||
else:
|
||||
attr[ATTR_LAST_TRIP_TIME] = None
|
||||
tripped = self.vera_device.is_tripped
|
||||
|
@ -85,7 +84,7 @@ class VeraSensor(VeraDevice, Entity):
|
|||
if vera_temp_units == 'F':
|
||||
self._temperature_units = TEMP_FAHRENHEIT
|
||||
else:
|
||||
self._temperature_units = TEMP_CELCIUS
|
||||
self._temperature_units = TEMP_CELSIUS
|
||||
|
||||
if self.hass:
|
||||
temp = self.hass.config.temperature(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue