mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 22:57:17 +00:00
Merge remote-tracking branch 'balloob/dev' into feature/enhance_wemo
This commit is contained in:
commit
bfbaaa8e9f
40
.coveragerc
40
.coveragerc
@ -10,50 +10,72 @@ omit =
|
|||||||
homeassistant/components/arduino.py
|
homeassistant/components/arduino.py
|
||||||
homeassistant/components/*/arduino.py
|
homeassistant/components/*/arduino.py
|
||||||
|
|
||||||
|
homeassistant/components/isy994.py
|
||||||
|
homeassistant/components/*/isy994.py
|
||||||
|
|
||||||
|
homeassistant/components/modbus.py
|
||||||
|
homeassistant/components/*/modbus.py
|
||||||
|
|
||||||
|
homeassistant/components/*/tellstick.py
|
||||||
|
homeassistant/components/*/vera.py
|
||||||
|
|
||||||
|
homeassistant/components/verisure.py
|
||||||
|
homeassistant/components/*/verisure.py
|
||||||
|
|
||||||
homeassistant/components/wink.py
|
homeassistant/components/wink.py
|
||||||
homeassistant/components/*/wink.py
|
homeassistant/components/*/wink.py
|
||||||
|
|
||||||
homeassistant/components/zwave.py
|
homeassistant/components/zwave.py
|
||||||
homeassistant/components/*/zwave.py
|
homeassistant/components/*/zwave.py
|
||||||
|
|
||||||
homeassistant/components/modbus.py
|
|
||||||
homeassistant/components/*/modbus.py
|
|
||||||
|
|
||||||
homeassistant/components/isy994.py
|
|
||||||
homeassistant/components/*/isy994.py
|
|
||||||
|
|
||||||
homeassistant/components/*/tellstick.py
|
|
||||||
homeassistant/components/*/vera.py
|
|
||||||
|
|
||||||
homeassistant/components/browser.py
|
homeassistant/components/browser.py
|
||||||
|
homeassistant/components/camera/*
|
||||||
|
homeassistant/components/device_tracker/actiontec.py
|
||||||
|
homeassistant/components/device_tracker/asuswrt.py
|
||||||
homeassistant/components/device_tracker/ddwrt.py
|
homeassistant/components/device_tracker/ddwrt.py
|
||||||
homeassistant/components/device_tracker/luci.py
|
homeassistant/components/device_tracker/luci.py
|
||||||
homeassistant/components/device_tracker/netgear.py
|
homeassistant/components/device_tracker/netgear.py
|
||||||
homeassistant/components/device_tracker/nmap_tracker.py
|
homeassistant/components/device_tracker/nmap_tracker.py
|
||||||
|
homeassistant/components/device_tracker/thomson.py
|
||||||
homeassistant/components/device_tracker/tomato.py
|
homeassistant/components/device_tracker/tomato.py
|
||||||
|
homeassistant/components/device_tracker/tplink.py
|
||||||
|
homeassistant/components/discovery.py
|
||||||
|
homeassistant/components/downloader.py
|
||||||
homeassistant/components/keyboard.py
|
homeassistant/components/keyboard.py
|
||||||
homeassistant/components/light/hue.py
|
homeassistant/components/light/hue.py
|
||||||
|
homeassistant/components/light/limitlessled.py
|
||||||
homeassistant/components/media_player/cast.py
|
homeassistant/components/media_player/cast.py
|
||||||
|
homeassistant/components/media_player/kodi.py
|
||||||
homeassistant/components/media_player/mpd.py
|
homeassistant/components/media_player/mpd.py
|
||||||
|
homeassistant/components/media_player/squeezebox.py
|
||||||
homeassistant/components/notify/file.py
|
homeassistant/components/notify/file.py
|
||||||
homeassistant/components/notify/instapush.py
|
homeassistant/components/notify/instapush.py
|
||||||
homeassistant/components/notify/nma.py
|
homeassistant/components/notify/nma.py
|
||||||
homeassistant/components/notify/pushbullet.py
|
homeassistant/components/notify/pushbullet.py
|
||||||
homeassistant/components/notify/pushover.py
|
homeassistant/components/notify/pushover.py
|
||||||
|
homeassistant/components/notify/slack.py
|
||||||
homeassistant/components/notify/smtp.py
|
homeassistant/components/notify/smtp.py
|
||||||
homeassistant/components/notify/syslog.py
|
homeassistant/components/notify/syslog.py
|
||||||
homeassistant/components/notify/xmpp.py
|
homeassistant/components/notify/xmpp.py
|
||||||
homeassistant/components/sensor/bitcoin.py
|
homeassistant/components/sensor/bitcoin.py
|
||||||
|
homeassistant/components/sensor/dht.py
|
||||||
homeassistant/components/sensor/efergy.py
|
homeassistant/components/sensor/efergy.py
|
||||||
homeassistant/components/sensor/forecast.py
|
homeassistant/components/sensor/forecast.py
|
||||||
homeassistant/components/sensor/mysensors.py
|
homeassistant/components/sensor/mysensors.py
|
||||||
homeassistant/components/sensor/openweathermap.py
|
homeassistant/components/sensor/openweathermap.py
|
||||||
|
homeassistant/components/sensor/rfxtrx.py
|
||||||
|
homeassistant/components/sensor/rpi_gpio.py
|
||||||
homeassistant/components/sensor/sabnzbd.py
|
homeassistant/components/sensor/sabnzbd.py
|
||||||
homeassistant/components/sensor/swiss_public_transport.py
|
homeassistant/components/sensor/swiss_public_transport.py
|
||||||
homeassistant/components/sensor/systemmonitor.py
|
homeassistant/components/sensor/systemmonitor.py
|
||||||
|
homeassistant/components/sensor/temper.py
|
||||||
homeassistant/components/sensor/time_date.py
|
homeassistant/components/sensor/time_date.py
|
||||||
homeassistant/components/sensor/transmission.py
|
homeassistant/components/sensor/transmission.py
|
||||||
|
homeassistant/components/switch/command_switch.py
|
||||||
|
homeassistant/components/switch/edimax.py
|
||||||
homeassistant/components/switch/hikvisioncam.py
|
homeassistant/components/switch/hikvisioncam.py
|
||||||
|
homeassistant/components/switch/rpi_gpio.py
|
||||||
|
homeassistant/components/switch/transmission.py
|
||||||
homeassistant/components/switch/wemo.py
|
homeassistant/components/switch/wemo.py
|
||||||
homeassistant/components/thermostat/nest.py
|
homeassistant/components/thermostat/nest.py
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@ homeassistant/components/frontend/www_static/polymer/bower_components/*
|
|||||||
config/custom_components/*
|
config/custom_components/*
|
||||||
!config/custom_components/example.py
|
!config/custom_components/example.py
|
||||||
!config/custom_components/hello_world.py
|
!config/custom_components/hello_world.py
|
||||||
|
!config/custom_components/mqtt_example.py
|
||||||
|
|
||||||
# Hide sublime text stuff
|
# Hide sublime text stuff
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
|
@ -3,7 +3,7 @@ language: python
|
|||||||
python:
|
python:
|
||||||
- "3.4"
|
- "3.4"
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements_all.txt
|
||||||
- pip install flake8 pylint coveralls
|
- pip install flake8 pylint coveralls
|
||||||
script:
|
script:
|
||||||
- flake8 homeassistant --exclude bower_components,external
|
- flake8 homeassistant --exclude bower_components,external
|
||||||
|
12
Dockerfile
12
Dockerfile
@ -3,10 +3,12 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
|
|||||||
|
|
||||||
VOLUME /config
|
VOLUME /config
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN pip3 install --no-cache-dir -r requirements_all.txt
|
||||||
apt-get install -y cython3 libudev-dev && \
|
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
#RUN apt-get update && \
|
||||||
pip3 install cython && \
|
# apt-get install -y cython3 libudev-dev && \
|
||||||
scripts/build_python_openzwave
|
# apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||||
|
# pip3 install cython && \
|
||||||
|
# scripts/build_python_openzwave
|
||||||
|
|
||||||
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
|
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
|
||||||
|
47
README.md
47
README.md
@ -1,18 +1,26 @@
|
|||||||
# Home Assistant [](https://travis-ci.org/balloob/home-assistant) [](https://coveralls.io/r/balloob/home-assistant?branch=master) [](https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# Home Assistant [](https://travis-ci.org/balloob/home-assistant) [](https://coveralls.io/r/balloob/home-assistant?branch=master) [](https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control. [Open a demo.](https://home-assistant.io/demo/)
|
[demo]: https://home-assistant.io/demo/
|
||||||
|
|
||||||
Check out [the website](https://home-assistant.io) for installation instructions, tutorials and documentation.
|
Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control.
|
||||||
|
|
||||||
[](https://home-assistant.io/demo/)
|
To get started:
|
||||||
|
```bash
|
||||||
|
python3 -m pip install homeassistant
|
||||||
|
hass --open-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out [the website](https://home-assistant.io) for [a demo][demo], installation instructions, tutorials and documentation.
|
||||||
|
|
||||||
|
[][demo]
|
||||||
|
|
||||||
Examples of devices it can interface it:
|
Examples of devices it can interface it:
|
||||||
|
|
||||||
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/)
|
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
|
||||||
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
|
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Efergy](https://efergy.com) plugs, [Edimax](http://www.edimax.com/) switches, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
|
||||||
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/) and [Kodi (XBMC)](http://kodi.tv/)
|
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), and [Kodi (XBMC)](http://kodi.tv/)
|
||||||
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), and [Modbus](http://www.modbus.org/)
|
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
|
||||||
* Integrate data from the [Bitcoin](https://bitcoin.org) network, local meteorological data from [OpenWeatherMap](http://openweathermap.org/), [Transmission](http://www.transmissionbt.com/) or [SABnzbd](http://sabnzbd.org).
|
* Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org).
|
||||||
* [See full list of supported devices](https://home-assistant.io/components/)
|
* [See full list of supported devices](https://home-assistant.io/components/)
|
||||||
|
|
||||||
Built home automation on top of your devices:
|
Built home automation on top of your devices:
|
||||||
@ -21,26 +29,9 @@ Built home automation on top of your devices:
|
|||||||
* Turn on the lights when people get home after sun set
|
* Turn on the lights when people get home after sun set
|
||||||
* Turn on lights slowly during sun set to compensate for less light
|
* Turn on lights slowly during sun set to compensate for less light
|
||||||
* Turn off all lights and devices when everybody leaves the house
|
* Turn off all lights and devices when everybody leaves the house
|
||||||
* Offers a [REST API](https://home-assistant.io/developers/api.html) for easy integration with other projects
|
* Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects
|
||||||
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), and [Jabber (XMPP)](http://xmpp.org)
|
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), and [Jabber (XMPP)](http://xmpp.org)
|
||||||
|
|
||||||
The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html).
|
The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html).
|
||||||
|
|
||||||
If you run into issues while using Home Assistant or during development of a component, reach out to the [Home Assistant help section](https://home-assistant.io/help/) how to reach us.
|
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.
|
||||||
|
|
||||||
## Quick-start guide
|
|
||||||
|
|
||||||
Running Home Assistant requires [Python 3.4](https://www.python.org/). Run the following code to get up and running:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone --recursive https://github.com/balloob/home-assistant.git
|
|
||||||
python3 -m venv home-assistant
|
|
||||||
cd home-assistant
|
|
||||||
python3 -m homeassistant --open-ui
|
|
||||||
```
|
|
||||||
|
|
||||||
The last command will start the Home Assistant server and launch its web interface. By default Home Assistant looks for the configuration file `config/configuration.yaml`. A standard configuration file will be written if none exists.
|
|
||||||
|
|
||||||
If you are still exploring if you want to use Home Assistant in the first place, you can enable the demo mode by adding the `--demo-mode` argument to the last command.
|
|
||||||
|
|
||||||
Please see [the getting started guide](https://home-assistant.io/getting-started/) on how to further configure Home Assistant.
|
|
||||||
|
60
config/custom_components/mqtt_example.py
Normal file
60
config/custom_components/mqtt_example.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""
|
||||||
|
custom_components.mqtt_example
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Shows how to communicate with MQTT. Follows a topic on MQTT and updates the
|
||||||
|
state of an entity to the last message received on that topic.
|
||||||
|
|
||||||
|
Also offers a service 'set_state' that will publish a message on the topic that
|
||||||
|
will be passed via MQTT to our message received listener. Call the service with
|
||||||
|
example payload {"new_state": "some new state"}.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the mqtt_example component you will need to add the following to your
|
||||||
|
config/configuration.yaml
|
||||||
|
|
||||||
|
mqtt_example:
|
||||||
|
topic: home-assistant/mqtt_example
|
||||||
|
|
||||||
|
"""
|
||||||
|
import homeassistant.loader as loader
|
||||||
|
|
||||||
|
# The domain of your component. Should be equal to the name of your component
|
||||||
|
DOMAIN = "mqtt_example"
|
||||||
|
|
||||||
|
# List of component names (string) your component depends upon
|
||||||
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
|
||||||
|
CONF_TOPIC = 'topic'
|
||||||
|
DEFAULT_TOPIC = 'home-assistant/mqtt_example'
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Setup our mqtt_example component. """
|
||||||
|
mqtt = loader.get_component('mqtt')
|
||||||
|
topic = config[DOMAIN].get('topic', DEFAULT_TOPIC)
|
||||||
|
entity_id = 'mqtt_example.last_message'
|
||||||
|
|
||||||
|
# Listen to a message on MQTT
|
||||||
|
|
||||||
|
def message_received(topic, payload, qos):
|
||||||
|
""" A new MQTT message has been received. """
|
||||||
|
hass.states.set(entity_id, payload)
|
||||||
|
|
||||||
|
mqtt.subscribe(hass, topic, message_received)
|
||||||
|
|
||||||
|
hass.states.set(entity_id, 'No messages')
|
||||||
|
|
||||||
|
# Service to publish a message on MQTT
|
||||||
|
|
||||||
|
def set_state_service(call):
|
||||||
|
""" Service to send a message. """
|
||||||
|
mqtt.publish(hass, topic, call.data.get('new_state'))
|
||||||
|
|
||||||
|
# Register our service with Home Assistant
|
||||||
|
hass.services.register(DOMAIN, 'set_state', set_state_service)
|
||||||
|
|
||||||
|
# return boolean to indicate that initialization was successful
|
||||||
|
return True
|
@ -1,988 +0,0 @@
|
|||||||
"""
|
|
||||||
homeassistant
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Home Assistant is a Home Automation framework for observing the state
|
|
||||||
of entities and react to changes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
import enum
|
|
||||||
import re
|
|
||||||
import functools as ft
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
|
||||||
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
|
||||||
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
|
|
||||||
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
|
||||||
import homeassistant.util as util
|
|
||||||
import homeassistant.util.dt as date_util
|
|
||||||
|
|
||||||
DOMAIN = "homeassistant"
|
|
||||||
|
|
||||||
# How often time_changed event should fire
|
|
||||||
TIMER_INTERVAL = 1 # seconds
|
|
||||||
|
|
||||||
# How long we wait for the result of a service call
|
|
||||||
SERVICE_CALL_LIMIT = 10 # seconds
|
|
||||||
|
|
||||||
# Define number of MINIMUM worker threads.
|
|
||||||
# During bootstrap of HA (see bootstrap.from_config_dict()) worker threads
|
|
||||||
# will be added for each component that polls devices.
|
|
||||||
MIN_WORKER_THREAD = 2
|
|
||||||
|
|
||||||
# Pattern for validating entity IDs (format: <domain>.<entity>)
|
|
||||||
ENTITY_ID_PATTERN = re.compile(r"^(?P<domain>\w+)\.(?P<entity>\w+)$")
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistant(object):
|
|
||||||
""" Core class to route all communication to right components. """
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.pool = pool = create_worker_pool()
|
|
||||||
self.bus = EventBus(pool)
|
|
||||||
self.services = ServiceRegistry(self.bus, pool)
|
|
||||||
self.states = StateMachine(self.bus)
|
|
||||||
self.config = Config()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def components(self):
|
|
||||||
""" DEPRECATED 3/21/2015. Use hass.config.components """
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.components is deprecated. Use hass.config.components')
|
|
||||||
return self.config.components
|
|
||||||
|
|
||||||
@property
|
|
||||||
def local_api(self):
|
|
||||||
""" DEPRECATED 3/21/2015. Use hass.config.api """
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.local_api is deprecated. Use hass.config.api')
|
|
||||||
return self.config.api
|
|
||||||
|
|
||||||
@property
|
|
||||||
def config_dir(self):
|
|
||||||
""" DEPRECATED 3/18/2015. Use hass.config.config_dir """
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.config_dir is deprecated. Use hass.config.config_dir')
|
|
||||||
return self.config.config_dir
|
|
||||||
|
|
||||||
def get_config_path(self, path):
|
|
||||||
""" DEPRECATED 3/18/2015. Use hass.config.path """
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.get_config_path is deprecated. Use hass.config.path')
|
|
||||||
return self.config.path(path)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
""" Start home assistant. """
|
|
||||||
_LOGGER.info(
|
|
||||||
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
|
||||||
|
|
||||||
Timer(self)
|
|
||||||
|
|
||||||
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
|
||||||
|
|
||||||
def block_till_stopped(self):
|
|
||||||
""" Will register service homeassistant/stop and
|
|
||||||
will block until called. """
|
|
||||||
request_shutdown = threading.Event()
|
|
||||||
|
|
||||||
self.services.register(DOMAIN, SERVICE_HOMEASSISTANT_STOP,
|
|
||||||
lambda service: request_shutdown.set())
|
|
||||||
|
|
||||||
while not request_shutdown.isSet():
|
|
||||||
try:
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def track_point_in_time(self, action, point_in_time):
|
|
||||||
"""
|
|
||||||
Adds a listener that fires once after a spefic point in time.
|
|
||||||
"""
|
|
||||||
utc_point_in_time = date_util.as_utc(point_in_time)
|
|
||||||
|
|
||||||
@ft.wraps(action)
|
|
||||||
def utc_converter(utc_now):
|
|
||||||
""" Converts passed in UTC now to local now. """
|
|
||||||
action(date_util.as_local(utc_now))
|
|
||||||
|
|
||||||
self.track_point_in_utc_time(utc_converter, utc_point_in_time)
|
|
||||||
|
|
||||||
def track_point_in_utc_time(self, action, point_in_time):
|
|
||||||
"""
|
|
||||||
Adds a listener that fires once after a specific point in UTC time.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@ft.wraps(action)
|
|
||||||
def point_in_time_listener(event):
|
|
||||||
""" Listens for matching time_changed events. """
|
|
||||||
now = event.data[ATTR_NOW]
|
|
||||||
|
|
||||||
if now >= point_in_time and \
|
|
||||||
not hasattr(point_in_time_listener, 'run'):
|
|
||||||
|
|
||||||
# Set variable so that we will never run twice.
|
|
||||||
# Because the event bus might have to wait till a thread comes
|
|
||||||
# available to execute this listener it might occur that the
|
|
||||||
# listener gets lined up twice to be executed. This will make
|
|
||||||
# sure the second time it does nothing.
|
|
||||||
point_in_time_listener.run = True
|
|
||||||
|
|
||||||
self.bus.remove_listener(EVENT_TIME_CHANGED,
|
|
||||||
point_in_time_listener)
|
|
||||||
|
|
||||||
action(now)
|
|
||||||
|
|
||||||
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
|
||||||
return point_in_time_listener
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
def track_utc_time_change(self, action,
|
|
||||||
year=None, month=None, day=None,
|
|
||||||
hour=None, minute=None, second=None):
|
|
||||||
""" Adds a listener that will fire if time matches a pattern. """
|
|
||||||
self.track_time_change(
|
|
||||||
action, year, month, day, hour, minute, second, utc=True)
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
def track_time_change(self, action,
|
|
||||||
year=None, month=None, day=None,
|
|
||||||
hour=None, minute=None, second=None, utc=False):
|
|
||||||
""" Adds a listener that will fire if UTC time matches a pattern. """
|
|
||||||
|
|
||||||
# We do not have to wrap the function with time pattern matching logic
|
|
||||||
# if no pattern given
|
|
||||||
if any((val is not None for val in
|
|
||||||
(year, month, day, hour, minute, second))):
|
|
||||||
|
|
||||||
pmp = _process_match_param
|
|
||||||
year, month, day = pmp(year), pmp(month), pmp(day)
|
|
||||||
hour, minute, second = pmp(hour), pmp(minute), pmp(second)
|
|
||||||
|
|
||||||
@ft.wraps(action)
|
|
||||||
def time_listener(event):
|
|
||||||
""" Listens for matching time_changed events. """
|
|
||||||
now = event.data[ATTR_NOW]
|
|
||||||
|
|
||||||
if not utc:
|
|
||||||
now = date_util.as_local(now)
|
|
||||||
|
|
||||||
mat = _matcher
|
|
||||||
|
|
||||||
if mat(now.year, year) and \
|
|
||||||
mat(now.month, month) and \
|
|
||||||
mat(now.day, day) and \
|
|
||||||
mat(now.hour, hour) and \
|
|
||||||
mat(now.minute, minute) and \
|
|
||||||
mat(now.second, second):
|
|
||||||
|
|
||||||
action(now)
|
|
||||||
|
|
||||||
else:
|
|
||||||
@ft.wraps(action)
|
|
||||||
def time_listener(event):
|
|
||||||
""" Fires every time event that comes in. """
|
|
||||||
action(event.data[ATTR_NOW])
|
|
||||||
|
|
||||||
self.bus.listen(EVENT_TIME_CHANGED, time_listener)
|
|
||||||
return time_listener
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
""" Stops Home Assistant and shuts down all threads. """
|
|
||||||
_LOGGER.info("Stopping")
|
|
||||||
|
|
||||||
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
|
||||||
|
|
||||||
# Wait till all responses to homeassistant_stop are done
|
|
||||||
self.pool.block_till_done()
|
|
||||||
|
|
||||||
self.pool.stop()
|
|
||||||
|
|
||||||
def get_entity_ids(self, domain_filter=None):
|
|
||||||
"""
|
|
||||||
Returns known entity ids.
|
|
||||||
|
|
||||||
THIS METHOD IS DEPRECATED. Use hass.states.entity_ids
|
|
||||||
"""
|
|
||||||
_LOGGER.warning(
|
|
||||||
"hass.get_entiy_ids is deprecated. Use hass.states.entity_ids")
|
|
||||||
|
|
||||||
return self.states.entity_ids(domain_filter)
|
|
||||||
|
|
||||||
def listen_once_event(self, event_type, listener):
|
|
||||||
""" Listen once for event of a specific type.
|
|
||||||
|
|
||||||
To listen to all events specify the constant ``MATCH_ALL``
|
|
||||||
as event_type.
|
|
||||||
|
|
||||||
Note: at the moment it is impossible to remove a one time listener.
|
|
||||||
|
|
||||||
THIS METHOD IS DEPRECATED. Please use hass.events.listen_once.
|
|
||||||
"""
|
|
||||||
_LOGGER.warning(
|
|
||||||
"hass.listen_once_event is deprecated. Use hass.bus.listen_once")
|
|
||||||
|
|
||||||
self.bus.listen_once(event_type, listener)
|
|
||||||
|
|
||||||
def track_state_change(self, entity_ids, action,
|
|
||||||
from_state=None, to_state=None):
|
|
||||||
"""
|
|
||||||
Track specific state changes.
|
|
||||||
entity_ids, from_state and to_state can be string or list.
|
|
||||||
Use list to match multiple.
|
|
||||||
|
|
||||||
THIS METHOD IS DEPRECATED. Use hass.states.track_change
|
|
||||||
"""
|
|
||||||
_LOGGER.warning((
|
|
||||||
"hass.track_state_change is deprecated. "
|
|
||||||
"Use hass.states.track_change"))
|
|
||||||
|
|
||||||
self.states.track_change(entity_ids, action, from_state, to_state)
|
|
||||||
|
|
||||||
def call_service(self, domain, service, service_data=None):
|
|
||||||
"""
|
|
||||||
Fires event to call specified service.
|
|
||||||
|
|
||||||
THIS METHOD IS DEPRECATED. Use hass.services.call
|
|
||||||
"""
|
|
||||||
_LOGGER.warning((
|
|
||||||
"hass.services.call is deprecated. "
|
|
||||||
"Use hass.services.call"))
|
|
||||||
|
|
||||||
self.services.call(domain, service, service_data)
|
|
||||||
|
|
||||||
|
|
||||||
def _process_match_param(parameter):
|
|
||||||
""" Wraps parameter in a list if it is not one and returns it. """
|
|
||||||
if parameter is None or parameter == MATCH_ALL:
|
|
||||||
return MATCH_ALL
|
|
||||||
elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
|
|
||||||
return (parameter,)
|
|
||||||
else:
|
|
||||||
return tuple(parameter)
|
|
||||||
|
|
||||||
|
|
||||||
def _matcher(subject, pattern):
|
|
||||||
""" Returns True if subject matches the pattern.
|
|
||||||
|
|
||||||
Pattern is either a list of allowed subjects or a `MATCH_ALL`.
|
|
||||||
"""
|
|
||||||
return MATCH_ALL == pattern or subject in pattern
|
|
||||||
|
|
||||||
|
|
||||||
class JobPriority(util.OrderedEnum):
|
|
||||||
""" Provides priorities for bus events. """
|
|
||||||
# pylint: disable=no-init,too-few-public-methods
|
|
||||||
|
|
||||||
EVENT_CALLBACK = 0
|
|
||||||
EVENT_SERVICE = 1
|
|
||||||
EVENT_STATE = 2
|
|
||||||
EVENT_TIME = 3
|
|
||||||
EVENT_DEFAULT = 4
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_event_type(event_type):
|
|
||||||
""" Returns a priority based on event type. """
|
|
||||||
if event_type == EVENT_TIME_CHANGED:
|
|
||||||
return JobPriority.EVENT_TIME
|
|
||||||
elif event_type == EVENT_STATE_CHANGED:
|
|
||||||
return JobPriority.EVENT_STATE
|
|
||||||
elif event_type == EVENT_CALL_SERVICE:
|
|
||||||
return JobPriority.EVENT_SERVICE
|
|
||||||
elif event_type == EVENT_SERVICE_EXECUTED:
|
|
||||||
return JobPriority.EVENT_CALLBACK
|
|
||||||
else:
|
|
||||||
return JobPriority.EVENT_DEFAULT
|
|
||||||
|
|
||||||
|
|
||||||
def create_worker_pool():
|
|
||||||
""" Creates a worker pool to be used. """
|
|
||||||
|
|
||||||
def job_handler(job):
|
|
||||||
""" Called whenever a job is available to do. """
|
|
||||||
try:
|
|
||||||
func, arg = job
|
|
||||||
func(arg)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
# Catch any exception our service/event_listener might throw
|
|
||||||
# We do not want to crash our ThreadPool
|
|
||||||
_LOGGER.exception("BusHandler:Exception doing job")
|
|
||||||
|
|
||||||
def busy_callback(worker_count, current_jobs, pending_jobs_count):
|
|
||||||
""" Callback to be called when the pool queue gets too big. """
|
|
||||||
|
|
||||||
_LOGGER.warning(
|
|
||||||
"WorkerPool:All %d threads are busy and %d jobs pending",
|
|
||||||
worker_count, pending_jobs_count)
|
|
||||||
|
|
||||||
for start, job in current_jobs:
|
|
||||||
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
|
||||||
date_util.datetime_to_local_str(start), job)
|
|
||||||
|
|
||||||
return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback)
|
|
||||||
|
|
||||||
|
|
||||||
class EventOrigin(enum.Enum):
|
|
||||||
""" Distinguish between origin of event. """
|
|
||||||
# pylint: disable=no-init,too-few-public-methods
|
|
||||||
|
|
||||||
local = "LOCAL"
|
|
||||||
remote = "REMOTE"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class Event(object):
|
|
||||||
""" Represents an event within the Bus. """
|
|
||||||
|
|
||||||
__slots__ = ['event_type', 'data', 'origin', 'time_fired']
|
|
||||||
|
|
||||||
def __init__(self, event_type, data=None, origin=EventOrigin.local,
|
|
||||||
time_fired=None):
|
|
||||||
self.event_type = event_type
|
|
||||||
self.data = data or {}
|
|
||||||
self.origin = origin
|
|
||||||
self.time_fired = util.strip_microseconds(
|
|
||||||
time_fired or date_util.utcnow())
|
|
||||||
|
|
||||||
def as_dict(self):
|
|
||||||
""" Returns a dict representation of this Event. """
|
|
||||||
return {
|
|
||||||
'event_type': self.event_type,
|
|
||||||
'data': dict(self.data),
|
|
||||||
'origin': str(self.origin),
|
|
||||||
'time_fired': date_util.datetime_to_str(self.time_fired),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
# pylint: disable=maybe-no-member
|
|
||||||
if self.data:
|
|
||||||
return "<Event {}[{}]: {}>".format(
|
|
||||||
self.event_type, str(self.origin)[0],
|
|
||||||
util.repr_helper(self.data))
|
|
||||||
else:
|
|
||||||
return "<Event {}[{}]>".format(self.event_type,
|
|
||||||
str(self.origin)[0])
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (self.__class__ == other.__class__ and
|
|
||||||
self.event_type == other.event_type and
|
|
||||||
self.data == other.data and
|
|
||||||
self.origin == other.origin and
|
|
||||||
self.time_fired == other.time_fired)
|
|
||||||
|
|
||||||
|
|
||||||
class EventBus(object):
|
|
||||||
""" Class that allows different components to communicate via services
|
|
||||||
and events.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, pool=None):
|
|
||||||
self._listeners = {}
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
self._pool = pool or create_worker_pool()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def listeners(self):
|
|
||||||
""" Dict with events that is being listened for and the number
|
|
||||||
of listeners.
|
|
||||||
"""
|
|
||||||
with self._lock:
|
|
||||||
return {key: len(self._listeners[key])
|
|
||||||
for key in self._listeners}
|
|
||||||
|
|
||||||
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
|
||||||
""" Fire an event. """
|
|
||||||
if not self._pool.running:
|
|
||||||
raise HomeAssistantError('Home Assistant has shut down.')
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
# Copy the list of the current listeners because some listeners
|
|
||||||
# remove themselves as a listener while being executed which
|
|
||||||
# causes the iterator to be confused.
|
|
||||||
get = self._listeners.get
|
|
||||||
listeners = get(MATCH_ALL, []) + get(event_type, [])
|
|
||||||
|
|
||||||
event = Event(event_type, event_data, origin)
|
|
||||||
|
|
||||||
if event_type != EVENT_TIME_CHANGED:
|
|
||||||
_LOGGER.info("Bus:Handling %s", event)
|
|
||||||
|
|
||||||
if not listeners:
|
|
||||||
return
|
|
||||||
|
|
||||||
job_priority = JobPriority.from_event_type(event_type)
|
|
||||||
|
|
||||||
for func in listeners:
|
|
||||||
self._pool.add_job(job_priority, (func, event))
|
|
||||||
|
|
||||||
def listen(self, event_type, listener):
|
|
||||||
""" Listen for all events or events of a specific type.
|
|
||||||
|
|
||||||
To listen to all events specify the constant ``MATCH_ALL``
|
|
||||||
as event_type.
|
|
||||||
"""
|
|
||||||
with self._lock:
|
|
||||||
if event_type in self._listeners:
|
|
||||||
self._listeners[event_type].append(listener)
|
|
||||||
else:
|
|
||||||
self._listeners[event_type] = [listener]
|
|
||||||
|
|
||||||
def listen_once(self, event_type, listener):
|
|
||||||
""" Listen once for event of a specific type.
|
|
||||||
|
|
||||||
To listen to all events specify the constant ``MATCH_ALL``
|
|
||||||
as event_type.
|
|
||||||
|
|
||||||
Note: at the moment it is impossible to remove a one time listener.
|
|
||||||
"""
|
|
||||||
@ft.wraps(listener)
|
|
||||||
def onetime_listener(event):
|
|
||||||
""" Removes listener from eventbus and then fires listener. """
|
|
||||||
if not hasattr(onetime_listener, 'run'):
|
|
||||||
# Set variable so that we will never run twice.
|
|
||||||
# Because the event bus might have to wait till a thread comes
|
|
||||||
# available to execute this listener it might occur that the
|
|
||||||
# listener gets lined up twice to be executed.
|
|
||||||
# This will make sure the second time it does nothing.
|
|
||||||
onetime_listener.run = True
|
|
||||||
|
|
||||||
self.remove_listener(event_type, onetime_listener)
|
|
||||||
|
|
||||||
listener(event)
|
|
||||||
|
|
||||||
self.listen(event_type, onetime_listener)
|
|
||||||
|
|
||||||
def remove_listener(self, event_type, listener):
|
|
||||||
""" Removes a listener of a specific event_type. """
|
|
||||||
with self._lock:
|
|
||||||
try:
|
|
||||||
self._listeners[event_type].remove(listener)
|
|
||||||
|
|
||||||
# delete event_type list if empty
|
|
||||||
if not self._listeners[event_type]:
|
|
||||||
self._listeners.pop(event_type)
|
|
||||||
|
|
||||||
except (KeyError, ValueError):
|
|
||||||
# KeyError is key event_type listener did not exist
|
|
||||||
# ValueError if listener did not exist within event_type
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class State(object):
|
|
||||||
"""
|
|
||||||
Object to represent a state within the state machine.
|
|
||||||
|
|
||||||
entity_id: the entity that is represented.
|
|
||||||
state: the state of the entity
|
|
||||||
attributes: extra information on entity and state
|
|
||||||
last_changed: last time the state was changed, not the attributes.
|
|
||||||
last_updated: last time this object was updated.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ['entity_id', 'state', 'attributes',
|
|
||||||
'last_changed', 'last_updated']
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
def __init__(self, entity_id, state, attributes=None, last_changed=None,
|
|
||||||
last_updated=None):
|
|
||||||
if not ENTITY_ID_PATTERN.match(entity_id):
|
|
||||||
raise InvalidEntityFormatError((
|
|
||||||
"Invalid entity id encountered: {}. "
|
|
||||||
"Format should be <domain>.<object_id>").format(entity_id))
|
|
||||||
|
|
||||||
self.entity_id = entity_id.lower()
|
|
||||||
self.state = state
|
|
||||||
self.attributes = attributes or {}
|
|
||||||
self.last_updated = date_util.strip_microseconds(
|
|
||||||
last_updated or date_util.utcnow())
|
|
||||||
|
|
||||||
# Strip microsecond from last_changed else we cannot guarantee
|
|
||||||
# state == State.from_dict(state.as_dict())
|
|
||||||
# This behavior occurs because to_dict uses datetime_to_str
|
|
||||||
# which does not preserve microseconds
|
|
||||||
self.last_changed = date_util.strip_microseconds(
|
|
||||||
last_changed or self.last_updated)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def domain(self):
|
|
||||||
""" Returns domain of this state. """
|
|
||||||
return util.split_entity_id(self.entity_id)[0]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def object_id(self):
|
|
||||||
""" Returns object_id of this state. """
|
|
||||||
return util.split_entity_id(self.entity_id)[1]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
""" Name to represent this state. """
|
|
||||||
return (
|
|
||||||
self.attributes.get(ATTR_FRIENDLY_NAME) or
|
|
||||||
self.object_id.replace('_', ' '))
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
""" Creates a copy of itself. """
|
|
||||||
return State(self.entity_id, self.state,
|
|
||||||
dict(self.attributes), self.last_changed)
|
|
||||||
|
|
||||||
def as_dict(self):
|
|
||||||
""" Converts State to a dict to be used within JSON.
|
|
||||||
Ensures: state == State.from_dict(state.as_dict()) """
|
|
||||||
|
|
||||||
return {'entity_id': self.entity_id,
|
|
||||||
'state': self.state,
|
|
||||||
'attributes': self.attributes,
|
|
||||||
'last_changed': date_util.datetime_to_str(self.last_changed),
|
|
||||||
'last_updated': date_util.datetime_to_str(self.last_updated)}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, json_dict):
|
|
||||||
""" Static method to create a state from a dict.
|
|
||||||
Ensures: state == State.from_json_dict(state.to_json_dict()) """
|
|
||||||
|
|
||||||
if not (json_dict and
|
|
||||||
'entity_id' in json_dict and
|
|
||||||
'state' in json_dict):
|
|
||||||
return None
|
|
||||||
|
|
||||||
last_changed = json_dict.get('last_changed')
|
|
||||||
|
|
||||||
if last_changed:
|
|
||||||
last_changed = date_util.str_to_datetime(last_changed)
|
|
||||||
|
|
||||||
last_updated = json_dict.get('last_updated')
|
|
||||||
|
|
||||||
if last_updated:
|
|
||||||
last_updated = date_util.str_to_datetime(last_updated)
|
|
||||||
|
|
||||||
return cls(json_dict['entity_id'], json_dict['state'],
|
|
||||||
json_dict.get('attributes'), last_changed, last_updated)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (self.__class__ == other.__class__ and
|
|
||||||
self.entity_id == other.entity_id and
|
|
||||||
self.state == other.state and
|
|
||||||
self.attributes == other.attributes)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
attr = "; {}".format(util.repr_helper(self.attributes)) \
|
|
||||||
if self.attributes else ""
|
|
||||||
|
|
||||||
return "<state {}={}{} @ {}>".format(
|
|
||||||
self.entity_id, self.state, attr,
|
|
||||||
date_util.datetime_to_local_str(self.last_changed))
|
|
||||||
|
|
||||||
|
|
||||||
class StateMachine(object):
|
|
||||||
""" Helper class that tracks the state of different entities. """
|
|
||||||
|
|
||||||
def __init__(self, bus):
|
|
||||||
self._states = {}
|
|
||||||
self._bus = bus
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
|
|
||||||
def entity_ids(self, domain_filter=None):
|
|
||||||
""" List of entity ids that are being tracked. """
|
|
||||||
if domain_filter is not None:
|
|
||||||
domain_filter = domain_filter.lower()
|
|
||||||
|
|
||||||
return [state.entity_id for key, state
|
|
||||||
in self._states.items()
|
|
||||||
if util.split_entity_id(key)[0] == domain_filter]
|
|
||||||
else:
|
|
||||||
return list(self._states.keys())
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
""" Returns a list of all states. """
|
|
||||||
return [state.copy() for state in self._states.values()]
|
|
||||||
|
|
||||||
def get(self, entity_id):
|
|
||||||
""" Returns the state of the specified entity. """
|
|
||||||
state = self._states.get(entity_id.lower())
|
|
||||||
|
|
||||||
# Make a copy so people won't mutate the state
|
|
||||||
return state.copy() if state else None
|
|
||||||
|
|
||||||
def get_since(self, point_in_time):
|
|
||||||
"""
|
|
||||||
Returns all states that have been changed since point_in_time.
|
|
||||||
"""
|
|
||||||
point_in_time = date_util.strip_microseconds(point_in_time)
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
return [state for state in self._states.values()
|
|
||||||
if state.last_updated >= point_in_time]
|
|
||||||
|
|
||||||
def is_state(self, entity_id, state):
|
|
||||||
""" Returns True if entity exists and is specified state. """
|
|
||||||
entity_id = entity_id.lower()
|
|
||||||
|
|
||||||
return (entity_id in self._states and
|
|
||||||
self._states[entity_id].state == state)
|
|
||||||
|
|
||||||
def remove(self, entity_id):
|
|
||||||
""" Removes an entity from the state machine.
|
|
||||||
|
|
||||||
Returns boolean to indicate if an entity was removed. """
|
|
||||||
entity_id = entity_id.lower()
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
return self._states.pop(entity_id, None) is not None
|
|
||||||
|
|
||||||
def set(self, entity_id, new_state, attributes=None):
|
|
||||||
""" Set the state of an entity, add entity if it does not exist.
|
|
||||||
|
|
||||||
Attributes is an optional dict to specify attributes of this state.
|
|
||||||
|
|
||||||
If you just update the attributes and not the state, last changed will
|
|
||||||
not be affected.
|
|
||||||
"""
|
|
||||||
entity_id = entity_id.lower()
|
|
||||||
new_state = str(new_state)
|
|
||||||
attributes = attributes or {}
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
old_state = self._states.get(entity_id)
|
|
||||||
|
|
||||||
is_existing = old_state is not None
|
|
||||||
same_state = is_existing and old_state.state == new_state
|
|
||||||
same_attr = is_existing and old_state.attributes == attributes
|
|
||||||
|
|
||||||
# If state did not exist or is different, set it
|
|
||||||
if not (same_state and same_attr):
|
|
||||||
last_changed = old_state.last_changed if same_state else None
|
|
||||||
|
|
||||||
state = State(entity_id, new_state, attributes, last_changed)
|
|
||||||
self._states[entity_id] = state
|
|
||||||
|
|
||||||
event_data = {'entity_id': entity_id, 'new_state': state}
|
|
||||||
|
|
||||||
if old_state:
|
|
||||||
event_data['old_state'] = old_state
|
|
||||||
|
|
||||||
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
|
||||||
|
|
||||||
def track_change(self, entity_ids, action, from_state=None, to_state=None):
|
|
||||||
"""
|
|
||||||
Track specific state changes.
|
|
||||||
entity_ids, from_state and to_state can be string or list.
|
|
||||||
Use list to match multiple.
|
|
||||||
|
|
||||||
Returns the listener that listens on the bus for EVENT_STATE_CHANGED.
|
|
||||||
Pass the return value into hass.bus.remove_listener to remove it.
|
|
||||||
"""
|
|
||||||
from_state = _process_match_param(from_state)
|
|
||||||
to_state = _process_match_param(to_state)
|
|
||||||
|
|
||||||
# Ensure it is a lowercase list with entity ids we want to match on
|
|
||||||
if isinstance(entity_ids, str):
|
|
||||||
entity_ids = (entity_ids.lower(),)
|
|
||||||
else:
|
|
||||||
entity_ids = tuple(entity_id.lower() for entity_id in entity_ids)
|
|
||||||
|
|
||||||
@ft.wraps(action)
|
|
||||||
def state_listener(event):
|
|
||||||
""" The listener that listens for specific state changes. """
|
|
||||||
if event.data['entity_id'] not in entity_ids:
|
|
||||||
return
|
|
||||||
|
|
||||||
if 'old_state' in event.data:
|
|
||||||
old_state = event.data['old_state'].state
|
|
||||||
else:
|
|
||||||
old_state = None
|
|
||||||
|
|
||||||
if _matcher(old_state, from_state) and \
|
|
||||||
_matcher(event.data['new_state'].state, to_state):
|
|
||||||
|
|
||||||
action(event.data['entity_id'],
|
|
||||||
event.data.get('old_state'),
|
|
||||||
event.data['new_state'])
|
|
||||||
|
|
||||||
self._bus.listen(EVENT_STATE_CHANGED, state_listener)
|
|
||||||
|
|
||||||
return state_listener
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class ServiceCall(object):
|
|
||||||
""" Represents a call to a service. """
|
|
||||||
|
|
||||||
__slots__ = ['domain', 'service', 'data']
|
|
||||||
|
|
||||||
def __init__(self, domain, service, data=None):
|
|
||||||
self.domain = domain
|
|
||||||
self.service = service
|
|
||||||
self.data = data or {}
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.data:
|
|
||||||
return "<ServiceCall {}.{}: {}>".format(
|
|
||||||
self.domain, self.service, util.repr_helper(self.data))
|
|
||||||
else:
|
|
||||||
return "<ServiceCall {}.{}>".format(self.domain, self.service)
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceRegistry(object):
|
|
||||||
""" Offers services over the eventbus. """
|
|
||||||
|
|
||||||
def __init__(self, bus, pool=None):
|
|
||||||
self._services = {}
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
self._pool = pool or create_worker_pool()
|
|
||||||
self._bus = bus
|
|
||||||
self._cur_id = 0
|
|
||||||
bus.listen(EVENT_CALL_SERVICE, self._event_to_service_call)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def services(self):
|
|
||||||
""" Dict with per domain a list of available services. """
|
|
||||||
with self._lock:
|
|
||||||
return {domain: list(self._services[domain].keys())
|
|
||||||
for domain in self._services}
|
|
||||||
|
|
||||||
def has_service(self, domain, service):
|
|
||||||
""" Returns True if specified service exists. """
|
|
||||||
return service in self._services.get(domain, [])
|
|
||||||
|
|
||||||
def register(self, domain, service, service_func):
|
|
||||||
""" Register a service. """
|
|
||||||
with self._lock:
|
|
||||||
if domain in self._services:
|
|
||||||
self._services[domain][service] = service_func
|
|
||||||
else:
|
|
||||||
self._services[domain] = {service: service_func}
|
|
||||||
|
|
||||||
self._bus.fire(
|
|
||||||
EVENT_SERVICE_REGISTERED,
|
|
||||||
{ATTR_DOMAIN: domain, ATTR_SERVICE: service})
|
|
||||||
|
|
||||||
def call(self, domain, service, service_data=None, blocking=False):
|
|
||||||
"""
|
|
||||||
Calls specified service.
|
|
||||||
Specify blocking=True to wait till service is executed.
|
|
||||||
Waits a maximum of SERVICE_CALL_LIMIT.
|
|
||||||
|
|
||||||
If blocking = True, will return boolean if service executed
|
|
||||||
succesfully within SERVICE_CALL_LIMIT.
|
|
||||||
|
|
||||||
This method will fire an event to call the service.
|
|
||||||
This event will be picked up by this ServiceRegistry and any
|
|
||||||
other ServiceRegistry that is listening on the EventBus.
|
|
||||||
|
|
||||||
Because the service is sent as an event you are not allowed to use
|
|
||||||
the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data.
|
|
||||||
"""
|
|
||||||
call_id = self._generate_unique_id()
|
|
||||||
event_data = service_data or {}
|
|
||||||
event_data[ATTR_DOMAIN] = domain
|
|
||||||
event_data[ATTR_SERVICE] = service
|
|
||||||
event_data[ATTR_SERVICE_CALL_ID] = call_id
|
|
||||||
|
|
||||||
if blocking:
|
|
||||||
executed_event = threading.Event()
|
|
||||||
|
|
||||||
def service_executed(call):
|
|
||||||
"""
|
|
||||||
Called when a service is executed.
|
|
||||||
Will set the event if matches our service call.
|
|
||||||
"""
|
|
||||||
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
|
|
||||||
executed_event.set()
|
|
||||||
|
|
||||||
self._bus.remove_listener(
|
|
||||||
EVENT_SERVICE_EXECUTED, service_executed)
|
|
||||||
|
|
||||||
self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed)
|
|
||||||
|
|
||||||
self._bus.fire(EVENT_CALL_SERVICE, event_data)
|
|
||||||
|
|
||||||
if blocking:
|
|
||||||
# wait will return False if event not set after our limit has
|
|
||||||
# passed. If not set, clean up the listener
|
|
||||||
if not executed_event.wait(SERVICE_CALL_LIMIT):
|
|
||||||
self._bus.remove_listener(
|
|
||||||
EVENT_SERVICE_EXECUTED, service_executed)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _event_to_service_call(self, event):
|
|
||||||
""" Calls a service from an event. """
|
|
||||||
service_data = dict(event.data)
|
|
||||||
domain = service_data.pop(ATTR_DOMAIN, None)
|
|
||||||
service = service_data.pop(ATTR_SERVICE, None)
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
if domain in self._services and service in self._services[domain]:
|
|
||||||
service_call = ServiceCall(domain, service, service_data)
|
|
||||||
|
|
||||||
# Add a job to the pool that calls _execute_service
|
|
||||||
self._pool.add_job(JobPriority.EVENT_SERVICE,
|
|
||||||
(self._execute_service,
|
|
||||||
(self._services[domain][service],
|
|
||||||
service_call)))
|
|
||||||
|
|
||||||
def _execute_service(self, service_and_call):
|
|
||||||
""" Executes a service and fires a SERVICE_EXECUTED event. """
|
|
||||||
service, call = service_and_call
|
|
||||||
|
|
||||||
service(call)
|
|
||||||
|
|
||||||
self._bus.fire(
|
|
||||||
EVENT_SERVICE_EXECUTED, {
|
|
||||||
ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]
|
|
||||||
})
|
|
||||||
|
|
||||||
def _generate_unique_id(self):
|
|
||||||
""" Generates a unique service call id. """
|
|
||||||
self._cur_id += 1
|
|
||||||
return "{}-{}".format(id(self), self._cur_id)
|
|
||||||
|
|
||||||
|
|
||||||
class Timer(threading.Thread):
|
|
||||||
""" Timer will sent out an event every TIMER_INTERVAL seconds. """
|
|
||||||
|
|
||||||
def __init__(self, hass, interval=None):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
|
|
||||||
self.daemon = True
|
|
||||||
self.hass = hass
|
|
||||||
self.interval = interval or TIMER_INTERVAL
|
|
||||||
self._stop_event = threading.Event()
|
|
||||||
|
|
||||||
# We want to be able to fire every time a minute starts (seconds=0).
|
|
||||||
# We want this so other modules can use that to make sure they fire
|
|
||||||
# every minute.
|
|
||||||
assert 60 % self.interval == 0, "60 % TIMER_INTERVAL should be 0!"
|
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
|
|
||||||
lambda event: self.start())
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
""" Start the timer. """
|
|
||||||
|
|
||||||
self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
|
||||||
lambda event: self._stop_event.set())
|
|
||||||
|
|
||||||
_LOGGER.info("Timer:starting")
|
|
||||||
|
|
||||||
last_fired_on_second = -1
|
|
||||||
|
|
||||||
calc_now = date_util.utcnow
|
|
||||||
interval = self.interval
|
|
||||||
|
|
||||||
while not self._stop_event.isSet():
|
|
||||||
now = calc_now()
|
|
||||||
|
|
||||||
# First check checks if we are not on a second matching the
|
|
||||||
# timer interval. Second check checks if we did not already fire
|
|
||||||
# this interval.
|
|
||||||
if now.second % interval or \
|
|
||||||
now.second == last_fired_on_second:
|
|
||||||
|
|
||||||
# Sleep till it is the next time that we have to fire an event.
|
|
||||||
# Aim for halfway through the second that fits TIMER_INTERVAL.
|
|
||||||
# If TIMER_INTERVAL is 10 fire at .5, 10.5, 20.5, etc seconds.
|
|
||||||
# This will yield the best results because time.sleep() is not
|
|
||||||
# 100% accurate because of non-realtime OS's
|
|
||||||
slp_seconds = interval - now.second % interval + \
|
|
||||||
.5 - now.microsecond/1000000.0
|
|
||||||
|
|
||||||
time.sleep(slp_seconds)
|
|
||||||
|
|
||||||
now = calc_now()
|
|
||||||
|
|
||||||
last_fired_on_second = now.second
|
|
||||||
|
|
||||||
# Event might have been set while sleeping
|
|
||||||
if not self._stop_event.isSet():
|
|
||||||
try:
|
|
||||||
self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
|
|
||||||
except HomeAssistantError:
|
|
||||||
# HA raises error if firing event after it has shut down
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
|
||||||
""" Configuration settings for Home Assistant. """
|
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
|
||||||
def __init__(self):
|
|
||||||
self.latitude = None
|
|
||||||
self.longitude = None
|
|
||||||
self.temperature_unit = None
|
|
||||||
self.location_name = None
|
|
||||||
self.time_zone = None
|
|
||||||
|
|
||||||
# List of loaded components
|
|
||||||
self.components = []
|
|
||||||
|
|
||||||
# Remote.API object pointing at local API
|
|
||||||
self.api = None
|
|
||||||
|
|
||||||
# Directory that holds the configuration
|
|
||||||
self.config_dir = os.path.join(os.getcwd(), 'config')
|
|
||||||
|
|
||||||
def path(self, *path):
|
|
||||||
""" Returns path to the file within the config dir. """
|
|
||||||
return os.path.join(self.config_dir, *path)
|
|
||||||
|
|
||||||
def temperature(self, value, unit):
|
|
||||||
""" Converts temperature to user preferred unit if set. """
|
|
||||||
if not (unit and self.temperature_unit and
|
|
||||||
unit != self.temperature_unit):
|
|
||||||
return value, unit
|
|
||||||
|
|
||||||
try:
|
|
||||||
if unit == TEMP_CELCIUS:
|
|
||||||
# Convert C to F
|
|
||||||
return round(float(value) * 1.8 + 32.0, 1), TEMP_FAHRENHEIT
|
|
||||||
|
|
||||||
# Convert F to C
|
|
||||||
return round((float(value)-32.0)/1.8, 1), TEMP_CELCIUS
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
# Could not convert value to float
|
|
||||||
return value, unit
|
|
||||||
|
|
||||||
def as_dict(self):
|
|
||||||
""" Converts config to a dictionary. """
|
|
||||||
time_zone = self.time_zone or date_util.UTC
|
|
||||||
|
|
||||||
return {
|
|
||||||
'latitude': self.latitude,
|
|
||||||
'longitude': self.longitude,
|
|
||||||
'temperature_unit': self.temperature_unit,
|
|
||||||
'location_name': self.location_name,
|
|
||||||
'time_zone': time_zone.zone,
|
|
||||||
'components': self.components,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantError(Exception):
|
|
||||||
""" General Home Assistant exception occured. """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidEntityFormatError(HomeAssistantError):
|
|
||||||
""" When an invalid formatted entity is encountered. """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoEntitySpecifiedError(HomeAssistantError):
|
|
||||||
""" When no entity is specified. """
|
|
||||||
pass
|
|
@ -4,120 +4,61 @@ from __future__ import print_function
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
DEPENDENCIES = ['requests>=2.0', 'pyyaml>=3.11', 'pytz>=2015.2']
|
|
||||||
IS_VIRTUAL = (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or
|
|
||||||
hasattr(sys, 'real_prefix'))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_python():
|
|
||||||
""" Validate we're running the right Python version. """
|
|
||||||
major, minor = sys.version_info[:2]
|
|
||||||
|
|
||||||
if major < 3 or (major == 3 and minor < 4):
|
|
||||||
print("Home Assistant requires atleast Python 3.4")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_pip():
|
|
||||||
""" Validate pip is installed so we can install packages on demand. """
|
|
||||||
if importlib.find_loader('pip') is None:
|
|
||||||
print("Your Python installation did not bundle 'pip'")
|
|
||||||
print("Home Assistant requires 'pip' to be installed.")
|
|
||||||
print("Please install pip: "
|
|
||||||
"https://pip.pypa.io/en/latest/installing.html")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
# Copy of homeassistant.util.package because we can't import yet
|
|
||||||
def install_package(package):
|
|
||||||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
|
||||||
Return boolean if install successfull."""
|
|
||||||
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
|
||||||
if not IS_VIRTUAL:
|
|
||||||
args.append('--user')
|
|
||||||
try:
|
|
||||||
return 0 == subprocess.call(args)
|
|
||||||
except subprocess.SubprocessError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def validate_dependencies():
|
|
||||||
""" Validate all dependencies that HA uses. """
|
|
||||||
ensure_pip()
|
|
||||||
|
|
||||||
print("Validating dependencies...")
|
|
||||||
import_fail = False
|
|
||||||
|
|
||||||
for requirement in DEPENDENCIES:
|
|
||||||
if not install_package(requirement):
|
|
||||||
import_fail = True
|
|
||||||
print('Fatal Error: Unable to install dependency', requirement)
|
|
||||||
|
|
||||||
if import_fail:
|
|
||||||
print(("Install dependencies by running: "
|
|
||||||
"python3 -m pip install -r requirements.txt"))
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_path_and_load_bootstrap():
|
|
||||||
""" Ensure sys load path is correct and load Home Assistant bootstrap. """
|
|
||||||
try:
|
|
||||||
from homeassistant import bootstrap
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# This is to add support to load Home Assistant using
|
|
||||||
# `python3 homeassistant` instead of `python3 -m homeassistant`
|
|
||||||
|
|
||||||
# Insert the parent directory of this file into the module search path
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
|
import homeassistant.config as config_util
|
||||||
return bootstrap
|
from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START
|
||||||
|
|
||||||
|
|
||||||
def validate_git_submodules():
|
|
||||||
""" Validate the git submodules are cloned. """
|
|
||||||
try:
|
|
||||||
# pylint: disable=no-name-in-module, unused-variable
|
|
||||||
from homeassistant.external.noop import WORKING # noqa
|
|
||||||
except ImportError:
|
|
||||||
print("Repository submodules have not been initialized")
|
|
||||||
print("Please run: git submodule update --init --recursive")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_config_path(config_dir):
|
def ensure_config_path(config_dir):
|
||||||
""" Gets the path to the configuration file.
|
""" Validates configuration directory. """
|
||||||
Creates one if it not exists. """
|
|
||||||
|
lib_dir = os.path.join(config_dir, 'lib')
|
||||||
|
|
||||||
# Test if configuration directory exists
|
# Test if configuration directory exists
|
||||||
if not os.path.isdir(config_dir):
|
if not os.path.isdir(config_dir):
|
||||||
print(('Fatal Error: Unable to find specified configuration '
|
if config_dir != config_util.get_default_config_dir():
|
||||||
|
print(('Fatal Error: Specified configuration directory does '
|
||||||
|
'not exist {} ').format(config_dir))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir(config_dir)
|
||||||
|
except OSError:
|
||||||
|
print(('Fatal Error: Unable to create default configuration '
|
||||||
'directory {} ').format(config_dir))
|
'directory {} ').format(config_dir))
|
||||||
sys.exit()
|
sys.exit(1)
|
||||||
|
|
||||||
import homeassistant.config as config_util
|
# Test if library directory exists
|
||||||
|
if not os.path.isdir(lib_dir):
|
||||||
|
try:
|
||||||
|
os.mkdir(lib_dir)
|
||||||
|
except OSError:
|
||||||
|
print(('Fatal Error: Unable to create library '
|
||||||
|
'directory {} ').format(lib_dir))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_config_file(config_dir):
|
||||||
|
""" Ensure configuration file exists. """
|
||||||
config_path = config_util.ensure_config_exists(config_dir)
|
config_path = config_util.ensure_config_exists(config_dir)
|
||||||
|
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
print('Error getting configuration path')
|
print('Error getting configuration path')
|
||||||
sys.exit()
|
sys.exit(1)
|
||||||
|
|
||||||
return config_path
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
def get_arguments():
|
def get_arguments():
|
||||||
""" Get parsed passed in arguments. """
|
""" Get parsed passed in arguments. """
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Home Assistant: Observe, Control, Automate.")
|
||||||
|
parser.add_argument('--version', action='version', version=__version__)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-c', '--config',
|
'-c', '--config',
|
||||||
metavar='path_to_config_dir',
|
metavar='path_to_config_dir',
|
||||||
default="config",
|
default=config_util.get_default_config_dir(),
|
||||||
help="Directory that contains the Home Assistant configuration")
|
help="Directory that contains the Home Assistant configuration")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--demo-mode',
|
'--demo-mode',
|
||||||
@ -133,34 +74,21 @@ def get_arguments():
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" Starts Home Assistant. """
|
""" Starts Home Assistant. """
|
||||||
validate_python()
|
|
||||||
validate_dependencies()
|
|
||||||
|
|
||||||
# Windows needs this to pick up new modules
|
|
||||||
importlib.invalidate_caches()
|
|
||||||
|
|
||||||
bootstrap = ensure_path_and_load_bootstrap()
|
|
||||||
|
|
||||||
validate_git_submodules()
|
|
||||||
|
|
||||||
args = get_arguments()
|
args = get_arguments()
|
||||||
|
|
||||||
config_dir = os.path.join(os.getcwd(), args.config)
|
config_dir = os.path.join(os.getcwd(), args.config)
|
||||||
config_path = ensure_config_path(config_dir)
|
ensure_config_path(config_dir)
|
||||||
|
|
||||||
if args.demo_mode:
|
if args.demo_mode:
|
||||||
from homeassistant.components import frontend, demo
|
|
||||||
|
|
||||||
hass = bootstrap.from_config_dict({
|
hass = bootstrap.from_config_dict({
|
||||||
frontend.DOMAIN: {},
|
'frontend': {},
|
||||||
demo.DOMAIN: {}
|
'demo': {}
|
||||||
})
|
}, config_dir=config_dir)
|
||||||
else:
|
else:
|
||||||
hass = bootstrap.from_config_file(config_path)
|
config_file = ensure_config_file(config_dir)
|
||||||
|
hass = bootstrap.from_config_file(config_file)
|
||||||
|
|
||||||
if args.open_ui:
|
if args.open_ui:
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
|
||||||
|
|
||||||
def open_browser(event):
|
def open_browser(event):
|
||||||
""" Open the webinterface in a browser. """
|
""" Open the webinterface in a browser. """
|
||||||
if hass.config.api is not None:
|
if hass.config.api is not None:
|
||||||
|
@ -10,10 +10,11 @@ start by calling homeassistant.start_home_assistant(bus)
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import homeassistant
|
import homeassistant.core as core
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
import homeassistant.util.package as pkg_util
|
import homeassistant.util.package as pkg_util
|
||||||
import homeassistant.util.location as loc_util
|
import homeassistant.util.location as loc_util
|
||||||
@ -61,14 +62,17 @@ def setup_component(hass, domain, config=None):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _handle_requirements(component, name):
|
def _handle_requirements(hass, component, name):
|
||||||
""" Installs requirements for component. """
|
""" Installs requirements for component. """
|
||||||
if hasattr(component, 'REQUIREMENTS'):
|
if not hasattr(component, 'REQUIREMENTS'):
|
||||||
|
return True
|
||||||
|
|
||||||
for req in component.REQUIREMENTS:
|
for req in component.REQUIREMENTS:
|
||||||
if not pkg_util.install_package(req):
|
if not pkg_util.install_package(req, target=hass.config.path('lib')):
|
||||||
_LOGGER.error('Not initializing %s because could not install '
|
_LOGGER.error('Not initializing %s because could not install '
|
||||||
'dependency %s', name, req)
|
'dependency %s', name, req)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -83,14 +87,19 @@ def _setup_component(hass, domain, config):
|
|||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'Not initializing %s because not all dependencies loaded: %s',
|
'Not initializing %s because not all dependencies loaded: %s',
|
||||||
domain, ", ".join(missing_deps))
|
domain, ", ".join(missing_deps))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not _handle_requirements(component, domain):
|
if not _handle_requirements(hass, component, domain):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if component.setup(hass, config):
|
if not component.setup(hass, config):
|
||||||
|
_LOGGER.error('component %s failed to initialize', domain)
|
||||||
|
return False
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception('Error during setup of component %s', domain)
|
||||||
|
return False
|
||||||
|
|
||||||
hass.config.components.append(component.DOMAIN)
|
hass.config.components.append(component.DOMAIN)
|
||||||
|
|
||||||
# Assumption: if a component does not depend on groups
|
# Assumption: if a component does not depend on groups
|
||||||
@ -103,14 +112,6 @@ def _setup_component(hass, domain, config):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
|
||||||
_LOGGER.error('component %s failed to initialize', domain)
|
|
||||||
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
_LOGGER.exception('Error during setup of component %s', domain)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_setup_platform(hass, config, domain, platform_name):
|
def prepare_setup_platform(hass, config, domain, platform_name):
|
||||||
""" Loads a platform and makes sure dependencies are setup. """
|
""" Loads a platform and makes sure dependencies are setup. """
|
||||||
@ -138,24 +139,34 @@ def prepare_setup_platform(hass, config, domain, platform_name):
|
|||||||
component)
|
component)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not _handle_requirements(platform, platform_path):
|
if not _handle_requirements(hass, platform, platform_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return platform
|
return platform
|
||||||
|
|
||||||
|
|
||||||
|
def mount_local_lib_path(config_dir):
|
||||||
|
""" Add local library to Python Path """
|
||||||
|
sys.path.insert(0, os.path.join(config_dir, 'lib'))
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-statements
|
# pylint: disable=too-many-branches, too-many-statements
|
||||||
def from_config_dict(config, hass=None):
|
def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
|
||||||
"""
|
"""
|
||||||
Tries to configure Home Assistant from a config dict.
|
Tries to configure Home Assistant from a config dict.
|
||||||
|
|
||||||
Dynamically loads required components and its dependencies.
|
Dynamically loads required components and its dependencies.
|
||||||
"""
|
"""
|
||||||
if hass is None:
|
if hass is None:
|
||||||
hass = homeassistant.HomeAssistant()
|
hass = core.HomeAssistant()
|
||||||
|
if config_dir is not None:
|
||||||
|
config_dir = os.path.abspath(config_dir)
|
||||||
|
hass.config.config_dir = config_dir
|
||||||
|
mount_local_lib_path(config_dir)
|
||||||
|
|
||||||
process_ha_core_config(hass, config.get(homeassistant.DOMAIN, {}))
|
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
|
||||||
|
|
||||||
|
if enable_log:
|
||||||
enable_logging(hass)
|
enable_logging(hass)
|
||||||
|
|
||||||
_ensure_loader_prepared(hass)
|
_ensure_loader_prepared(hass)
|
||||||
@ -168,7 +179,7 @@ def from_config_dict(config, hass=None):
|
|||||||
|
|
||||||
# Filter out the repeating and common config section [homeassistant]
|
# Filter out the repeating and common config section [homeassistant]
|
||||||
components = (key for key in config.keys()
|
components = (key for key in config.keys()
|
||||||
if ' ' not in key and key != homeassistant.DOMAIN)
|
if ' ' not in key and key != core.DOMAIN)
|
||||||
|
|
||||||
if not core_components.setup(hass, config):
|
if not core_components.setup(hass, config):
|
||||||
_LOGGER.error('Home Assistant core failed to initialize. '
|
_LOGGER.error('Home Assistant core failed to initialize. '
|
||||||
@ -192,14 +203,18 @@ def from_config_file(config_path, hass=None):
|
|||||||
instantiates a new Home Assistant object if 'hass' is not given.
|
instantiates a new Home Assistant object if 'hass' is not given.
|
||||||
"""
|
"""
|
||||||
if hass is None:
|
if hass is None:
|
||||||
hass = homeassistant.HomeAssistant()
|
hass = core.HomeAssistant()
|
||||||
|
|
||||||
# Set config dir to directory holding config file
|
# Set config dir to directory holding config file
|
||||||
hass.config.config_dir = os.path.abspath(os.path.dirname(config_path))
|
config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||||
|
hass.config.config_dir = config_dir
|
||||||
|
mount_local_lib_path(config_dir)
|
||||||
|
|
||||||
|
enable_logging(hass)
|
||||||
|
|
||||||
config_dict = config_util.load_config_file(config_path)
|
config_dict = config_util.load_config_file(config_path)
|
||||||
|
|
||||||
return from_config_dict(config_dict, hass)
|
return from_config_dict(config_dict, hass, enable_log=False)
|
||||||
|
|
||||||
|
|
||||||
def enable_logging(hass):
|
def enable_logging(hass):
|
||||||
@ -222,7 +237,8 @@ def enable_logging(hass):
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
_LOGGER.warn("Colorlog package not found, console coloring disabled")
|
_LOGGER.warning(
|
||||||
|
"Colorlog package not found, console coloring disabled")
|
||||||
|
|
||||||
# Log errors to a file if we have write access to file or config dir
|
# Log errors to a file if we have write access to file or config dir
|
||||||
err_log_path = hass.config.path('home-assistant.log')
|
err_log_path = hass.config.path('home-assistant.log')
|
||||||
|
@ -17,7 +17,7 @@ Each component should publish services only under its own domain.
|
|||||||
import itertools as it
|
import itertools as it
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers import extract_entity_ids
|
from homeassistant.helpers import extract_entity_ids
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
|
@ -9,7 +9,7 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.helpers.state import TrackStates
|
from homeassistant.helpers.state import TrackStates
|
||||||
import homeassistant.remote as rem
|
import homeassistant.remote as rem
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -6,7 +6,7 @@ Allows to setup simple automation rules via the config file.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.bootstrap import prepare_setup_platform
|
||||||
from homeassistant.helpers import config_per_platform
|
from homeassistant.helpers import config_per_platform
|
||||||
from homeassistant.util import split_entity_id
|
from homeassistant.util import split_entity_id
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
@ -25,9 +25,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Sets up automation. """
|
""" Sets up automation. """
|
||||||
|
success = False
|
||||||
|
|
||||||
for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER):
|
for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER):
|
||||||
platform = get_component('automation.{}'.format(p_type))
|
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
|
||||||
|
|
||||||
if platform is None:
|
if platform is None:
|
||||||
_LOGGER.error("Unknown automation platform specified: %s", p_type)
|
_LOGGER.error("Unknown automation platform specified: %s", p_type)
|
||||||
@ -36,11 +37,12 @@ def setup(hass, config):
|
|||||||
if platform.register(hass, p_config, _get_action(hass, p_config)):
|
if platform.register(hass, p_config, _get_action(hass, p_config)):
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
|
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
|
||||||
|
success = True
|
||||||
else:
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Error setting up rule %s", p_config.get(CONF_ALIAS, ""))
|
"Error setting up rule %s", p_config.get(CONF_ALIAS, ""))
|
||||||
|
|
||||||
return True
|
return success
|
||||||
|
|
||||||
|
|
||||||
def _get_action(hass, config):
|
def _get_action(hass, config):
|
||||||
@ -56,13 +58,16 @@ def _get_action(hass, config):
|
|||||||
service_data = config.get(CONF_SERVICE_DATA, {})
|
service_data = config.get(CONF_SERVICE_DATA, {})
|
||||||
|
|
||||||
if not isinstance(service_data, dict):
|
if not isinstance(service_data, dict):
|
||||||
_LOGGER.error(
|
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
|
||||||
"%s should be a serialized JSON object", CONF_SERVICE_DATA)
|
|
||||||
service_data = {}
|
service_data = {}
|
||||||
|
|
||||||
if CONF_SERVICE_ENTITY_ID in config:
|
if CONF_SERVICE_ENTITY_ID in config:
|
||||||
|
try:
|
||||||
service_data[ATTR_ENTITY_ID] = \
|
service_data[ATTR_ENTITY_ID] = \
|
||||||
config[CONF_SERVICE_ENTITY_ID].split(",")
|
config[CONF_SERVICE_ENTITY_ID].split(",")
|
||||||
|
except AttributeError:
|
||||||
|
service_data[ATTR_ENTITY_ID] = \
|
||||||
|
config[CONF_SERVICE_ENTITY_ID]
|
||||||
|
|
||||||
hass.services.call(domain, service, service_data)
|
hass.services.call(domain, service, service_data)
|
||||||
|
|
||||||
|
34
homeassistant/components/automation/mqtt.py
Normal file
34
homeassistant/components/automation/mqtt.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.automation.mqtt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Offers MQTT listening automation rules.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.components.mqtt as mqtt
|
||||||
|
|
||||||
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
CONF_TOPIC = 'mqtt_topic'
|
||||||
|
CONF_PAYLOAD = 'mqtt_payload'
|
||||||
|
|
||||||
|
|
||||||
|
def register(hass, config, action):
|
||||||
|
""" Listen for state changes based on `config`. """
|
||||||
|
topic = config.get(CONF_TOPIC)
|
||||||
|
payload = config.get(CONF_PAYLOAD)
|
||||||
|
|
||||||
|
if topic is None:
|
||||||
|
logging.getLogger(__name__).error(
|
||||||
|
"Missing configuration key %s", CONF_TOPIC)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def mqtt_automation_listener(msg_topic, msg_payload, qos):
|
||||||
|
""" Listens for MQTT messages. """
|
||||||
|
if payload is None or payload == msg_payload:
|
||||||
|
action()
|
||||||
|
|
||||||
|
mqtt.subscribe(hass, topic, mqtt_automation_listener)
|
||||||
|
|
||||||
|
return True
|
@ -6,6 +6,7 @@ Offers state listening automation rules.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.const import MATCH_ALL
|
from homeassistant.const import MATCH_ALL
|
||||||
|
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ def register(hass, config, action):
|
|||||||
""" Listens for state changes and calls action. """
|
""" Listens for state changes and calls action. """
|
||||||
action()
|
action()
|
||||||
|
|
||||||
hass.states.track_change(
|
track_state_change(
|
||||||
entity_id, state_automation_listener, from_state, to_state)
|
hass, entity_id, state_automation_listener, from_state, to_state)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -5,6 +5,7 @@ homeassistant.components.automation.time
|
|||||||
Offers time listening automation rules.
|
Offers time listening automation rules.
|
||||||
"""
|
"""
|
||||||
from homeassistant.util import convert
|
from homeassistant.util import convert
|
||||||
|
from homeassistant.helpers.event import track_time_change
|
||||||
|
|
||||||
CONF_HOURS = "time_hours"
|
CONF_HOURS = "time_hours"
|
||||||
CONF_MINUTES = "time_minutes"
|
CONF_MINUTES = "time_minutes"
|
||||||
@ -21,8 +22,7 @@ def register(hass, config, action):
|
|||||||
""" Listens for time changes and calls action. """
|
""" Listens for time changes and calls action. """
|
||||||
action()
|
action()
|
||||||
|
|
||||||
hass.track_time_change(
|
track_time_change(hass, time_automation_listener,
|
||||||
time_automation_listener,
|
|
||||||
hour=hours, minute=minutes, second=seconds)
|
hour=hours, minute=minutes, second=seconds)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -8,7 +8,7 @@ This is more a proof of concept.
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import homeassistant
|
from homeassistant import core
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
||||||
|
|
||||||
@ -52,14 +52,12 @@ def setup(hass, config):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if command == 'on':
|
if command == 'on':
|
||||||
hass.services.call(
|
hass.services.call(core.DOMAIN, SERVICE_TURN_ON, {
|
||||||
homeassistant.DOMAIN, SERVICE_TURN_ON, {
|
|
||||||
ATTR_ENTITY_ID: entity_ids,
|
ATTR_ENTITY_ID: entity_ids,
|
||||||
}, blocking=True)
|
}, blocking=True)
|
||||||
|
|
||||||
elif command == 'off':
|
elif command == 'off':
|
||||||
hass.services.call(
|
hass.services.call(core.DOMAIN, SERVICE_TURN_OFF, {
|
||||||
homeassistant.DOMAIN, SERVICE_TURN_OFF, {
|
|
||||||
ATTR_ENTITY_ID: entity_ids,
|
ATTR_ENTITY_ID: entity_ids,
|
||||||
}, blocking=True)
|
}, blocking=True)
|
||||||
|
|
||||||
|
@ -6,15 +6,15 @@ Sets up a demo environment that mimics interaction with devices.
|
|||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_PLATFORM, ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID)
|
CONF_PLATFORM, ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
|
||||||
|
|
||||||
DOMAIN = "demo"
|
DOMAIN = "demo"
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = ['introduction', 'conversation']
|
||||||
|
|
||||||
COMPONENTS_WITH_DEMO_PLATFORM = [
|
COMPONENTS_WITH_DEMO_PLATFORM = [
|
||||||
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
|
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
|
||||||
@ -48,8 +48,11 @@ def setup(hass, config):
|
|||||||
# Setup room groups
|
# Setup room groups
|
||||||
lights = hass.states.entity_ids('light')
|
lights = hass.states.entity_ids('light')
|
||||||
switches = hass.states.entity_ids('switch')
|
switches = hass.states.entity_ids('switch')
|
||||||
group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0]])
|
media_players = sorted(hass.states.entity_ids('media_player'))
|
||||||
group.setup_group(hass, 'bedroom', [lights[2], switches[1]])
|
group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0],
|
||||||
|
media_players[1]])
|
||||||
|
group.setup_group(hass, 'bedroom', [lights[2], switches[1],
|
||||||
|
media_players[0]])
|
||||||
|
|
||||||
# Setup IP Camera
|
# Setup IP Camera
|
||||||
bootstrap.setup_component(
|
bootstrap.setup_component(
|
||||||
@ -102,10 +105,10 @@ def setup(hass, config):
|
|||||||
# Setup fake device tracker
|
# Setup fake device tracker
|
||||||
hass.states.set("device_tracker.paulus", "home",
|
hass.states.set("device_tracker.paulus", "home",
|
||||||
{ATTR_ENTITY_PICTURE:
|
{ATTR_ENTITY_PICTURE:
|
||||||
"http://graph.facebook.com/297400035/picture"})
|
"http://graph.facebook.com/297400035/picture",
|
||||||
|
ATTR_FRIENDLY_NAME: 'Paulus'})
|
||||||
hass.states.set("device_tracker.anne_therese", "not_home",
|
hass.states.set("device_tracker.anne_therese", "not_home",
|
||||||
{ATTR_ENTITY_PICTURE:
|
{ATTR_FRIENDLY_NAME: 'Anne Therese'})
|
||||||
"http://graph.facebook.com/621994601/picture"})
|
|
||||||
|
|
||||||
hass.states.set("group.all_devices", "home",
|
hass.states.set("group.all_devices", "home",
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ the state of the sun and devices.
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.helpers.event import track_point_in_time, track_state_change
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
|
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
|
||||||
from . import light, sun, device_tracker, group
|
from . import light, sun, device_tracker, group
|
||||||
@ -91,13 +92,13 @@ def setup(hass, config):
|
|||||||
|
|
||||||
if start_point:
|
if start_point:
|
||||||
for index, light_id in enumerate(light_ids):
|
for index, light_id in enumerate(light_ids):
|
||||||
hass.track_point_in_time(turn_on(light_id),
|
track_point_in_time(
|
||||||
(start_point +
|
hass, turn_on(light_id),
|
||||||
index * LIGHT_TRANSITION_TIME))
|
(start_point + index * LIGHT_TRANSITION_TIME))
|
||||||
|
|
||||||
# Track every time sun rises so we can schedule a time-based
|
# Track every time sun rises so we can schedule a time-based
|
||||||
# pre-sun set event
|
# pre-sun set event
|
||||||
hass.states.track_change(sun.ENTITY_ID, schedule_light_on_sun_rise,
|
track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise,
|
||||||
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
|
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
|
||||||
|
|
||||||
# If the sun is already above horizon
|
# If the sun is already above horizon
|
||||||
@ -157,13 +158,13 @@ def setup(hass, config):
|
|||||||
light.turn_off(hass, light_ids)
|
light.turn_off(hass, light_ids)
|
||||||
|
|
||||||
# Track home coming of each device
|
# Track home coming of each device
|
||||||
hass.states.track_change(
|
track_state_change(
|
||||||
device_entity_ids, check_light_on_dev_state_change,
|
hass, device_entity_ids, check_light_on_dev_state_change,
|
||||||
STATE_NOT_HOME, STATE_HOME)
|
STATE_NOT_HOME, STATE_HOME)
|
||||||
|
|
||||||
# Track when all devices are gone to shut down lights
|
# Track when all devices are gone to shut down lights
|
||||||
hass.states.track_change(
|
track_state_change(
|
||||||
device_group, check_light_on_dev_state_change,
|
hass, device_group, check_light_on_dev_state_change,
|
||||||
STATE_HOME, STATE_NOT_HOME)
|
STATE_HOME, STATE_NOT_HOME)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -12,9 +12,11 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.helpers.entity import _OVERWRITE
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from homeassistant.helpers.event import track_utc_time_change
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
|
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
|
||||||
CONF_PLATFORM, DEVICE_DEFAULT_NAME)
|
CONF_PLATFORM, DEVICE_DEFAULT_NAME)
|
||||||
@ -65,14 +67,15 @@ def setup(hass, config):
|
|||||||
'device_tracker.{}'.format(tracker_type))
|
'device_tracker.{}'.format(tracker_type))
|
||||||
|
|
||||||
if tracker_implementation is None:
|
if tracker_implementation is None:
|
||||||
_LOGGER.error("Unknown device_tracker type specified.")
|
_LOGGER.error("Unknown device_tracker type specified: %s.",
|
||||||
|
tracker_type)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
device_scanner = tracker_implementation.get_scanner(hass, config)
|
device_scanner = tracker_implementation.get_scanner(hass, config)
|
||||||
|
|
||||||
if device_scanner is None:
|
if device_scanner is None:
|
||||||
_LOGGER.error("Failed to initialize device scanner for %s",
|
_LOGGER.error("Failed to initialize device scanner: %s",
|
||||||
tracker_type)
|
tracker_type)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -134,7 +137,7 @@ class DeviceTracker(object):
|
|||||||
seconds = range(0, 60, seconds)
|
seconds = range(0, 60, seconds)
|
||||||
|
|
||||||
_LOGGER.info("Device tracker interval second=%s", seconds)
|
_LOGGER.info("Device tracker interval second=%s", seconds)
|
||||||
hass.track_utc_time_change(update_device_state, second=seconds)
|
track_utc_time_change(hass, update_device_state, second=seconds)
|
||||||
|
|
||||||
hass.services.register(DOMAIN,
|
hass.services.register(DOMAIN,
|
||||||
SERVICE_DEVICE_TRACKER_RELOAD,
|
SERVICE_DEVICE_TRACKER_RELOAD,
|
||||||
@ -160,9 +163,12 @@ class DeviceTracker(object):
|
|||||||
|
|
||||||
state = STATE_HOME if is_home else STATE_NOT_HOME
|
state = STATE_HOME if is_home else STATE_NOT_HOME
|
||||||
|
|
||||||
|
# overwrite properties that have been set in the config file
|
||||||
|
attr = dict(dev_info['state_attr'])
|
||||||
|
attr.update(_OVERWRITE.get(dev_info['entity_id'], {}))
|
||||||
|
|
||||||
self.hass.states.set(
|
self.hass.states.set(
|
||||||
dev_info['entity_id'], state,
|
dev_info['entity_id'], state, attr)
|
||||||
dev_info['state_attr'])
|
|
||||||
|
|
||||||
def update_devices(self, now):
|
def update_devices(self, now):
|
||||||
""" Update device states based on the found devices. """
|
""" Update device states based on the found devices. """
|
||||||
|
149
homeassistant/components/device_tracker/actiontec.py
Normal file
149
homeassistant/components/device_tracker/actiontec.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.device_tracker.actiontec
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Device tracker platform that supports scanning an Actiontec MI424WR
|
||||||
|
(Verizon FIOS) router for device presence.
|
||||||
|
|
||||||
|
This device tracker needs telnet to be enabled on the router.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Actiontec tracker you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: actiontec
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_LEASES_REGEX = re.compile(
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})' +
|
||||||
|
r'\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))')
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
""" Validates config and returns a DD-WRT scanner. """
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
scanner = ActiontecDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
|
|
||||||
|
class ActiontecDeviceScanner(object):
|
||||||
|
""" This class queries a an actiontec router
|
||||||
|
for connected devices. Adapted from DD-WRT scanner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.host = config[CONF_HOST]
|
||||||
|
self.username = config[CONF_USERNAME]
|
||||||
|
self.password = config[CONF_PASSWORD]
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
self.last_results = {}
|
||||||
|
|
||||||
|
# Test the router is accessible
|
||||||
|
data = self.get_actiontec_data()
|
||||||
|
self.success_init = data is not None
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
return [client['mac'] for client in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" Returns the name of the given device or None if we don't know. """
|
||||||
|
if not self.last_results:
|
||||||
|
return None
|
||||||
|
for client in self.last_results:
|
||||||
|
if client['mac'] == device:
|
||||||
|
return client['ip']
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def _update_info(self):
|
||||||
|
""" Ensures the information from the Actiontec MI424WR router is up
|
||||||
|
to date. Returns boolean if scanning successful. """
|
||||||
|
if not self.success_init:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
# _LOGGER.info("Checking ARP")
|
||||||
|
data = self.get_actiontec_data()
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
active_clients = [client for client in data.values()]
|
||||||
|
self.last_results = active_clients
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_actiontec_data(self):
|
||||||
|
""" Retrieve data from Actiontec MI424WR and return parsed result. """
|
||||||
|
try:
|
||||||
|
telnet = telnetlib.Telnet(self.host)
|
||||||
|
telnet.read_until(b'Username: ')
|
||||||
|
telnet.write((self.username + '\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'Password: ')
|
||||||
|
telnet.write((self.password + '\n').encode('ascii'))
|
||||||
|
prompt = telnet.read_until(
|
||||||
|
b'Wireless Broadband Router> ').split(b'\n')[-1]
|
||||||
|
telnet.write('firewall mac_cache_dump\n'.encode('ascii'))
|
||||||
|
telnet.write('\n'.encode('ascii'))
|
||||||
|
telnet.read_until(prompt)
|
||||||
|
leases_result = telnet.read_until(prompt).split(b'\n')[1:-1]
|
||||||
|
telnet.write('exit\n'.encode('ascii'))
|
||||||
|
except EOFError:
|
||||||
|
_LOGGER.exception("Unexpected response from router")
|
||||||
|
return
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
_LOGGER.exception("Connection refused by router," +
|
||||||
|
" is telnet enabled?")
|
||||||
|
return None
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for lease in leases_result:
|
||||||
|
match = _LEASES_REGEX.search(lease.decode('utf-8'))
|
||||||
|
if match is not None:
|
||||||
|
devices[match.group('ip')] = {
|
||||||
|
'ip': match.group('ip'),
|
||||||
|
'mac': match.group('mac').upper()
|
||||||
|
}
|
||||||
|
return devices
|
167
homeassistant/components/device_tracker/asuswrt.py
Normal file
167
homeassistant/components/device_tracker/asuswrt.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.device_tracker.asuswrt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Device tracker platform that supports scanning a ASUSWRT router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
This device tracker needs telnet to be enabled on the router.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the ASUSWRT tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: asuswrt
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_LEASES_REGEX = re.compile(
|
||||||
|
r'\w+\s' +
|
||||||
|
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' +
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
|
||||||
|
r'(?P<host>([^\s]+))')
|
||||||
|
|
||||||
|
_IP_NEIGH_REGEX = re.compile(
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
|
||||||
|
r'\w+\s' +
|
||||||
|
r'\w+\s' +
|
||||||
|
r'(\w+\s(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s' +
|
||||||
|
r'(?P<status>(\w+))')
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
""" Validates config and returns a DD-WRT scanner. """
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
scanner = AsusWrtDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
|
|
||||||
|
class AsusWrtDeviceScanner(object):
|
||||||
|
""" This class queries a router running ASUSWRT firmware
|
||||||
|
for connected devices. Adapted from DD-WRT scanner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.host = config[CONF_HOST]
|
||||||
|
self.username = config[CONF_USERNAME]
|
||||||
|
self.password = config[CONF_PASSWORD]
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
self.last_results = {}
|
||||||
|
|
||||||
|
# Test the router is accessible
|
||||||
|
data = self.get_asuswrt_data()
|
||||||
|
self.success_init = data is not None
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
return [client['mac'] for client in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" Returns the name of the given device or None if we don't know. """
|
||||||
|
if not self.last_results:
|
||||||
|
return None
|
||||||
|
for client in self.last_results:
|
||||||
|
if client['mac'] == device:
|
||||||
|
return client['host']
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def _update_info(self):
|
||||||
|
""" Ensures the information from the ASUSWRT router is up to date.
|
||||||
|
Returns boolean if scanning successful. """
|
||||||
|
if not self.success_init:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
_LOGGER.info("Checking ARP")
|
||||||
|
data = self.get_asuswrt_data()
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
active_clients = [client for client in data.values() if
|
||||||
|
client['status'] == 'REACHABLE' or
|
||||||
|
client['status'] == 'DELAY' or
|
||||||
|
client['status'] == 'STALE']
|
||||||
|
self.last_results = active_clients
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_asuswrt_data(self):
|
||||||
|
""" Retrieve data from ASUSWRT and return parsed result. """
|
||||||
|
try:
|
||||||
|
telnet = telnetlib.Telnet(self.host)
|
||||||
|
telnet.read_until(b'login: ')
|
||||||
|
telnet.write((self.username + '\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'Password: ')
|
||||||
|
telnet.write((self.password + '\n').encode('ascii'))
|
||||||
|
prompt_string = telnet.read_until(b'#').split(b'\n')[-1]
|
||||||
|
telnet.write('ip neigh\n'.encode('ascii'))
|
||||||
|
neighbors = telnet.read_until(prompt_string).split(b'\n')[1:-1]
|
||||||
|
telnet.write('cat /var/lib/misc/dnsmasq.leases\n'.encode('ascii'))
|
||||||
|
leases_result = telnet.read_until(prompt_string).split(b'\n')[1:-1]
|
||||||
|
telnet.write('exit\n'.encode('ascii'))
|
||||||
|
except EOFError:
|
||||||
|
_LOGGER.exception("Unexpected response from router")
|
||||||
|
return
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
_LOGGER.exception("Connection refused by router," +
|
||||||
|
" is telnet enabled?")
|
||||||
|
return
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for lease in leases_result:
|
||||||
|
match = _LEASES_REGEX.search(lease.decode('utf-8'))
|
||||||
|
devices[match.group('ip')] = {
|
||||||
|
'ip': match.group('ip'),
|
||||||
|
'mac': match.group('mac').upper(),
|
||||||
|
'host': match.group('host'),
|
||||||
|
'status': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
for neighbor in neighbors:
|
||||||
|
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
|
||||||
|
if match.group('ip') in devices:
|
||||||
|
devices[match.group('ip')]['status'] = match.group('status')
|
||||||
|
return devices
|
@ -35,7 +35,6 @@ from datetime import timedelta
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
from homeassistant.helpers import validate_config
|
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
from homeassistant.components.device_tracker import DOMAIN
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
@ -43,20 +42,21 @@ from homeassistant.components.device_tracker import DOMAIN
|
|||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['pynetgear>=0.1']
|
REQUIREMENTS = ['pynetgear==0.3']
|
||||||
|
|
||||||
|
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
""" Validates config and returns a Netgear scanner. """
|
""" Validates config and returns a Netgear scanner. """
|
||||||
if not validate_config(config,
|
info = config[DOMAIN]
|
||||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
host = info.get(CONF_HOST)
|
||||||
_LOGGER):
|
username = info.get(CONF_USERNAME)
|
||||||
|
password = info.get(CONF_PASSWORD)
|
||||||
|
|
||||||
|
if password is not None and host is None:
|
||||||
|
_LOGGER.warning('Found username or password but no host')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
info = config[DOMAIN]
|
scanner = NetgearDeviceScanner(host, username, password)
|
||||||
|
|
||||||
scanner = NetgearDeviceScanner(
|
|
||||||
info[CONF_HOST], info[CONF_USERNAME], info[CONF_PASSWORD])
|
|
||||||
|
|
||||||
return scanner if scanner.success_init else None
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
@ -68,16 +68,24 @@ class NetgearDeviceScanner(object):
|
|||||||
import pynetgear
|
import pynetgear
|
||||||
|
|
||||||
self.last_results = []
|
self.last_results = []
|
||||||
|
|
||||||
self._api = pynetgear.Netgear(host, username, password)
|
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
if host is None:
|
||||||
|
print("BIER")
|
||||||
|
self._api = pynetgear.Netgear()
|
||||||
|
elif username is None:
|
||||||
|
self._api = pynetgear.Netgear(password, host)
|
||||||
|
else:
|
||||||
|
self._api = pynetgear.Netgear(password, host, username)
|
||||||
|
|
||||||
_LOGGER.info("Logging in")
|
_LOGGER.info("Logging in")
|
||||||
|
|
||||||
self.success_init = self._api.login()
|
results = self._api.get_attached_devices()
|
||||||
|
|
||||||
|
self.success_init = results is not None
|
||||||
|
|
||||||
if self.success_init:
|
if self.success_init:
|
||||||
self._update_info()
|
self.last_results = results
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Failed to Login")
|
_LOGGER.error("Failed to Login")
|
||||||
|
|
||||||
|
@ -26,8 +26,12 @@ from collections import namedtuple
|
|||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
from libnmap.process import NmapProcess
|
from libnmap.process import NmapProcess
|
||||||
from libnmap.parser import NmapParser, NmapParserException
|
from libnmap.parser import NmapParser, NmapParserException
|
||||||
|
LIB_LOADED = True
|
||||||
|
except ImportError:
|
||||||
|
LIB_LOADED = False
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import CONF_HOSTS
|
from homeassistant.const import CONF_HOSTS
|
||||||
@ -43,6 +47,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# interval in minutes to exclude devices from a scan while they are home
|
# interval in minutes to exclude devices from a scan while they are home
|
||||||
CONF_HOME_INTERVAL = "home_interval"
|
CONF_HOME_INTERVAL = "home_interval"
|
||||||
|
|
||||||
|
REQUIREMENTS = ['python-libnmap==0.6.1']
|
||||||
|
|
||||||
|
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
""" Validates config and returns a Nmap scanner. """
|
""" Validates config and returns a Nmap scanner. """
|
||||||
@ -50,6 +56,10 @@ def get_scanner(hass, config):
|
|||||||
_LOGGER):
|
_LOGGER):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if not LIB_LOADED:
|
||||||
|
_LOGGER.error("Error while importing dependency python-libnmap.")
|
||||||
|
return False
|
||||||
|
|
||||||
scanner = NmapDeviceScanner(config[DOMAIN])
|
scanner = NmapDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
return scanner if scanner.success_init else None
|
return scanner if scanner.success_init else None
|
||||||
|
157
homeassistant/components/device_tracker/thomson.py
Normal file
157
homeassistant/components/device_tracker/thomson.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.device_tracker.thomson
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Device tracker platform that supports scanning a THOMSON router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
This device tracker needs telnet to be enabled on the router.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the THOMSON tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: thomson
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_DEVICES_REGEX = re.compile(
|
||||||
|
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' +
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
|
||||||
|
r'(?P<status>([^\s]+))\s+' +
|
||||||
|
r'(?P<type>([^\s]+))\s+' +
|
||||||
|
r'(?P<intf>([^\s]+))\s+' +
|
||||||
|
r'(?P<hwintf>([^\s]+))\s+' +
|
||||||
|
r'(?P<host>([^\s]+))')
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
""" Validates config and returns a THOMSON scanner. """
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
scanner = ThomsonDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
|
|
||||||
|
class ThomsonDeviceScanner(object):
|
||||||
|
""" This class queries a router running THOMSON firmware
|
||||||
|
for connected devices. Adapted from ASUSWRT scanner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.host = config[CONF_HOST]
|
||||||
|
self.username = config[CONF_USERNAME]
|
||||||
|
self.password = config[CONF_PASSWORD]
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
self.last_results = {}
|
||||||
|
|
||||||
|
# Test the router is accessible
|
||||||
|
data = self.get_thomson_data()
|
||||||
|
self.success_init = data is not None
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
return [client['mac'] for client in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" Returns the name of the given device
|
||||||
|
or None if we don't know. """
|
||||||
|
if not self.last_results:
|
||||||
|
return None
|
||||||
|
for client in self.last_results:
|
||||||
|
if client['mac'] == device:
|
||||||
|
return client['host']
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def _update_info(self):
|
||||||
|
""" Ensures the information from the THOMSON router is up to date.
|
||||||
|
Returns boolean if scanning successful. """
|
||||||
|
if not self.success_init:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
_LOGGER.info("Checking ARP")
|
||||||
|
data = self.get_thomson_data()
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# flag C stands for CONNECTED
|
||||||
|
active_clients = [client for client in data.values() if
|
||||||
|
client['status'].find('C') != -1]
|
||||||
|
self.last_results = active_clients
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_thomson_data(self):
|
||||||
|
""" Retrieve data from THOMSON and return parsed result. """
|
||||||
|
try:
|
||||||
|
telnet = telnetlib.Telnet(self.host)
|
||||||
|
telnet.read_until(b'Username : ')
|
||||||
|
telnet.write((self.username + '\r\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'Password : ')
|
||||||
|
telnet.write((self.password + '\r\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'=>')
|
||||||
|
telnet.write(('hostmgr list\r\n').encode('ascii'))
|
||||||
|
devices_result = telnet.read_until(b'=>').split(b'\r\n')
|
||||||
|
telnet.write('exit\r\n'.encode('ascii'))
|
||||||
|
except EOFError:
|
||||||
|
_LOGGER.exception("Unexpected response from router")
|
||||||
|
return
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
_LOGGER.exception("Connection refused by router," +
|
||||||
|
" is telnet enabled?")
|
||||||
|
return
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for device in devices_result:
|
||||||
|
match = _DEVICES_REGEX.search(device.decode('utf-8'))
|
||||||
|
if match:
|
||||||
|
devices[match.group('ip')] = {
|
||||||
|
'ip': match.group('ip'),
|
||||||
|
'mac': match.group('mac').upper(),
|
||||||
|
'host': match.group('host'),
|
||||||
|
'status': match.group('status')
|
||||||
|
}
|
||||||
|
return devices
|
@ -31,6 +31,7 @@ password
|
|||||||
The password for your given admin account.
|
The password for your given admin account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import base64
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import re
|
import re
|
||||||
@ -55,6 +56,9 @@ def get_scanner(hass, config):
|
|||||||
_LOGGER):
|
_LOGGER):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
scanner = Tplink2DeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
if not scanner.success_init:
|
||||||
scanner = TplinkDeviceScanner(config[DOMAIN])
|
scanner = TplinkDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
return scanner if scanner.success_init else None
|
return scanner if scanner.success_init else None
|
||||||
@ -115,3 +119,63 @@ class TplinkDeviceScanner(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Tplink2DeviceScanner(TplinkDeviceScanner):
|
||||||
|
""" This class queries a wireless router running newer version of TP-Link
|
||||||
|
firmware for connected devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
return self.last_results.keys()
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" The TP-Link firmware doesn't save the name of the wireless
|
||||||
|
device. """
|
||||||
|
|
||||||
|
return self.last_results.get(device)
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def _update_info(self):
|
||||||
|
""" Ensures the information from the TP-Link router is up to date.
|
||||||
|
Returns boolean if scanning successful. """
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
_LOGGER.info("Loading wireless clients...")
|
||||||
|
|
||||||
|
url = 'http://{}/data/map_access_wireless_client_grid.json'\
|
||||||
|
.format(self.host)
|
||||||
|
referer = 'http://{}'.format(self.host)
|
||||||
|
|
||||||
|
# Router uses Authorization cookie instead of header
|
||||||
|
# Let's create the cookie
|
||||||
|
username_password = '{}:{}'.format(self.username, self.password)
|
||||||
|
b64_encoded_username_password = base64.b64encode(
|
||||||
|
username_password.encode('ascii')
|
||||||
|
).decode('ascii')
|
||||||
|
cookie = 'Authorization=Basic {}'\
|
||||||
|
.format(b64_encoded_username_password)
|
||||||
|
|
||||||
|
response = requests.post(url, headers={'referer': referer,
|
||||||
|
'cookie': cookie})
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = response.json().get('data')
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.error("Router didn't respond with JSON. "
|
||||||
|
"Check if credentials are correct.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if result:
|
||||||
|
self.last_results = {
|
||||||
|
device['mac_addr'].replace('-', ':'): device['name']
|
||||||
|
for device in result
|
||||||
|
}
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
@ -19,7 +19,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "discovery"
|
DOMAIN = "discovery"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['netdisco>=0.1']
|
REQUIREMENTS = ['netdisco==0.3']
|
||||||
|
|
||||||
SCAN_INTERVAL = 300 # seconds
|
SCAN_INTERVAL = 300 # seconds
|
||||||
|
|
||||||
@ -28,11 +28,13 @@ SCAN_INTERVAL = 300 # seconds
|
|||||||
SERVICE_WEMO = 'belkin_wemo'
|
SERVICE_WEMO = 'belkin_wemo'
|
||||||
SERVICE_HUE = 'philips_hue'
|
SERVICE_HUE = 'philips_hue'
|
||||||
SERVICE_CAST = 'google_cast'
|
SERVICE_CAST = 'google_cast'
|
||||||
|
SERVICE_NETGEAR = 'netgear_router'
|
||||||
|
|
||||||
SERVICE_HANDLERS = {
|
SERVICE_HANDLERS = {
|
||||||
SERVICE_WEMO: "switch",
|
SERVICE_WEMO: "switch",
|
||||||
SERVICE_CAST: "media_player",
|
SERVICE_CAST: "media_player",
|
||||||
SERVICE_HUE: "light",
|
SERVICE_HUE: "light",
|
||||||
|
SERVICE_NETGEAR: 'device_tracker',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -77,6 +79,13 @@ def setup(hass, config):
|
|||||||
if not component:
|
if not component:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Hack - fix when device_tracker supports discovery
|
||||||
|
if service == SERVICE_NETGEAR:
|
||||||
|
bootstrap.setup_component(hass, component, {
|
||||||
|
'device_tracker': {'platform': 'netgear'}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
# This component cannot be setup.
|
# This component cannot be setup.
|
||||||
if not bootstrap.setup_component(hass, component, config):
|
if not bootstrap.setup_component(hass, component, config):
|
||||||
return
|
return
|
||||||
|
@ -5,22 +5,44 @@
|
|||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
|
|
||||||
<link rel='manifest' href='/static/manifest.json' />
|
<link rel='manifest' href='/static/manifest.json' />
|
||||||
|
|
||||||
<meta name='apple-mobile-web-app-capable' content='yes'>
|
|
||||||
<meta name='mobile-web-app-capable' content='yes'>
|
|
||||||
|
|
||||||
<meta name='viewport' content='width=device-width,
|
|
||||||
user-scalable=no' />
|
|
||||||
|
|
||||||
<link rel='shortcut icon' href='/static/favicon.ico' />
|
<link rel='shortcut icon' href='/static/favicon.ico' />
|
||||||
<link rel='icon' type='image/png'
|
<link rel='icon' type='image/png'
|
||||||
href='/static/favicon-192x192.png' sizes='192x192'>
|
href='/static/favicon-192x192.png' sizes='192x192'>
|
||||||
<link rel='apple-touch-icon' sizes='180x180'
|
<link rel='apple-touch-icon' sizes='180x180'
|
||||||
href='/static/favicon-apple-180x180.png'>
|
href='/static/favicon-apple-180x180.png'>
|
||||||
|
<meta name='apple-mobile-web-app-capable' content='yes'>
|
||||||
|
<meta name='mobile-web-app-capable' content='yes'>
|
||||||
|
<meta name='viewport' content='width=device-width,
|
||||||
|
user-scalable=no' />
|
||||||
<meta name='theme-color' content='#03a9f4'>
|
<meta name='theme-color' content='#03a9f4'>
|
||||||
|
<style>
|
||||||
|
#init {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Roboto', 'Noto', sans-serif;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
#init p {
|
||||||
|
margin-bottom: 101px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body fullbleed>
|
<body fullbleed>
|
||||||
<h3 id='init' align='center'>Initializing Home Assistant</h3>
|
<div id='init'>
|
||||||
|
<img src='/static/splash.png' height='230' />
|
||||||
|
<p>Initializing</p>
|
||||||
|
</div>
|
||||||
<script src='/static/webcomponents-lite.min.js'></script>
|
<script src='/static/webcomponents-lite.min.js'></script>
|
||||||
<link rel='import' href='/static/{{ app_url }}' />
|
<link rel='import' href='/static/{{ app_url }}' />
|
||||||
<home-assistant auth='{{ auth }}'></home-assistant>
|
<home-assistant auth='{{ auth }}'></home-assistant>
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "ccfe7497d635ab4df3e6943b05adbd9b"
|
VERSION = "e9060d58fc9034468cfefa9794026d0c"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
Subproject commit 5a3fcc970b30d640e6a370b6f20904a745f69659
|
Subproject commit a97750b5dd887af42030e01bfe50bc3c60183514
|
BIN
homeassistant/components/frontend/www_static/splash.png
Normal file
BIN
homeassistant/components/frontend/www_static/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
@ -1,2 +0,0 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
|
||||||
VERSION = ""
|
|
File diff suppressed because one or more lines are too long
@ -5,8 +5,9 @@ homeassistant.components.group
|
|||||||
Provides functionality to group devices that can be turned on or off.
|
Provides functionality to group devices that can be turned on or off.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.helpers import generate_entity_id
|
from homeassistant.helpers import generate_entity_id
|
||||||
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -102,10 +103,8 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
|
|||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Sets up all groups found definded in the configuration. """
|
""" Sets up all groups found definded in the configuration. """
|
||||||
for name, entity_ids in config.get(DOMAIN, {}).items():
|
for name, entity_ids in config.get(DOMAIN, {}).items():
|
||||||
# Support old deprecated method - 2/28/2015
|
|
||||||
if isinstance(entity_ids, str):
|
if isinstance(entity_ids, str):
|
||||||
entity_ids = entity_ids.split(",")
|
entity_ids = entity_ids.split(",")
|
||||||
|
|
||||||
setup_group(hass, name, entity_ids)
|
setup_group(hass, name, entity_ids)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -162,8 +161,8 @@ class Group(Entity):
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
""" Starts the tracking. """
|
""" Starts the tracking. """
|
||||||
self.hass.states.track_change(
|
track_state_change(
|
||||||
self.tracking, self._state_changed_listener)
|
self.hass, self.tracking, self._state_changed_listener)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Unregisters the group from Home Assistant. """
|
""" Unregisters the group from Home Assistant. """
|
||||||
|
@ -86,7 +86,7 @@ from http import cookies
|
|||||||
from socketserver import ThreadingMixIn
|
from socketserver import ThreadingMixIn
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
SERVER_PORT, CONTENT_TYPE_JSON,
|
SERVER_PORT, CONTENT_TYPE_JSON,
|
||||||
HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING,
|
HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING,
|
||||||
@ -119,7 +119,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup(hass, config=None):
|
def setup(hass, config=None):
|
||||||
""" Sets up the HTTP API and debug interface. """
|
""" Sets up the HTTP API and debug interface. """
|
||||||
|
|
||||||
if config is None or DOMAIN not in config:
|
if config is None or DOMAIN not in config:
|
||||||
config = {DOMAIN: {}}
|
config = {DOMAIN: {}}
|
||||||
|
|
||||||
@ -139,9 +138,14 @@ def setup(hass, config=None):
|
|||||||
|
|
||||||
sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True)
|
sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True)
|
||||||
|
|
||||||
|
try:
|
||||||
server = HomeAssistantHTTPServer(
|
server = HomeAssistantHTTPServer(
|
||||||
(server_host, server_port), RequestHandler, hass, api_password,
|
(server_host, server_port), RequestHandler, hass, api_password,
|
||||||
development, no_password_set, sessions_enabled)
|
development, no_password_set, sessions_enabled)
|
||||||
|
except OSError:
|
||||||
|
# Happens if address already in use
|
||||||
|
_LOGGER.exception("Error setting up HTTP server")
|
||||||
|
return False
|
||||||
|
|
||||||
hass.bus.listen_once(
|
hass.bus.listen_once(
|
||||||
ha.EVENT_HOMEASSISTANT_START,
|
ha.EVENT_HOMEASSISTANT_START,
|
||||||
@ -183,10 +187,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
|||||||
_LOGGER.info("running http in development mode")
|
_LOGGER.info("running http in development mode")
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
""" Starts the server. """
|
""" Starts the HTTP server. """
|
||||||
self.hass.bus.listen_once(
|
def stop_http(event):
|
||||||
ha.EVENT_HOMEASSISTANT_STOP,
|
""" Stops the HTTP server. """
|
||||||
lambda event: self.shutdown())
|
self.shutdown()
|
||||||
|
|
||||||
|
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Starting web interface at http://%s:%d", *self.server_address)
|
"Starting web interface at http://%s:%d", *self.server_address)
|
||||||
@ -199,7 +205,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
|||||||
self.serve_forever()
|
self.serve_forever()
|
||||||
|
|
||||||
def register_path(self, method, url, callback, require_auth=True):
|
def register_path(self, method, url, callback, require_auth=True):
|
||||||
""" Regitsters a path wit the server. """
|
""" Registers a path wit the server. """
|
||||||
self.paths.append((method, url, callback, require_auth))
|
self.paths.append((method, url, callback, require_auth))
|
||||||
|
|
||||||
|
|
||||||
|
41
homeassistant/components/introduction.py
Normal file
41
homeassistant/components/introduction.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.introduction
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Component that will help guide the user taking its first steps.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
DOMAIN = 'introduction'
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config=None):
|
||||||
|
""" Setup the introduction component. """
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.info("""
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Hello, and welcome to Home Assistant!
|
||||||
|
|
||||||
|
We'll hope that we can make all your dreams come true.
|
||||||
|
|
||||||
|
Here are some resources to get started:
|
||||||
|
|
||||||
|
- Configuring Home Assistant:
|
||||||
|
https://home-assistant.io/getting-started/configuration.html
|
||||||
|
|
||||||
|
- Available components:
|
||||||
|
https://home-assistant.io/components/
|
||||||
|
|
||||||
|
- Chat room:
|
||||||
|
https://gitter.im/balloob/home-assistant
|
||||||
|
|
||||||
|
This message is generated by the introduction component. You can
|
||||||
|
disable it in configuration.yaml.
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
""")
|
||||||
|
|
||||||
|
return True
|
@ -1,15 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.isy994
|
homeassistant.components.isy994
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Connects to an ISY-994 controller and loads relevant components to control its
|
Connects to an ISY-994 controller and loads relevant components to control its
|
||||||
devices. Also contains the base classes for ISY Sensors, Lights, and Switches.
|
devices. Also contains the base classes for ISY Sensors, Lights, and Switches.
|
||||||
|
|
||||||
|
For configuration details please visit the documentation for this component at
|
||||||
|
https://home-assistant.io/components/isy994.html
|
||||||
"""
|
"""
|
||||||
# system imports
|
|
||||||
import logging
|
import logging
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
# homeassistant imports
|
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
@ -19,10 +19,9 @@ from homeassistant.const import (
|
|||||||
EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED,
|
EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED,
|
||||||
ATTR_FRIENDLY_NAME)
|
ATTR_FRIENDLY_NAME)
|
||||||
|
|
||||||
# homeassistant constants
|
|
||||||
DOMAIN = "isy994"
|
DOMAIN = "isy994"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['PyISY>=1.0.5']
|
REQUIREMENTS = ['PyISY==1.0.5']
|
||||||
DISCOVER_LIGHTS = "isy994.lights"
|
DISCOVER_LIGHTS = "isy994.lights"
|
||||||
DISCOVER_SWITCHES = "isy994.switches"
|
DISCOVER_SWITCHES = "isy994.switches"
|
||||||
DISCOVER_SENSORS = "isy994.sensors"
|
DISCOVER_SENSORS = "isy994.sensors"
|
||||||
@ -31,7 +30,6 @@ SENSOR_STRING = 'Sensor'
|
|||||||
HIDDEN_STRING = '{HIDE ME}'
|
HIDDEN_STRING = '{HIDE ME}'
|
||||||
CONF_TLS_VER = 'tls'
|
CONF_TLS_VER = 'tls'
|
||||||
|
|
||||||
# setup logger
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -158,6 +156,12 @@ class ISYDeviceABC(ToggleEntity):
|
|||||||
attr = {ATTR_FRIENDLY_NAME: self.name}
|
attr = {ATTR_FRIENDLY_NAME: self.name}
|
||||||
for name, prop in self._attrs.items():
|
for name, prop in self._attrs.items():
|
||||||
attr[name] = getattr(self, prop)
|
attr[name] = getattr(self, prop)
|
||||||
|
attr = self._attr_filter(attr)
|
||||||
|
return attr
|
||||||
|
|
||||||
|
def _attr_filter(self, attr):
|
||||||
|
""" Placeholder for attribute filters. """
|
||||||
|
# pylint: disable=no-self-use
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "keyboard"
|
DOMAIN = "keyboard"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['pyuserinput>=0.1.9']
|
REQUIREMENTS = ['pyuserinput==0.1.9']
|
||||||
|
|
||||||
|
|
||||||
def volume_up(hass):
|
def volume_up(hass):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.light
|
homeassistant.components.light
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Provides functionality to interact with lights.
|
Provides functionality to interact with lights.
|
||||||
|
|
||||||
@ -166,8 +166,8 @@ def setup(hass, config):
|
|||||||
profiles = {}
|
profiles = {}
|
||||||
|
|
||||||
for profile_path in profile_paths:
|
for profile_path in profile_paths:
|
||||||
|
if not os.path.isfile(profile_path):
|
||||||
if os.path.isfile(profile_path):
|
continue
|
||||||
with open(profile_path) as inp:
|
with open(profile_path) as inp:
|
||||||
reader = csv.reader(inp)
|
reader = csv.reader(inp)
|
||||||
|
|
||||||
@ -178,7 +178,6 @@ def setup(hass, config):
|
|||||||
for profile_id, color_x, color_y, brightness in reader:
|
for profile_id, color_x, color_y, brightness in reader:
|
||||||
profiles[profile_id] = (float(color_x), float(color_y),
|
profiles[profile_id] = (float(color_x), float(color_y),
|
||||||
int(brightness))
|
int(brightness))
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# ValueError if not 4 values per row
|
# ValueError if not 4 values per row
|
||||||
# ValueError if convert to float/int failed
|
# ValueError if convert to float/int failed
|
||||||
@ -206,7 +205,11 @@ def setup(hass, config):
|
|||||||
for light in target_lights:
|
for light in target_lights:
|
||||||
light.turn_off(**params)
|
light.turn_off(**params)
|
||||||
|
|
||||||
else:
|
for light in target_lights:
|
||||||
|
if light.should_poll:
|
||||||
|
light.update_ha_state(True)
|
||||||
|
return
|
||||||
|
|
||||||
# Processing extra data for turn light on request
|
# Processing extra data for turn light on request
|
||||||
|
|
||||||
# We process the profile first so that we get the desired
|
# We process the profile first so that we get the desired
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
""" Support for Hue lights. """
|
"""
|
||||||
|
homeassistant.components.light.hue
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Hue lights.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -12,7 +16,7 @@ from homeassistant.components.light import (
|
|||||||
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT,
|
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT,
|
||||||
EFFECT_COLORLOOP)
|
EFFECT_COLORLOOP)
|
||||||
|
|
||||||
REQUIREMENTS = ['phue>=0.8']
|
REQUIREMENTS = ['phue==0.8']
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
||||||
|
|
||||||
@ -35,7 +39,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if discovery_info is not None:
|
if discovery_info is not None:
|
||||||
host = urlparse(discovery_info).hostname
|
host = urlparse(discovery_info[1]).hostname
|
||||||
else:
|
else:
|
||||||
host = config.get(CONF_HOST, None)
|
host = config.get(CONF_HOST, None)
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
""" Support for ISY994 lights. """
|
"""
|
||||||
# system imports
|
homeassistant.components.light.isy994
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for ISY994 lights.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# homeassistant imports
|
|
||||||
from homeassistant.components.isy994 import (ISYDeviceABC, ISY, SENSOR_STRING,
|
from homeassistant.components.isy994 import (ISYDeviceABC, ISY, SENSOR_STRING,
|
||||||
HIDDEN_STRING)
|
HIDDEN_STRING)
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||||
@ -10,7 +12,7 @@ from homeassistant.const import STATE_ON, STATE_OFF
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the isy994 platform. """
|
""" Sets up the ISY994 platform. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
devs = []
|
devs = []
|
||||||
# verify connection
|
# verify connection
|
||||||
@ -29,10 +31,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class ISYLightDevice(ISYDeviceABC):
|
class ISYLightDevice(ISYDeviceABC):
|
||||||
""" represents as isy light within home assistant. """
|
""" Represents as ISY light. """
|
||||||
|
|
||||||
_domain = 'light'
|
_domain = 'light'
|
||||||
_dtype = 'analog'
|
_dtype = 'analog'
|
||||||
_attrs = {ATTR_BRIGHTNESS: 'value'}
|
_attrs = {ATTR_BRIGHTNESS: 'value'}
|
||||||
_onattrs = [ATTR_BRIGHTNESS]
|
_onattrs = [ATTR_BRIGHTNESS]
|
||||||
_states = [STATE_ON, STATE_OFF]
|
_states = [STATE_ON, STATE_OFF]
|
||||||
|
|
||||||
|
def _attr_filter(self, attr):
|
||||||
|
""" Filter brightness out of entity while off. """
|
||||||
|
if ATTR_BRIGHTNESS in attr and not self.is_on:
|
||||||
|
del attr[ATTR_BRIGHTNESS]
|
||||||
|
return attr
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.light.limitlessled
|
homeassistant.components.light.limitlessled
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Support for LimitlessLED bulbs, also known as...
|
Support for LimitlessLED bulbs, also known as...
|
||||||
|
|
||||||
EasyBulb
|
- EasyBulb
|
||||||
AppLight
|
- AppLight
|
||||||
AppLamp
|
- AppLamp
|
||||||
MiLight
|
- MiLight
|
||||||
LEDme
|
- LEDme
|
||||||
dekolight
|
- dekolight
|
||||||
iLight
|
- iLight
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use limitlessled you will need to add the following to your
|
||||||
|
config/configuration.yaml.
|
||||||
|
|
||||||
light:
|
light:
|
||||||
platform: limitlessled
|
platform: limitlessled
|
||||||
@ -24,10 +29,12 @@ light:
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||||
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
from homeassistant.components.light import (Light, ATTR_BRIGHTNESS,
|
||||||
|
ATTR_XY_COLOR)
|
||||||
|
from homeassistant.util.color import color_RGB_to_xy
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['ledcontroller>=1.0.7']
|
REQUIREMENTS = ['ledcontroller==1.0.7']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
@ -57,6 +64,29 @@ class LimitlessLED(Light):
|
|||||||
self._name = name or DEVICE_DEFAULT_NAME
|
self._name = name or DEVICE_DEFAULT_NAME
|
||||||
self._state = False
|
self._state = False
|
||||||
self._brightness = 100
|
self._brightness = 100
|
||||||
|
self._xy_color = color_RGB_to_xy(255, 255, 255)
|
||||||
|
|
||||||
|
# Build a color table that maps an RGB color to a color string
|
||||||
|
# recognized by LedController's set_color method
|
||||||
|
self._color_table = [(color_RGB_to_xy(*x[0]), x[1]) for x in [
|
||||||
|
((0xFF, 0xFF, 0xFF), 'white'),
|
||||||
|
((0xEE, 0x82, 0xEE), 'violet'),
|
||||||
|
((0x41, 0x69, 0xE1), 'royal_blue'),
|
||||||
|
((0x87, 0xCE, 0xFA), 'baby_blue'),
|
||||||
|
((0x00, 0xFF, 0xFF), 'aqua'),
|
||||||
|
((0x7F, 0xFF, 0xD4), 'royal_mint'),
|
||||||
|
((0x2E, 0x8B, 0x57), 'seafoam_green'),
|
||||||
|
((0x00, 0x80, 0x00), 'green'),
|
||||||
|
((0x32, 0xCD, 0x32), 'lime_green'),
|
||||||
|
((0xFF, 0xFF, 0x00), 'yellow'),
|
||||||
|
((0xDA, 0xA5, 0x20), 'yellow_orange'),
|
||||||
|
((0xFF, 0xA5, 0x00), 'orange'),
|
||||||
|
((0xFF, 0x00, 0x00), 'red'),
|
||||||
|
((0xFF, 0xC0, 0xCB), 'pink'),
|
||||||
|
((0xFF, 0x00, 0xFF), 'fusia'),
|
||||||
|
((0xDA, 0x70, 0xD6), 'lilac'),
|
||||||
|
((0xE6, 0xE6, 0xFA), 'lavendar'),
|
||||||
|
]]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -72,6 +102,22 @@ class LimitlessLED(Light):
|
|||||||
def brightness(self):
|
def brightness(self):
|
||||||
return self._brightness
|
return self._brightness
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_xy(self):
|
||||||
|
return self._xy_color
|
||||||
|
|
||||||
|
def _xy_to_led_color(self, xy_color):
|
||||||
|
""" Convert an XY color to the closest LedController color string """
|
||||||
|
def abs_dist_squared(p_0, p_1):
|
||||||
|
""" Returns the absolute value of the squared distance """
|
||||||
|
return abs((p_0[0] - p_1[0])**2 + (p_0[1] - p_1[1])**2)
|
||||||
|
|
||||||
|
candidates = [(abs_dist_squared(xy_color, x[0]), x[1]) for x in
|
||||||
|
self._color_table]
|
||||||
|
|
||||||
|
# First candidate in the sorted list is closest to desired color:
|
||||||
|
return sorted(candidates)[0][1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
""" True if device is on. """
|
""" True if device is on. """
|
||||||
@ -84,7 +130,11 @@ class LimitlessLED(Light):
|
|||||||
if ATTR_BRIGHTNESS in kwargs:
|
if ATTR_BRIGHTNESS in kwargs:
|
||||||
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
||||||
|
|
||||||
self.led.set_brightness(self._brightness, self.group)
|
if ATTR_XY_COLOR in kwargs:
|
||||||
|
self._xy_color = kwargs[ATTR_XY_COLOR]
|
||||||
|
|
||||||
|
self.led.set_color(self._xy_to_led_color(self._xy_color), self.group)
|
||||||
|
self.led.set_brightness(self._brightness / 255.0, self.group)
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
""" Support for Tellstick lights. """
|
"""
|
||||||
|
homeassistant.components.light.tellstick
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Tellstick lights.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
# pylint: disable=no-name-in-module, import-error
|
# pylint: disable=no-name-in-module, import-error
|
||||||
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
||||||
from homeassistant.const import ATTR_FRIENDLY_NAME
|
from homeassistant.const import ATTR_FRIENDLY_NAME
|
||||||
import tellcore.constants as tellcore_constants
|
import tellcore.constants as tellcore_constants
|
||||||
|
|
||||||
|
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return tellstick lights. """
|
""" Find and return Tellstick lights. """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tellcore.telldus as telldus
|
import tellcore.telldus as telldus
|
||||||
@ -27,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class TellstickLight(Light):
|
class TellstickLight(Light):
|
||||||
""" Represents a tellstick light """
|
""" Represents a Tellstick light. """
|
||||||
last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
||||||
tellcore_constants.TELLSTICK_TURNOFF |
|
tellcore_constants.TELLSTICK_TURNOFF |
|
||||||
tellcore_constants.TELLSTICK_DIM |
|
tellcore_constants.TELLSTICK_DIM |
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
Support for Vera lights.
|
homeassistant.components.light.vera
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for Vera lights. This component is useful if you wish for switches
|
||||||
|
connected to your Vera controller to appear as lights in Home Assistant.
|
||||||
|
All switches will be added as a light unless you exclude them in the config.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
This component is useful if you wish for switches connected to your Vera
|
|
||||||
controller to appear as lights in homeassistant. All switches will be added
|
|
||||||
as a light unless you exclude them in the config.
|
|
||||||
|
|
||||||
To use the Vera lights you will need to add something like the following to
|
To use the Vera lights you will need to add something like the following to
|
||||||
your config/configuration.yaml
|
your config/configuration.yaml.
|
||||||
|
|
||||||
light:
|
light:
|
||||||
platform: vera
|
platform: vera
|
||||||
@ -19,22 +21,19 @@ light:
|
|||||||
13:
|
13:
|
||||||
name: Another switch
|
name: Another switch
|
||||||
|
|
||||||
VARIABLES:
|
Variables:
|
||||||
|
|
||||||
vera_controller_url
|
vera_controller_url
|
||||||
*Required
|
*Required
|
||||||
This is the base URL of your vera controller including the port number if not
|
This is the base URL of your vera controller including the port number if not
|
||||||
running on 80
|
running on 80. Example: http://192.168.1.21:3480/
|
||||||
Example: http://192.168.1.21:3480/
|
|
||||||
|
|
||||||
|
|
||||||
device_data
|
device_data
|
||||||
*Optional
|
*Optional
|
||||||
This contains an array additional device info for your Vera devices. It is not
|
This contains an array additional device info for your Vera devices. It is not
|
||||||
required and if not specified all lights configured in your Vera controller
|
required and if not specified all lights configured in your Vera controller
|
||||||
will be added with default values. You should use the id of your vera device
|
will be added with default values. You should use the id of your vera device
|
||||||
as the key for the device within device_data
|
as the key for the device within device_data.
|
||||||
|
|
||||||
|
|
||||||
These are the variables for the device_data array:
|
These are the variables for the device_data array:
|
||||||
|
|
||||||
@ -42,13 +41,12 @@ name
|
|||||||
*Optional
|
*Optional
|
||||||
This parameter allows you to override the name of your Vera device in the HA
|
This parameter allows you to override the name of your Vera device in the HA
|
||||||
interface, if not specified the value configured for the device in your Vera
|
interface, if not specified the value configured for the device in your Vera
|
||||||
will be used
|
will be used.
|
||||||
|
|
||||||
|
|
||||||
exclude
|
exclude
|
||||||
*Optional
|
*Optional
|
||||||
This parameter allows you to exclude the specified device from homeassistant,
|
This parameter allows you to exclude the specified device from Home Assistant,
|
||||||
it should be set to "true" if you want this device excluded
|
it should be set to "true" if you want this device excluded.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
""" Support for Wink lights. """
|
"""
|
||||||
|
homeassistant.components.light.wink
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Wink lights.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
|
||||||
|
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return Wink lights. """
|
""" Find and return Wink lights. """
|
||||||
@ -26,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class WinkLight(WinkToggleDevice):
|
class WinkLight(WinkToggleDevice):
|
||||||
""" Represents a Wink light """
|
""" Represents a Wink light. """
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
|
@ -8,7 +8,7 @@ from datetime import timedelta
|
|||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from homeassistant import State, DOMAIN as HA_DOMAIN
|
from homeassistant.core import State, DOMAIN as HA_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
|
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)
|
||||||
|
@ -98,9 +98,7 @@ ATTR_TO_PROPERTY = [
|
|||||||
def is_on(hass, entity_id=None):
|
def is_on(hass, entity_id=None):
|
||||||
""" Returns true if specified media player entity_id is on.
|
""" Returns true if specified media player entity_id is on.
|
||||||
Will check all media player if no entity_id specified. """
|
Will check all media player if no entity_id specified. """
|
||||||
|
|
||||||
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
|
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
|
||||||
|
|
||||||
return any(not hass.states.is_state(entity_id, STATE_OFF)
|
return any(not hass.states.is_state(entity_id, STATE_OFF)
|
||||||
for entity_id in entity_ids)
|
for entity_id in entity_ids)
|
||||||
|
|
||||||
@ -108,28 +106,24 @@ def is_on(hass, entity_id=None):
|
|||||||
def turn_on(hass, entity_id=None):
|
def turn_on(hass, entity_id=None):
|
||||||
""" Will turn on specified media player or all. """
|
""" Will turn on specified media player or all. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
||||||
|
|
||||||
|
|
||||||
def turn_off(hass, entity_id=None):
|
def turn_off(hass, entity_id=None):
|
||||||
""" Will turn off specified media player or all. """
|
""" Will turn off specified media player or all. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||||
|
|
||||||
|
|
||||||
def volume_up(hass, entity_id=None):
|
def volume_up(hass, entity_id=None):
|
||||||
""" Send the media player the command for volume up. """
|
""" Send the media player the command for volume up. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
|
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
|
||||||
|
|
||||||
|
|
||||||
def volume_down(hass, entity_id=None):
|
def volume_down(hass, entity_id=None):
|
||||||
""" Send the media player the command for volume down. """
|
""" Send the media player the command for volume down. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
|
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
|
||||||
|
|
||||||
|
|
||||||
@ -156,35 +150,30 @@ def set_volume_level(hass, volume, entity_id=None):
|
|||||||
def media_play_pause(hass, entity_id=None):
|
def media_play_pause(hass, entity_id=None):
|
||||||
""" Send the media player the command for play/pause. """
|
""" Send the media player the command for play/pause. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
|
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
|
||||||
|
|
||||||
|
|
||||||
def media_play(hass, entity_id=None):
|
def media_play(hass, entity_id=None):
|
||||||
""" Send the media player the command for play/pause. """
|
""" Send the media player the command for play/pause. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
|
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
|
||||||
|
|
||||||
|
|
||||||
def media_pause(hass, entity_id=None):
|
def media_pause(hass, entity_id=None):
|
||||||
""" Send the media player the command for play/pause. """
|
""" Send the media player the command for play/pause. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
|
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
|
||||||
|
|
||||||
|
|
||||||
def media_next_track(hass, entity_id=None):
|
def media_next_track(hass, entity_id=None):
|
||||||
""" Send the media player the command for next track. """
|
""" Send the media player the command for next track. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
|
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
|
||||||
|
|
||||||
|
|
||||||
def media_previous_track(hass, entity_id=None):
|
def media_previous_track(hass, entity_id=None):
|
||||||
""" Send the media player the command for prev track. """
|
""" Send the media player the command for prev track. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
|
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
|
||||||
|
|
||||||
|
|
||||||
@ -262,29 +251,30 @@ def setup(hass, config):
|
|||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service)
|
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service)
|
||||||
|
|
||||||
def play_youtube_video_service(service, media_id):
|
def play_youtube_video_service(service, media_id=None):
|
||||||
""" Plays specified media_id on the media player. """
|
""" Plays specified media_id on the media player. """
|
||||||
target_players = component.extract_from_service(service)
|
if media_id is None:
|
||||||
|
service.data.get('video')
|
||||||
|
|
||||||
if media_id:
|
if media_id is None:
|
||||||
for player in target_players:
|
return
|
||||||
|
|
||||||
|
for player in component.extract_from_service(service):
|
||||||
player.play_youtube(media_id)
|
player.play_youtube(media_id)
|
||||||
|
|
||||||
if player.should_poll:
|
if player.should_poll:
|
||||||
player.update_ha_state(True)
|
player.update_ha_state(True)
|
||||||
|
|
||||||
hass.services.register(DOMAIN, "start_fireplace",
|
hass.services.register(
|
||||||
lambda service:
|
DOMAIN, "start_fireplace",
|
||||||
play_youtube_video_service(service, "eyU3bRy2x44"))
|
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"))
|
||||||
|
|
||||||
hass.services.register(DOMAIN, "start_epic_sax",
|
hass.services.register(
|
||||||
lambda service:
|
DOMAIN, "start_epic_sax",
|
||||||
play_youtube_video_service(service, "kxopViU98Xo"))
|
lambda service: play_youtube_video_service(service, "kxopViU98Xo"))
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO,
|
hass.services.register(
|
||||||
lambda service:
|
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service)
|
||||||
play_youtube_video_service(
|
|
||||||
service, service.data.get('video')))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -8,14 +8,9 @@ WARNING: This platform is currently not working due to a changed Cast API
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
|
||||||
import pychromecast
|
|
||||||
except ImportError:
|
|
||||||
pychromecast = None
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
|
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
|
||||||
STATE_UNKNOWN)
|
STATE_UNKNOWN, CONF_HOST)
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
MediaPlayerDevice,
|
MediaPlayerDevice,
|
||||||
@ -24,7 +19,7 @@ from homeassistant.components.media_player import (
|
|||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
||||||
|
|
||||||
REQUIREMENTS = ['pychromecast>=0.6.9']
|
REQUIREMENTS = ['pychromecast==0.6.12']
|
||||||
CONF_IGNORE_CEC = 'ignore_cec'
|
CONF_IGNORE_CEC = 'ignore_cec'
|
||||||
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
||||||
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
@ -32,21 +27,23 @@ SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
|||||||
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE
|
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE
|
||||||
KNOWN_HOSTS = []
|
KNOWN_HOSTS = []
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
cast = None
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the cast platform. """
|
""" Sets up the cast platform. """
|
||||||
global pychromecast # pylint: disable=invalid-name
|
global cast
|
||||||
if pychromecast is None:
|
import pychromecast
|
||||||
import pychromecast as pychromecast_
|
cast = pychromecast
|
||||||
pychromecast = pychromecast_
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# import CEC IGNORE attributes
|
# import CEC IGNORE attributes
|
||||||
ignore_cec = config.get(CONF_IGNORE_CEC, [])
|
ignore_cec = config.get(CONF_IGNORE_CEC, [])
|
||||||
if isinstance(ignore_cec, list):
|
if isinstance(ignore_cec, list):
|
||||||
pychromecast.IGNORE_CEC += ignore_cec
|
cast.IGNORE_CEC += ignore_cec
|
||||||
else:
|
else:
|
||||||
logger.error('Chromecast conig, %s must be a list.', CONF_IGNORE_CEC)
|
logger.error('Chromecast conig, %s must be a list.', CONF_IGNORE_CEC)
|
||||||
|
|
||||||
@ -55,9 +52,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if discovery_info and discovery_info[0] not in KNOWN_HOSTS:
|
if discovery_info and discovery_info[0] not in KNOWN_HOSTS:
|
||||||
hosts = [discovery_info[0]]
|
hosts = [discovery_info[0]]
|
||||||
|
|
||||||
|
elif CONF_HOST in config:
|
||||||
|
hosts = [config[CONF_HOST]]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
hosts = (host_port[0] for host_port
|
hosts = (host_port[0] for host_port
|
||||||
in pychromecast.discover_chromecasts()
|
in cast.discover_chromecasts()
|
||||||
if host_port[0] not in KNOWN_HOSTS)
|
if host_port[0] not in KNOWN_HOSTS)
|
||||||
|
|
||||||
casts = []
|
casts = []
|
||||||
@ -65,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
for host in hosts:
|
for host in hosts:
|
||||||
try:
|
try:
|
||||||
casts.append(CastDevice(host))
|
casts.append(CastDevice(host))
|
||||||
except pychromecast.ChromecastConnectionError:
|
except cast.ChromecastConnectionError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
KNOWN_HOSTS.append(host)
|
KNOWN_HOSTS.append(host)
|
||||||
@ -80,7 +80,7 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
import pychromecast.controllers.youtube as youtube
|
import pychromecast.controllers.youtube as youtube
|
||||||
self.cast = pychromecast.Chromecast(host)
|
self.cast = cast.Chromecast(host)
|
||||||
self.youtube = youtube.YouTubeController()
|
self.youtube = youtube.YouTubeController()
|
||||||
self.cast.register_handler(self.youtube)
|
self.cast.register_handler(self.youtube)
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
self.cast.quit_app()
|
self.cast.quit_app()
|
||||||
|
|
||||||
self.cast.play_media(
|
self.cast.play_media(
|
||||||
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
|
CAST_SPLASH, cast.STREAM_TYPE_BUFFERED)
|
||||||
|
|
||||||
def turn_off(self):
|
def turn_off(self):
|
||||||
""" Turns Chromecast off. """
|
""" Turns Chromecast off. """
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.media_player.kodi
|
homeassistant.components.media_player.kodi
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Provides an interface to the XBMC/Kodi JSON-RPC API
|
Provides an interface to the XBMC/Kodi JSON-RPC API
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
To use Kodi add something like this to your configuration:
|
To use the Kodi you will need to add something like the following to
|
||||||
|
your config/configuration.yaml.
|
||||||
|
|
||||||
media_player:
|
media_player:
|
||||||
platform: kodi
|
platform: kodi
|
||||||
@ -19,7 +19,7 @@ Variables:
|
|||||||
|
|
||||||
name
|
name
|
||||||
*Optional
|
*Optional
|
||||||
The name of the device
|
The name of the device.
|
||||||
|
|
||||||
url
|
url
|
||||||
*Required
|
*Required
|
||||||
@ -27,13 +27,12 @@ The URL of the XBMC/Kodi JSON-RPC API. Example: http://192.168.0.123/jsonrpc
|
|||||||
|
|
||||||
user
|
user
|
||||||
*Optional
|
*Optional
|
||||||
The XBMC/Kodi HTTP username
|
The XBMC/Kodi HTTP username.
|
||||||
|
|
||||||
password
|
password
|
||||||
*Optional
|
*Optional
|
||||||
The XBMC/Kodi HTTP password
|
The XBMC/Kodi HTTP password.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ except ImportError:
|
|||||||
jsonrpc_requests = None
|
jsonrpc_requests = None
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['jsonrpc-requests>=0.1']
|
REQUIREMENTS = ['jsonrpc-requests==0.1']
|
||||||
|
|
||||||
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
||||||
|
@ -48,7 +48,7 @@ from homeassistant.components.media_player import (
|
|||||||
MEDIA_TYPE_MUSIC)
|
MEDIA_TYPE_MUSIC)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['python-mpd2>=0.5.4']
|
REQUIREMENTS = ['python-mpd2==0.5.4']
|
||||||
|
|
||||||
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||||
|
319
homeassistant/components/media_player/squeezebox.py
Normal file
319
homeassistant/components/media_player/squeezebox.py
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.media_player.squeezebox
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Provides an interface to the Logitech SqueezeBox API
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use SqueezeBox add something like this to your configuration:
|
||||||
|
|
||||||
|
media_player:
|
||||||
|
platform: squeezebox
|
||||||
|
host: 192.168.1.21
|
||||||
|
port: 9090
|
||||||
|
username: user
|
||||||
|
password: password
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The host name or address of the Logitech Media Server
|
||||||
|
|
||||||
|
port
|
||||||
|
*Optional
|
||||||
|
Telnet port to Logitech Media Server, default 9090
|
||||||
|
|
||||||
|
usermame
|
||||||
|
*Optional
|
||||||
|
Username, if password protection is enabled
|
||||||
|
|
||||||
|
password
|
||||||
|
*Optional
|
||||||
|
Password, if password protection is enabled
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import telnetlib
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import (
|
||||||
|
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET,
|
||||||
|
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||||
|
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
|
||||||
|
MEDIA_TYPE_MUSIC, DOMAIN)
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
|
||||||
|
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
||||||
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK |\
|
||||||
|
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the squeezebox platform. """
|
||||||
|
if not config.get(CONF_HOST):
|
||||||
|
_LOGGER.error(
|
||||||
|
"Missing required configuration items in %s: %s",
|
||||||
|
DOMAIN,
|
||||||
|
CONF_HOST)
|
||||||
|
return False
|
||||||
|
|
||||||
|
lms = LogitechMediaServer(
|
||||||
|
config.get(CONF_HOST),
|
||||||
|
config.get('port', '9090'),
|
||||||
|
config.get(CONF_USERNAME),
|
||||||
|
config.get(CONF_PASSWORD))
|
||||||
|
|
||||||
|
if not lms.init_success:
|
||||||
|
return False
|
||||||
|
|
||||||
|
add_devices(lms.create_players())
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class LogitechMediaServer(object):
|
||||||
|
""" Represents a Logitech media server. """
|
||||||
|
|
||||||
|
def __init__(self, host, port, username, password):
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self._username = username
|
||||||
|
self._password = password
|
||||||
|
self.http_port = self._get_http_port()
|
||||||
|
self.init_success = True if self.http_port else False
|
||||||
|
|
||||||
|
def _get_http_port(self):
|
||||||
|
""" Get http port from media server, it is used to get cover art """
|
||||||
|
http_port = None
|
||||||
|
try:
|
||||||
|
http_port = self.query('pref', 'httpport', '?')
|
||||||
|
if not http_port:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Unable to read data from server %s:%s",
|
||||||
|
self.host,
|
||||||
|
self.port)
|
||||||
|
return
|
||||||
|
return http_port
|
||||||
|
except ConnectionError as ex:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Failed to connect to server %s:%s - %s",
|
||||||
|
self.host,
|
||||||
|
self.port,
|
||||||
|
ex)
|
||||||
|
return
|
||||||
|
|
||||||
|
def create_players(self):
|
||||||
|
""" Create a list of SqueezeBoxDevices connected to the LMS """
|
||||||
|
players = []
|
||||||
|
count = self.query('player', 'count', '?')
|
||||||
|
for index in range(0, int(count)):
|
||||||
|
player_id = self.query('player', 'id', str(index), '?')
|
||||||
|
player = SqueezeBoxDevice(self, player_id)
|
||||||
|
players.append(player)
|
||||||
|
return players
|
||||||
|
|
||||||
|
def query(self, *parameters):
|
||||||
|
""" Send request and await response from server """
|
||||||
|
telnet = telnetlib.Telnet(self.host, self.port)
|
||||||
|
if self._username and self._password:
|
||||||
|
telnet.write('login {username} {password}\n'.format(
|
||||||
|
username=self._username,
|
||||||
|
password=self._password).encode('UTF-8'))
|
||||||
|
telnet.read_until(b'\n', timeout=3)
|
||||||
|
message = '{}\n'.format(' '.join(parameters))
|
||||||
|
telnet.write(message.encode('UTF-8'))
|
||||||
|
response = telnet.read_until(b'\n', timeout=3)\
|
||||||
|
.decode('UTF-8')\
|
||||||
|
.split(' ')[-1]\
|
||||||
|
.strip()
|
||||||
|
telnet.write(b'exit\n')
|
||||||
|
return urllib.parse.unquote(response)
|
||||||
|
|
||||||
|
def get_player_status(self, player):
|
||||||
|
""" Get ithe status of a player """
|
||||||
|
# (title) : Song title
|
||||||
|
# Requested Information
|
||||||
|
# a (artist): Artist name 'artist'
|
||||||
|
# d (duration): Song duration in seconds 'duration'
|
||||||
|
# K (artwork_url): URL to remote artwork
|
||||||
|
tags = 'adK'
|
||||||
|
new_status = {}
|
||||||
|
telnet = telnetlib.Telnet(self.host, self.port)
|
||||||
|
telnet.write('{player} status - 1 tags:{tags}\n'.format(
|
||||||
|
player=player,
|
||||||
|
tags=tags
|
||||||
|
).encode('UTF-8'))
|
||||||
|
response = telnet.read_until(b'\n', timeout=3)\
|
||||||
|
.decode('UTF-8')\
|
||||||
|
.split(' ')
|
||||||
|
telnet.write(b'exit\n')
|
||||||
|
for item in response:
|
||||||
|
parts = urllib.parse.unquote(item).partition(':')
|
||||||
|
new_status[parts[0]] = parts[2]
|
||||||
|
return new_status
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
# pylint: disable=too-many-public-methods
|
||||||
|
class SqueezeBoxDevice(MediaPlayerDevice):
|
||||||
|
""" Represents a SqueezeBox device. """
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def __init__(self, lms, player_id):
|
||||||
|
super(SqueezeBoxDevice, self).__init__()
|
||||||
|
self._lms = lms
|
||||||
|
self._id = player_id
|
||||||
|
self._name = self._lms.query(self._id, 'name', '?')
|
||||||
|
self._status = self._lms.get_player_status(self._id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
if 'power' in self._status and self._status['power'] == '0':
|
||||||
|
return STATE_OFF
|
||||||
|
if 'mode' in self._status:
|
||||||
|
if self._status['mode'] == 'pause':
|
||||||
|
return STATE_PAUSED
|
||||||
|
if self._status['mode'] == 'play':
|
||||||
|
return STATE_PLAYING
|
||||||
|
if self._status['mode'] == 'stop':
|
||||||
|
return STATE_IDLE
|
||||||
|
return STATE_UNKNOWN
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Retrieve latest state. """
|
||||||
|
self._status = self._lms.get_player_status(self._name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume_level(self):
|
||||||
|
""" Volume level of the media player (0..1). """
|
||||||
|
if 'mixer volume' in self._status:
|
||||||
|
return int(self._status['mixer volume']) / 100.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_volume_muted(self):
|
||||||
|
if 'mixer volume' in self._status:
|
||||||
|
return self._status['mixer volume'].startswith('-')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_content_id(self):
|
||||||
|
""" Content ID of current playing media. """
|
||||||
|
if 'current_title' in self._status:
|
||||||
|
return self._status['current_title']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_content_type(self):
|
||||||
|
""" Content type of current playing media. """
|
||||||
|
return MEDIA_TYPE_MUSIC
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_duration(self):
|
||||||
|
""" Duration of current playing media in seconds. """
|
||||||
|
if 'duration' in self._status:
|
||||||
|
return int(float(self._status['duration']))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_image_url(self):
|
||||||
|
""" Image url of current playing media. """
|
||||||
|
if 'artwork_url' in self._status:
|
||||||
|
return self._status['artwork_url']
|
||||||
|
return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\
|
||||||
|
.format(
|
||||||
|
server=self._lms.host,
|
||||||
|
port=self._lms.http_port,
|
||||||
|
player=self._id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_title(self):
|
||||||
|
""" Title of current playing media. """
|
||||||
|
if 'artist' in self._status and 'title' in self._status:
|
||||||
|
return '{artist} - {title}'.format(
|
||||||
|
artist=self._status['artist'],
|
||||||
|
title=self._status['title']
|
||||||
|
)
|
||||||
|
if 'current_title' in self._status:
|
||||||
|
return self._status['current_title']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_media_commands(self):
|
||||||
|
""" Flags of media commands that are supported. """
|
||||||
|
return SUPPORT_SQUEEZEBOX
|
||||||
|
|
||||||
|
def turn_off(self):
|
||||||
|
""" turn_off media player. """
|
||||||
|
self._lms.query(self._id, 'power', '0')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def volume_up(self):
|
||||||
|
""" volume_up media player. """
|
||||||
|
self._lms.query(self._id, 'mixer', 'volume', '+5')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def volume_down(self):
|
||||||
|
""" volume_down media player. """
|
||||||
|
self._lms.query(self._id, 'mixer', 'volume', '-5')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def set_volume_level(self, volume):
|
||||||
|
""" set volume level, range 0..1. """
|
||||||
|
volume_percent = str(int(volume*100))
|
||||||
|
self._lms.query(self._id, 'mixer', 'volume', volume_percent)
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def mute_volume(self, mute):
|
||||||
|
""" mute (true) or unmute (false) media player. """
|
||||||
|
mute_numeric = '1' if mute else '0'
|
||||||
|
self._lms.query(self._id, 'mixer', 'muting', mute_numeric)
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def media_play_pause(self):
|
||||||
|
""" media_play_pause media player. """
|
||||||
|
self._lms.query(self._id, 'pause')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def media_play(self):
|
||||||
|
""" media_play media player. """
|
||||||
|
self._lms.query(self._id, 'play')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def media_pause(self):
|
||||||
|
""" media_pause media player. """
|
||||||
|
self._lms.query(self._id, 'pause', '0')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def media_next_track(self):
|
||||||
|
""" Send next track command. """
|
||||||
|
self._lms.query(self._id, 'playlist', 'index', '+1')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def media_previous_track(self):
|
||||||
|
""" Send next track command. """
|
||||||
|
self._lms.query(self._id, 'playlist', 'index', '-1')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def media_seek(self, position):
|
||||||
|
""" Send seek command. """
|
||||||
|
self._lms.query(self._id, 'time', position)
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def turn_on(self):
|
||||||
|
""" turn the media player on. """
|
||||||
|
self._lms.query(self._id, 'power', '1')
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def play_youtube(self, media_id):
|
||||||
|
""" Plays a YouTube media. """
|
||||||
|
raise NotImplementedError()
|
@ -5,6 +5,11 @@ Modbus component, using pymodbus (python3 branch)
|
|||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
|
To use the Forecast sensor you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
To use the Modbus component you will need to add something like the following
|
To use the Modbus component you will need to add something like the following
|
||||||
to your config/configuration.yaml
|
to your config/configuration.yaml
|
||||||
|
|
||||||
@ -33,6 +38,8 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
|||||||
DOMAIN = "modbus"
|
DOMAIN = "modbus"
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
|
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' +
|
||||||
|
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip']
|
||||||
|
|
||||||
# Type of network
|
# Type of network
|
||||||
MEDIUM = "type"
|
MEDIUM = "type"
|
||||||
|
256
homeassistant/components/mqtt.py
Normal file
256
homeassistant/components/mqtt.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.mqtt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
MQTT component, using paho-mqtt. This component needs a MQTT broker like
|
||||||
|
Mosquitto or Mosca. The Eclipse Foundation is running a public MQTT server
|
||||||
|
at iot.eclipse.org. If you prefer to use that one, keep in mind to adjust
|
||||||
|
the topic/client ID and that your messages are public.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use MQTT you will need to add something like the following to your
|
||||||
|
config/configuration.yaml.
|
||||||
|
|
||||||
|
mqtt:
|
||||||
|
broker: 127.0.0.1
|
||||||
|
|
||||||
|
Or, if you want more options:
|
||||||
|
|
||||||
|
mqtt:
|
||||||
|
broker: 127.0.0.1
|
||||||
|
port: 1883
|
||||||
|
client_id: home-assistant-1
|
||||||
|
keepalive: 60
|
||||||
|
username: your_username
|
||||||
|
password: your_secret_password
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
broker
|
||||||
|
*Required
|
||||||
|
This is the IP address of your MQTT broker, e.g. 192.168.1.32.
|
||||||
|
|
||||||
|
port
|
||||||
|
*Optional
|
||||||
|
The network port to connect to. Default is 1883.
|
||||||
|
|
||||||
|
client_id
|
||||||
|
*Optional
|
||||||
|
Client ID that Home Assistant will use. Has to be unique on the server.
|
||||||
|
Default is a random generated one.
|
||||||
|
|
||||||
|
keepalive
|
||||||
|
*Optional
|
||||||
|
The keep alive in seconds for this client. Default is 60.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
import homeassistant.util as util
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.const import (
|
||||||
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = "mqtt"
|
||||||
|
|
||||||
|
MQTT_CLIENT = None
|
||||||
|
|
||||||
|
DEFAULT_PORT = 1883
|
||||||
|
DEFAULT_KEEPALIVE = 60
|
||||||
|
DEFAULT_QOS = 0
|
||||||
|
|
||||||
|
SERVICE_PUBLISH = 'publish'
|
||||||
|
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
|
||||||
|
|
||||||
|
DEPENDENCIES = []
|
||||||
|
REQUIREMENTS = ['paho-mqtt==1.1']
|
||||||
|
|
||||||
|
CONF_BROKER = 'broker'
|
||||||
|
CONF_PORT = 'port'
|
||||||
|
CONF_CLIENT_ID = 'client_id'
|
||||||
|
CONF_KEEPALIVE = 'keepalive'
|
||||||
|
CONF_USERNAME = 'username'
|
||||||
|
CONF_PASSWORD = 'password'
|
||||||
|
|
||||||
|
ATTR_QOS = 'qos'
|
||||||
|
ATTR_TOPIC = 'topic'
|
||||||
|
ATTR_PAYLOAD = 'payload'
|
||||||
|
|
||||||
|
|
||||||
|
def publish(hass, topic, payload):
|
||||||
|
""" Send an MQTT message. """
|
||||||
|
data = {
|
||||||
|
ATTR_TOPIC: topic,
|
||||||
|
ATTR_PAYLOAD: payload,
|
||||||
|
}
|
||||||
|
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
|
||||||
|
|
||||||
|
|
||||||
|
def subscribe(hass, topic, callback, qos=0):
|
||||||
|
""" Subscribe to a topic. """
|
||||||
|
def mqtt_topic_subscriber(event):
|
||||||
|
""" Match subscribed MQTT topic. """
|
||||||
|
if _match_topic(topic, event.data[ATTR_TOPIC]):
|
||||||
|
callback(event.data[ATTR_TOPIC], event.data[ATTR_PAYLOAD],
|
||||||
|
event.data[ATTR_QOS])
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_MQTT_MESSAGE_RECEIVED, mqtt_topic_subscriber)
|
||||||
|
|
||||||
|
if topic not in MQTT_CLIENT.topics:
|
||||||
|
MQTT_CLIENT.subscribe(topic, qos)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Get the MQTT protocol service. """
|
||||||
|
|
||||||
|
if not validate_config(config, {DOMAIN: ['broker']}, _LOGGER):
|
||||||
|
return False
|
||||||
|
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
|
||||||
|
broker = conf[CONF_BROKER]
|
||||||
|
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
||||||
|
client_id = util.convert(conf.get(CONF_CLIENT_ID), str)
|
||||||
|
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
|
||||||
|
username = util.convert(conf.get(CONF_USERNAME), str)
|
||||||
|
password = util.convert(conf.get(CONF_PASSWORD), str)
|
||||||
|
|
||||||
|
global MQTT_CLIENT
|
||||||
|
try:
|
||||||
|
MQTT_CLIENT = MQTT(hass, broker, port, client_id, keepalive, username,
|
||||||
|
password)
|
||||||
|
except socket.error:
|
||||||
|
_LOGGER.exception("Can't connect to the broker. "
|
||||||
|
"Please check your settings and the broker "
|
||||||
|
"itself.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def stop_mqtt(event):
|
||||||
|
""" Stop MQTT component. """
|
||||||
|
MQTT_CLIENT.stop()
|
||||||
|
|
||||||
|
def start_mqtt(event):
|
||||||
|
""" Launch MQTT component when Home Assistant starts up. """
|
||||||
|
MQTT_CLIENT.start()
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_mqtt)
|
||||||
|
|
||||||
|
def publish_service(call):
|
||||||
|
""" Handle MQTT publish service calls. """
|
||||||
|
msg_topic = call.data.get(ATTR_TOPIC)
|
||||||
|
payload = call.data.get(ATTR_PAYLOAD)
|
||||||
|
if msg_topic is None or payload is None:
|
||||||
|
return
|
||||||
|
MQTT_CLIENT.publish(msg_topic, payload)
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
|
||||||
|
|
||||||
|
hass.services.register(DOMAIN, SERVICE_PUBLISH, publish_service)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# This is based on one of the paho-mqtt examples:
|
||||||
|
# http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.python.git/tree/examples/sub-class.py
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
class MQTT(object): # pragma: no cover
|
||||||
|
""" Implements messaging service for MQTT. """
|
||||||
|
def __init__(self, hass, broker, port, client_id, keepalive, username,
|
||||||
|
password):
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
|
self.hass = hass
|
||||||
|
self._progress = {}
|
||||||
|
self.topics = {}
|
||||||
|
|
||||||
|
if client_id is None:
|
||||||
|
self._mqttc = mqtt.Client()
|
||||||
|
else:
|
||||||
|
self._mqttc = mqtt.Client(client_id)
|
||||||
|
if username is not None:
|
||||||
|
self._mqttc.username_pw_set(username, password)
|
||||||
|
self._mqttc.on_subscribe = self._mqtt_on_subscribe
|
||||||
|
self._mqttc.on_unsubscribe = self._mqtt_on_unsubscribe
|
||||||
|
self._mqttc.on_connect = self._mqtt_on_connect
|
||||||
|
self._mqttc.on_message = self._mqtt_on_message
|
||||||
|
self._mqttc.connect(broker, port, keepalive)
|
||||||
|
|
||||||
|
def publish(self, topic, payload):
|
||||||
|
""" Publish a MQTT message. """
|
||||||
|
self._mqttc.publish(topic, payload)
|
||||||
|
|
||||||
|
def unsubscribe(self, topic):
|
||||||
|
""" Unsubscribe from topic. """
|
||||||
|
result, mid = self._mqttc.unsubscribe(topic)
|
||||||
|
_raise_on_error(result)
|
||||||
|
self._progress[mid] = topic
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
""" Run the MQTT client. """
|
||||||
|
self._mqttc.loop_start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
""" Stop the MQTT client. """
|
||||||
|
self._mqttc.loop_stop()
|
||||||
|
|
||||||
|
def subscribe(self, topic, qos):
|
||||||
|
""" Subscribe to a topic. """
|
||||||
|
if topic in self.topics:
|
||||||
|
return
|
||||||
|
result, mid = self._mqttc.subscribe(topic, qos)
|
||||||
|
_raise_on_error(result)
|
||||||
|
self._progress[mid] = topic
|
||||||
|
self.topics[topic] = None
|
||||||
|
|
||||||
|
def _mqtt_on_connect(self, mqttc, obj, flags, result_code):
|
||||||
|
""" On connect, resubscribe to all topics we were subscribed to. """
|
||||||
|
old_topics = self.topics
|
||||||
|
self._progress = {}
|
||||||
|
self.topics = {}
|
||||||
|
for topic, qos in old_topics.items():
|
||||||
|
# qos is None if we were in process of subscribing
|
||||||
|
if qos is not None:
|
||||||
|
self._mqttc.subscribe(topic, qos)
|
||||||
|
|
||||||
|
def _mqtt_on_subscribe(self, mqttc, obj, mid, granted_qos):
|
||||||
|
""" Called when subscribe succesfull. """
|
||||||
|
topic = self._progress.pop(mid, None)
|
||||||
|
if topic is None:
|
||||||
|
return
|
||||||
|
self.topics[topic] = granted_qos
|
||||||
|
|
||||||
|
def _mqtt_on_unsubscribe(self, mqttc, obj, mid, granted_qos):
|
||||||
|
""" Called when subscribe succesfull. """
|
||||||
|
topic = self._progress.pop(mid, None)
|
||||||
|
if topic is None:
|
||||||
|
return
|
||||||
|
self.topics.pop(topic, None)
|
||||||
|
|
||||||
|
def _mqtt_on_message(self, mqttc, obj, msg):
|
||||||
|
""" Message callback """
|
||||||
|
self.hass.bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, {
|
||||||
|
ATTR_TOPIC: msg.topic,
|
||||||
|
ATTR_QOS: msg.qos,
|
||||||
|
ATTR_PAYLOAD: msg.payload.decode('utf-8'),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _raise_on_error(result): # pragma: no cover
|
||||||
|
""" Raise error if error result. """
|
||||||
|
if result != 0:
|
||||||
|
raise HomeAssistantError('Error talking to MQTT: {}'.format(result))
|
||||||
|
|
||||||
|
|
||||||
|
def _match_topic(subscription, topic):
|
||||||
|
""" Returns if topic matches subscription. """
|
||||||
|
if subscription.endswith('#'):
|
||||||
|
return (subscription[:-2] == topic or
|
||||||
|
topic.startswith(subscription[:-1]))
|
||||||
|
|
||||||
|
sub_parts = subscription.split('/')
|
||||||
|
topic_parts = topic.split('/')
|
||||||
|
|
||||||
|
return (len(sub_parts) == len(topic_parts) and
|
||||||
|
all(a == b for a, b in zip(sub_parts, topic_parts) if a != '+'))
|
@ -4,12 +4,13 @@ homeassistant.components.notify
|
|||||||
|
|
||||||
Provides functionality to notify people.
|
Provides functionality to notify people.
|
||||||
"""
|
"""
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import config_per_platform
|
||||||
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_NAME
|
||||||
|
|
||||||
DOMAIN = "notify"
|
DOMAIN = "notify"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -33,29 +34,28 @@ def send_message(hass, message):
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Sets up notify services. """
|
""" Sets up notify services. """
|
||||||
|
success = False
|
||||||
|
|
||||||
if not validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER):
|
for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER):
|
||||||
return False
|
# get platform
|
||||||
|
|
||||||
platform = config[DOMAIN].get(CONF_PLATFORM)
|
|
||||||
|
|
||||||
notify_implementation = get_component(
|
notify_implementation = get_component(
|
||||||
'notify.{}'.format(platform))
|
'notify.{}'.format(platform))
|
||||||
|
|
||||||
if notify_implementation is None:
|
if notify_implementation is None:
|
||||||
_LOGGER.error("Unknown notification service specified.")
|
_LOGGER.error("Unknown notification service specified.")
|
||||||
|
continue
|
||||||
|
|
||||||
return False
|
# create platform service
|
||||||
|
notify_service = notify_implementation.get_service(
|
||||||
notify_service = notify_implementation.get_service(hass, config)
|
hass, {DOMAIN: p_config})
|
||||||
|
|
||||||
if notify_service is None:
|
if notify_service is None:
|
||||||
_LOGGER.error("Failed to initialize notification service %s",
|
_LOGGER.error("Failed to initialize notification service %s",
|
||||||
platform)
|
platform)
|
||||||
|
continue
|
||||||
|
|
||||||
return False
|
# create service handler
|
||||||
|
def notify_message(notify_service, call):
|
||||||
def notify_message(call):
|
|
||||||
""" Handle sending notification message service calls. """
|
""" Handle sending notification message service calls. """
|
||||||
message = call.data.get(ATTR_MESSAGE)
|
message = call.data.get(ATTR_MESSAGE)
|
||||||
|
|
||||||
@ -66,9 +66,13 @@ def setup(hass, config):
|
|||||||
|
|
||||||
notify_service.send_message(message, title=title)
|
notify_service.send_message(message, title=title)
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_NOTIFY, notify_message)
|
# register service
|
||||||
|
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)
|
||||||
|
success = True
|
||||||
|
|
||||||
return True
|
return success
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
@ -28,7 +28,7 @@ from homeassistant.components.notify import (
|
|||||||
from homeassistant.const import CONF_API_KEY
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['pushbullet.py>=0.7.1']
|
REQUIREMENTS = ['pushbullet.py==0.7.1']
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
|
@ -42,7 +42,7 @@ from homeassistant.components.notify import (
|
|||||||
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||||
from homeassistant.const import CONF_API_KEY
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
REQUIREMENTS = ['python-pushover>=0.2']
|
REQUIREMENTS = ['python-pushover==0.2']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
92
homeassistant/components/notify/slack.py
Normal file
92
homeassistant/components/notify/slack.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.notify.slack
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Slack platform for notify component.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Slack notifier you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
notify:
|
||||||
|
platform: slack
|
||||||
|
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
||||||
|
default_channel: '#general'
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
api_key
|
||||||
|
*Required
|
||||||
|
The slack API token to use for sending slack messages.
|
||||||
|
You can get your slack API token here https://api.slack.com/web?sudo=1
|
||||||
|
|
||||||
|
default_channel
|
||||||
|
*Required
|
||||||
|
The default channel to post to if no channel is explicitly specified when
|
||||||
|
sending the notification message.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.notify import (
|
||||||
|
DOMAIN, BaseNotificationService)
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
|
REQUIREMENTS = ['slacker==0.6.8']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-variable
|
||||||
|
def get_service(hass, config):
|
||||||
|
""" Get the slack notification service. """
|
||||||
|
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: ['default_channel', CONF_API_KEY]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-name-in-module, unused-variable
|
||||||
|
from slacker import Error as SlackError
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import slacker. "
|
||||||
|
"Did you maybe not install the 'slacker.py' package?")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
api_token = config[DOMAIN].get(CONF_API_KEY)
|
||||||
|
|
||||||
|
return SlackNotificationService(
|
||||||
|
config[DOMAIN]['default_channel'],
|
||||||
|
api_token)
|
||||||
|
|
||||||
|
except SlackError as ex:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Slack authentication failed")
|
||||||
|
_LOGGER.exception(ex)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class SlackNotificationService(BaseNotificationService):
|
||||||
|
""" Implements notification service for Slack. """
|
||||||
|
|
||||||
|
def __init__(self, default_channel, api_token):
|
||||||
|
from slacker import Slacker
|
||||||
|
self._default_channel = default_channel
|
||||||
|
self._api_token = api_token
|
||||||
|
self.slack = Slacker(self._api_token)
|
||||||
|
self.slack.auth.test()
|
||||||
|
|
||||||
|
def send_message(self, message="", **kwargs):
|
||||||
|
""" Send a message to a user. """
|
||||||
|
|
||||||
|
from slacker import Error as SlackError
|
||||||
|
channel = kwargs.get('channel', self._default_channel)
|
||||||
|
try:
|
||||||
|
self.slack.chat.post_message(channel, message)
|
||||||
|
except SlackError as ex:
|
||||||
|
_LOGGER.exception("Could not send slack notification")
|
||||||
|
_LOGGER.exception(ex)
|
@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.notify.xmpp
|
homeassistant.components.notify.xmpp
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Jabber (XMPP) notification service.
|
Jabber (XMPP) notification service.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
@ -29,7 +28,6 @@ The password for your given Jabber account.
|
|||||||
recipient
|
recipient
|
||||||
*Required
|
*Required
|
||||||
The Jabber ID (JID) that will receive the messages.
|
The Jabber ID (JID) that will receive the messages.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -47,7 +45,7 @@ from homeassistant.helpers import validate_config
|
|||||||
from homeassistant.components.notify import (
|
from homeassistant.components.notify import (
|
||||||
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||||
|
|
||||||
REQUIREMENTS = ['sleekxmpp>=1.3.1']
|
REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0']
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
"""
|
|
||||||
homeassistant.components.process
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Provides functionality to watch for specific processes running
|
|
||||||
on the host machine.
|
|
||||||
|
|
||||||
Author: Markus Stenberg <fingon@iki.fi>
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
|
||||||
import homeassistant.util as util
|
|
||||||
|
|
||||||
DOMAIN = 'process'
|
|
||||||
DEPENDENCIES = []
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
|
||||||
|
|
||||||
PS_STRING = 'ps awx'
|
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
|
||||||
""" Sets up a check if specified processes are running.
|
|
||||||
|
|
||||||
processes: dict mapping entity id to substring to search for
|
|
||||||
in process list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Deprecated as of 3/7/2015
|
|
||||||
logging.getLogger(__name__).warning(
|
|
||||||
"This component has been deprecated and will be removed in the future."
|
|
||||||
" Please use sensor.systemmonitor with the process type")
|
|
||||||
|
|
||||||
entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring
|
|
||||||
for pname, pstring in config[DOMAIN].items()}
|
|
||||||
|
|
||||||
def update_process_states(time):
|
|
||||||
""" Check ps for currently running processes and update states. """
|
|
||||||
with os.popen(PS_STRING, 'r') as psfile:
|
|
||||||
lines = list(psfile)
|
|
||||||
|
|
||||||
for entity_id, pstring in entities.items():
|
|
||||||
state = STATE_ON if any(pstring in l for l in lines) else STATE_OFF
|
|
||||||
|
|
||||||
hass.states.set(entity_id, state)
|
|
||||||
|
|
||||||
update_process_states(None)
|
|
||||||
|
|
||||||
hass.track_time_change(update_process_states, second=[0, 30])
|
|
||||||
|
|
||||||
return True
|
|
@ -13,7 +13,7 @@ from datetime import datetime, date
|
|||||||
import json
|
import json
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
from homeassistant import Event, EventOrigin, State
|
from homeassistant.core import Event, EventOrigin, State
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
from homeassistant.remote import JSONEncoder
|
from homeassistant.remote import JSONEncoder
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -18,7 +18,8 @@ old state will not be restored when it is being deactivated.
|
|||||||
import logging
|
import logging
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from homeassistant import State
|
from homeassistant.core import State
|
||||||
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.state import reproduce_state
|
from homeassistant.helpers.state import reproduce_state
|
||||||
@ -104,8 +105,8 @@ class Scene(ToggleEntity):
|
|||||||
self.prev_states = None
|
self.prev_states = None
|
||||||
self.ignore_updates = False
|
self.ignore_updates = False
|
||||||
|
|
||||||
self.hass.states.track_change(
|
track_state_change(
|
||||||
self.entity_ids, self.entity_state_changed)
|
self.hass, self.entity_ids, self.entity_state_changed)
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ class ServiceEventListener(EventListener):
|
|||||||
def execute(self, hass):
|
def execute(self, hass):
|
||||||
""" Call the service. """
|
""" Call the service. """
|
||||||
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
||||||
hass.call_service(self.domain, self.service, data)
|
hass.services.call(self.domain, self.service, data)
|
||||||
|
|
||||||
# Reschedule for next day
|
# Reschedule for next day
|
||||||
self.schedule(hass)
|
self.schedule(hass)
|
||||||
|
@ -17,6 +17,7 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.helpers.event import track_point_in_time
|
||||||
from homeassistant.components.scheduler import ServiceEventListener
|
from homeassistant.components.scheduler import ServiceEventListener
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -62,7 +63,7 @@ class TimeEventListener(ServiceEventListener):
|
|||||||
""" Call the execute method """
|
""" Call the execute method """
|
||||||
self.execute(hass)
|
self.execute(hass)
|
||||||
|
|
||||||
hass.track_point_in_time(execute, next_time)
|
track_point_in_time(hass, execute, next_time)
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
'TimeEventListener scheduled for %s, will call service %s.%s',
|
'TimeEventListener scheduled for %s, will call service %s.%s',
|
||||||
|
@ -10,6 +10,7 @@ from datetime import timedelta
|
|||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from homeassistant.helpers.event import track_point_in_time
|
||||||
from homeassistant.util import split_entity_id
|
from homeassistant.util import split_entity_id
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, EVENT_TIME_CHANGED)
|
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, EVENT_TIME_CHANGED)
|
||||||
@ -111,8 +112,8 @@ class Script(object):
|
|||||||
elif CONF_DELAY in action:
|
elif CONF_DELAY in action:
|
||||||
delay = timedelta(**action[CONF_DELAY])
|
delay = timedelta(**action[CONF_DELAY])
|
||||||
point_in_time = date_util.now() + delay
|
point_in_time = date_util.now() + delay
|
||||||
self.listener = self.hass.track_point_in_time(
|
self.listener = track_point_in_time(
|
||||||
self, point_in_time)
|
self.hass, self, point_in_time)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ Component to interface with various sensors that can be monitored.
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.components import wink, zwave, isy994
|
from homeassistant.components import wink, zwave, isy994, verisure
|
||||||
|
|
||||||
DOMAIN = 'sensor'
|
DOMAIN = 'sensor'
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -18,7 +18,8 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
|||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
wink.DISCOVER_SENSORS: 'wink',
|
wink.DISCOVER_SENSORS: 'wink',
|
||||||
zwave.DISCOVER_SENSORS: 'zwave',
|
zwave.DISCOVER_SENSORS: 'zwave',
|
||||||
isy994.DISCOVER_SENSORS: 'isy994'
|
isy994.DISCOVER_SENSORS: 'isy994',
|
||||||
|
verisure.DISCOVER_SENSORS: 'verisure'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ from homeassistant.util import Throttle
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ['blockchain>=1.1.2']
|
REQUIREMENTS = ['blockchain==1.1.2']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
OPTION_TYPES = {
|
OPTION_TYPES = {
|
||||||
'wallet': ['Wallet balance', 'BTC'],
|
'wallet': ['Wallet balance', 'BTC'],
|
||||||
|
@ -15,6 +15,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices([
|
add_devices([
|
||||||
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12),
|
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12),
|
||||||
DemoSensor('Outside Humidity', 54, '%', None),
|
DemoSensor('Outside Humidity', 54, '%', None),
|
||||||
|
DemoSensor('Alarm back', 'Armed', None, None),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
164
homeassistant/components/sensor/dht.py
Normal file
164
homeassistant/components/sensor/dht.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.dht
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Adafruit DHT temperature and humidity sensor.
|
||||||
|
You need a Python3 compatible version of the Adafruit_Python_DHT library
|
||||||
|
(e.g. https://github.com/mala-zaba/Adafruit_Python_DHT,
|
||||||
|
also see requirements.txt).
|
||||||
|
As this requires access to the GPIO, you will need to run home-assistant
|
||||||
|
as root.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Adafruit DHT sensor you will need to
|
||||||
|
add something like the following to your config/configuration.yaml:
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: dht
|
||||||
|
sensor: DHT22
|
||||||
|
pin: 23
|
||||||
|
monitored_conditions:
|
||||||
|
- temperature
|
||||||
|
- humidity
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
sensor
|
||||||
|
*Required
|
||||||
|
The sensor type, DHT11, DHT22 or AM2302
|
||||||
|
|
||||||
|
pin
|
||||||
|
*Required
|
||||||
|
The pin the sensor is connected to, something like
|
||||||
|
'P8_11' for Beaglebone, '23' for Raspberry Pi
|
||||||
|
|
||||||
|
monitored_conditions
|
||||||
|
*Optional
|
||||||
|
Conditions to monitor. Available conditions are temperature and humidity.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.const import TEMP_FAHRENHEIT
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
# update this requirement to upstream as soon as it supports python3
|
||||||
|
REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/' +
|
||||||
|
'4101340de8d2457dd194bca1e8d11cbfc237e919.zip']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
SENSOR_TYPES = {
|
||||||
|
'temperature': ['Temperature', ''],
|
||||||
|
'humidity': ['Humidity', '%']
|
||||||
|
}
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
# DHT11 is able to deliver data once per second, DHT22 once every two
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Get the DHT sensor. """
|
||||||
|
|
||||||
|
try:
|
||||||
|
import Adafruit_DHT
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import Adafruit_DHT. "
|
||||||
|
"Did you maybe not install the 'Adafruit_DHT' package?")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
|
||||||
|
unit = hass.config.temperature_unit
|
||||||
|
available_sensors = {
|
||||||
|
"DHT11": Adafruit_DHT.DHT11,
|
||||||
|
"DHT22": Adafruit_DHT.DHT22,
|
||||||
|
"AM2302": Adafruit_DHT.AM2302
|
||||||
|
}
|
||||||
|
sensor = available_sensors[config['sensor']]
|
||||||
|
|
||||||
|
pin = config['pin']
|
||||||
|
|
||||||
|
if not sensor or not pin:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Config error "
|
||||||
|
"Please check your settings for DHT, sensor not supported.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = DHTClient(Adafruit_DHT, sensor, pin)
|
||||||
|
dev = []
|
||||||
|
try:
|
||||||
|
for variable in config['monitored_conditions']:
|
||||||
|
if variable not in SENSOR_TYPES:
|
||||||
|
_LOGGER.error('Sensor type: "%s" does not exist', variable)
|
||||||
|
else:
|
||||||
|
dev.append(DHTSensor(data, variable, unit))
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
add_devices(dev)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class DHTSensor(Entity):
|
||||||
|
|
||||||
|
""" Implements an DHT sensor. """
|
||||||
|
|
||||||
|
def __init__(self, dht_client, sensor_type, temp_unit):
|
||||||
|
self.client_name = 'DHT sensor'
|
||||||
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
|
self.dht_client = dht_client
|
||||||
|
self.temp_unit = temp_unit
|
||||||
|
self.type = sensor_type
|
||||||
|
self._state = None
|
||||||
|
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return '{} {}'.format(self.client_name, self._name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement of this entity, if any. """
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data from the DHT and updates the states. """
|
||||||
|
|
||||||
|
self.dht_client.update()
|
||||||
|
data = self.dht_client.data
|
||||||
|
|
||||||
|
if self.type == 'temperature':
|
||||||
|
self._state = round(data['temperature'], 1)
|
||||||
|
if self.temp_unit == TEMP_FAHRENHEIT:
|
||||||
|
self._state = round(data['temperature'] * 1.8 + 32, 1)
|
||||||
|
elif self.type == 'humidity':
|
||||||
|
self._state = round(data['humidity'], 1)
|
||||||
|
|
||||||
|
|
||||||
|
class DHTClient(object):
|
||||||
|
|
||||||
|
""" Gets the latest data from the DHT sensor. """
|
||||||
|
|
||||||
|
def __init__(self, adafruit_dht, sensor, pin):
|
||||||
|
self.adafruit_dht = adafruit_dht
|
||||||
|
self.sensor = sensor
|
||||||
|
self.pin = pin
|
||||||
|
self.data = dict()
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data the DHT sensor. """
|
||||||
|
humidity, temperature = self.adafruit_dht.read_retry(self.sensor,
|
||||||
|
self.pin)
|
||||||
|
if temperature:
|
||||||
|
self.data['temperature'] = temperature
|
||||||
|
if humidity:
|
||||||
|
self.data['humidity'] = humidity
|
@ -1,14 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.efergy
|
homeassistant.components.sensor.efergy
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Monitors home energy use as measured by an efergy engage hub using its
|
||||||
Monitors home energy use as measured by an efergy
|
(unofficial, undocumented) API.
|
||||||
engage hub using its (unofficial, undocumented) API.
|
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
To use the efergy sensor you will need to add something
|
To use the efergy sensor you will need to add something like the following
|
||||||
like the following to your config/configuration.yaml
|
to your config/configuration.yaml
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
platform: efergy
|
platform: efergy
|
||||||
@ -39,8 +38,8 @@ An array specifying the variables to monitor.
|
|||||||
|
|
||||||
period
|
period
|
||||||
*Optional
|
*Optional
|
||||||
Some variables take a period argument. Valid options are "day",
|
Some variables take a period argument. Valid options are "day", "week",
|
||||||
1"week", "month", and "year"
|
"month", and "year".
|
||||||
|
|
||||||
currency
|
currency
|
||||||
*Optional
|
*Optional
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.forecast
|
homeassistant.components.sensor.forecast
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Forecast.io service.
|
Forecast.io service.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
@ -50,6 +49,8 @@ Details for the API : https://developer.forecast.io/docs/v2
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
REQUIREMENTS = ['python-forecastio==1.3.3']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import forecastio
|
import forecastio
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -121,10 +122,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class ForeCastSensor(Entity):
|
class ForeCastSensor(Entity):
|
||||||
""" Implements an OpenWeatherMap sensor. """
|
""" Implements an Forecast.io sensor. """
|
||||||
|
|
||||||
def __init__(self, weather_data, sensor_type, unit):
|
def __init__(self, weather_data, sensor_type, unit):
|
||||||
self.client_name = 'Forecast'
|
self.client_name = 'Weather'
|
||||||
self._name = SENSOR_TYPES[sensor_type][0]
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
self.forecast_client = weather_data
|
self.forecast_client = weather_data
|
||||||
self._unit = unit
|
self._unit = unit
|
||||||
@ -135,7 +136,7 @@ class ForeCastSensor(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return '{} - {}'.format(self.client_name, self._name)
|
return '{} {}'.format(self.client_name, self._name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -157,10 +158,6 @@ class ForeCastSensor(Entity):
|
|||||||
try:
|
try:
|
||||||
if self.type == 'summary':
|
if self.type == 'summary':
|
||||||
self._state = data.summary
|
self._state = data.summary
|
||||||
# elif self.type == 'sunrise_time':
|
|
||||||
# self._state = data.sunriseTime
|
|
||||||
# elif self.type == 'sunset_time':
|
|
||||||
# self._state = data.sunsetTime
|
|
||||||
elif self.type == 'precip_intensity':
|
elif self.type == 'precip_intensity':
|
||||||
if data.precipIntensity == 0:
|
if data.precipIntensity == 0:
|
||||||
self._state = 'None'
|
self._state = 'None'
|
||||||
@ -220,5 +217,6 @@ class ForeCastData(object):
|
|||||||
|
|
||||||
forecast = forecastio.load_forecast(self._api_key,
|
forecast = forecastio.load_forecast(self._api_key,
|
||||||
self.latitude,
|
self.latitude,
|
||||||
self.longitude)
|
self.longitude,
|
||||||
|
units='si')
|
||||||
self.data = forecast.currently()
|
self.data = forecast.currently()
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.modbus
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Support for Modbus sensors.
|
Support for Modbus sensors.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
@ -18,16 +20,32 @@ sensor:
|
|||||||
name: My boolean sensor
|
name: My boolean sensor
|
||||||
2:
|
2:
|
||||||
name: My other boolean sensor
|
name: My other boolean sensor
|
||||||
|
coils:
|
||||||
|
0:
|
||||||
|
name: My coil switch
|
||||||
|
|
||||||
VARIABLES:
|
Variables:
|
||||||
|
|
||||||
- "slave" = slave number (ignored and can be omitted if not serial Modbus)
|
slave
|
||||||
- "unit" = unit to attach to value (optional, ignored for boolean sensors)
|
*Required
|
||||||
- "registers" contains a list of relevant registers to read from
|
Slave number (ignored and can be omitted if not serial Modbus).
|
||||||
it can contain a "bits" section, listing relevant bits
|
|
||||||
|
|
||||||
- each named register will create an integer sensor
|
unit
|
||||||
- each named bit will create a boolean sensor
|
*Required
|
||||||
|
Unit to attach to value (optional, ignored for boolean sensors).
|
||||||
|
|
||||||
|
registers
|
||||||
|
*Required
|
||||||
|
Contains a list of relevant registers to read from. It can contain a
|
||||||
|
"bits" section, listing relevant bits.
|
||||||
|
|
||||||
|
coils
|
||||||
|
*Optional
|
||||||
|
Contains a list of relevant coils to read from.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- Each named register will create an integer sensor.
|
||||||
|
- Each named bit will create a boolean sensor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -39,6 +57,7 @@ from homeassistant.const import (
|
|||||||
STATE_ON, STATE_OFF)
|
STATE_ON, STATE_OFF)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
DEPENDENCIES = ['modbus']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -49,6 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
_LOGGER.error("No slave number provided for serial Modbus")
|
_LOGGER.error("No slave number provided for serial Modbus")
|
||||||
return False
|
return False
|
||||||
registers = config.get("registers")
|
registers = config.get("registers")
|
||||||
|
if registers:
|
||||||
for regnum, register in registers.items():
|
for regnum, register in registers.items():
|
||||||
if register.get("name"):
|
if register.get("name"):
|
||||||
sensors.append(ModbusSensor(register.get("name"),
|
sensors.append(ModbusSensor(register.get("name"),
|
||||||
@ -64,6 +84,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
slave,
|
slave,
|
||||||
regnum,
|
regnum,
|
||||||
bitnum))
|
bitnum))
|
||||||
|
coils = config.get("coils")
|
||||||
|
if coils:
|
||||||
|
for coilnum, coil in coils.items():
|
||||||
|
sensors.append(ModbusSensor(coil.get("name"),
|
||||||
|
slave,
|
||||||
|
coilnum,
|
||||||
|
coil=True))
|
||||||
|
|
||||||
add_devices(sensors)
|
add_devices(sensors)
|
||||||
|
|
||||||
|
|
||||||
@ -71,13 +99,14 @@ class ModbusSensor(Entity):
|
|||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
""" Represents a Modbus Sensor """
|
""" Represents a Modbus Sensor """
|
||||||
|
|
||||||
def __init__(self, name, slave, register, bit=None, unit=None):
|
def __init__(self, name, slave, register, bit=None, unit=None, coil=False):
|
||||||
self._name = name
|
self._name = name
|
||||||
self.slave = int(slave) if slave else 1
|
self.slave = int(slave) if slave else 1
|
||||||
self.register = int(register)
|
self.register = int(register)
|
||||||
self.bit = int(bit) if bit else None
|
self.bit = int(bit) if bit else None
|
||||||
self._value = None
|
self._value = None
|
||||||
self._unit = unit
|
self._unit = unit
|
||||||
|
self._coil = coil
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: %s" % (self.name, self.state)
|
return "%s: %s" % (self.name, self.state)
|
||||||
@ -118,14 +147,14 @@ class ModbusSensor(Entity):
|
|||||||
else:
|
else:
|
||||||
return self._unit
|
return self._unit
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
attr = super().state_attributes
|
|
||||||
return attr
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
result = modbus.NETWORK.read_holding_registers(unit=self.slave,
|
""" Update the state of the sensor. """
|
||||||
address=self.register,
|
if self._coil:
|
||||||
|
result = modbus.NETWORK.read_coils(self.register, 1)
|
||||||
|
self._value = result.bits[0]
|
||||||
|
else:
|
||||||
|
result = modbus.NETWORK.read_holding_registers(
|
||||||
|
unit=self.slave, address=self.register,
|
||||||
count=1)
|
count=1)
|
||||||
val = 0
|
val = 0
|
||||||
for i, res in enumerate(result.registers):
|
for i, res in enumerate(result.registers):
|
||||||
|
94
homeassistant/components/sensor/mqtt.py
Normal file
94
homeassistant/components/sensor/mqtt.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
homeassistant.components.sensor.mqtt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure a MQTT sensor.
|
||||||
|
|
||||||
|
This generic sensor implementation uses the MQTT message payload
|
||||||
|
as the sensor value. If messages in this state_topic are published
|
||||||
|
with RETAIN flag, the sensor will receive an instant update with
|
||||||
|
last known value. Otherwise, the initial state will be undefined.
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: mqtt
|
||||||
|
name: "MQTT Sensor"
|
||||||
|
state_topic: "home/bedroom/temperature"
|
||||||
|
unit_of_measurement: "ºC"
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
name
|
||||||
|
*Optional
|
||||||
|
The name of the sensor. Default is 'MQTT Sensor'.
|
||||||
|
|
||||||
|
state_topic
|
||||||
|
*Required
|
||||||
|
The MQTT topic subscribed to receive sensor values.
|
||||||
|
|
||||||
|
unit_of_measurement
|
||||||
|
*Optional
|
||||||
|
Defines the units of measurement of the sensor, if any.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
import homeassistant.components.mqtt as mqtt
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_NAME = "MQTT Sensor"
|
||||||
|
|
||||||
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Add MQTT Sensor """
|
||||||
|
|
||||||
|
if config.get('state_topic') is None:
|
||||||
|
_LOGGER.error("Missing required variable: state_topic")
|
||||||
|
return False
|
||||||
|
|
||||||
|
add_devices_callback([MqttSensor(
|
||||||
|
hass,
|
||||||
|
config.get('name', DEFAULT_NAME),
|
||||||
|
config.get('state_topic'),
|
||||||
|
config.get('unit_of_measurement'))])
|
||||||
|
|
||||||
|
|
||||||
|
class MqttSensor(Entity):
|
||||||
|
""" Represents a sensor that can be updated using MQTT """
|
||||||
|
def __init__(self, hass, name, state_topic, unit_of_measurement):
|
||||||
|
self._state = "-"
|
||||||
|
self._hass = hass
|
||||||
|
self._name = name
|
||||||
|
self._state_topic = state_topic
|
||||||
|
self._unit_of_measurement = unit_of_measurement
|
||||||
|
|
||||||
|
def message_received(topic, payload, qos):
|
||||||
|
""" A new MQTT message has been received. """
|
||||||
|
self._state = payload
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
mqtt.subscribe(hass, self._state_topic, message_received)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the sensor """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit this state is expressed in. """
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the entity. """
|
||||||
|
return self._state
|
@ -36,15 +36,15 @@ ATTR_NODE_ID = "node_id"
|
|||||||
ATTR_CHILD_ID = "child_id"
|
ATTR_CHILD_ID = "child_id"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/' +
|
||||||
'#egg=pymysensors-0.1']
|
'35b87d880147a34107da0d40cb815d75e6cb4af7.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Setup the mysensors platform. """
|
""" Setup the mysensors platform. """
|
||||||
|
|
||||||
import mysensors.mysensors as mysensors
|
import mysensors.mysensors as mysensors
|
||||||
import mysensors.const as const
|
import mysensors.const_14 as const
|
||||||
|
|
||||||
devices = {} # keep track of devices added to HA
|
devices = {} # keep track of devices added to HA
|
||||||
# Just assume celcius means that the user wants metric for now.
|
# Just assume celcius means that the user wants metric for now.
|
||||||
|
@ -48,7 +48,7 @@ from homeassistant.util import Throttle
|
|||||||
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['pywm>=2.2.1']
|
REQUIREMENTS = ['pyowm==2.2.1']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'weather': ['Condition', ''],
|
'weather': ['Condition', ''],
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.rfxtrx
|
homeassistant.components.sensor.rfxtrx
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Shows sensor values from rfxtrx sensors.
|
Shows sensor values from RFXtrx sensors.
|
||||||
|
|
||||||
Possible config keys:
|
Configuration:
|
||||||
device="path to rfxtrx device"
|
To use the rfxtrx sensors you will need to add something like the following to
|
||||||
|
your config/configuration.yaml
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
sensor 2:
|
sensor:
|
||||||
platform: rfxtrx
|
platform: rfxtrx
|
||||||
device : /dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0
|
device: PATH_TO_DEVICE
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
device
|
||||||
|
*Required
|
||||||
|
Path to your RFXtrx device.
|
||||||
|
E.g. /dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@ -19,8 +26,8 @@ from collections import OrderedDict
|
|||||||
from homeassistant.const import (TEMP_CELCIUS)
|
from homeassistant.const import (TEMP_CELCIUS)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' +
|
||||||
'#RFXtrx>=0.15']
|
'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip']
|
||||||
|
|
||||||
DATA_TYPES = OrderedDict([
|
DATA_TYPES = OrderedDict([
|
||||||
('Temperature', TEMP_CELCIUS),
|
('Temperature', TEMP_CELCIUS),
|
||||||
@ -31,7 +38,7 @@ DATA_TYPES = OrderedDict([
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Setup the rfxtrx platform. """
|
""" Setup the RFXtrx platform. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
sensors = {} # keep track of sensors added to HA
|
sensors = {} # keep track of sensors added to HA
|
||||||
@ -57,7 +64,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class RfxtrxSensor(Entity):
|
class RfxtrxSensor(Entity):
|
||||||
""" Represents a Rfxtrx Sensor. """
|
""" Represents a RFXtrx sensor. """
|
||||||
|
|
||||||
def __init__(self, event):
|
def __init__(self, event):
|
||||||
self.event = event
|
self.event = event
|
||||||
|
133
homeassistant/components/sensor/rpi_gpio.py
Normal file
133
homeassistant/components/sensor/rpi_gpio.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
homeassistant.components.sensor.rpi_gpio
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure a binary state sensor using RPi GPIO.
|
||||||
|
Note: To use RPi GPIO, Home Assistant must be run as root.
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: rpi_gpio
|
||||||
|
pull_mode: "UP"
|
||||||
|
value_high: "Active"
|
||||||
|
value_low: "Inactive"
|
||||||
|
ports:
|
||||||
|
11: PIR Office
|
||||||
|
12: PIR Bedroom
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
pull_mode
|
||||||
|
*Optional
|
||||||
|
The internal pull to use (UP or DOWN). Default is UP.
|
||||||
|
|
||||||
|
value_high
|
||||||
|
*Optional
|
||||||
|
The value of the sensor when the port is HIGH. Default is "HIGH".
|
||||||
|
|
||||||
|
value_low
|
||||||
|
*Optional
|
||||||
|
The value of the sensor when the port is LOW. Default is "LOW".
|
||||||
|
|
||||||
|
bouncetime
|
||||||
|
*Optional
|
||||||
|
The time in milliseconds for port debouncing. Default is 50ms.
|
||||||
|
|
||||||
|
ports
|
||||||
|
*Required
|
||||||
|
An array specifying the GPIO ports to use and the name to use in the frontend.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
try:
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
except ImportError:
|
||||||
|
GPIO = None
|
||||||
|
from homeassistant.const import (DEVICE_DEFAULT_NAME,
|
||||||
|
EVENT_HOMEASSISTANT_START,
|
||||||
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
|
DEFAULT_PULL_MODE = "UP"
|
||||||
|
DEFAULT_VALUE_HIGH = "HIGH"
|
||||||
|
DEFAULT_VALUE_LOW = "LOW"
|
||||||
|
DEFAULT_BOUNCETIME = 50
|
||||||
|
|
||||||
|
REQUIREMENTS = ['RPi.GPIO==0.5.11']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Raspberry PI GPIO ports. """
|
||||||
|
if GPIO is None:
|
||||||
|
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
|
||||||
|
return
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
|
||||||
|
value_high = config.get('value_high', DEFAULT_VALUE_HIGH)
|
||||||
|
value_low = config.get('value_low', DEFAULT_VALUE_LOW)
|
||||||
|
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
|
||||||
|
ports = config.get('ports')
|
||||||
|
for port_num, port_name in ports.items():
|
||||||
|
sensors.append(RPiGPIOSensor(
|
||||||
|
port_name, port_num, pull_mode,
|
||||||
|
value_high, value_low, bouncetime))
|
||||||
|
add_devices(sensors)
|
||||||
|
|
||||||
|
def cleanup_gpio(event):
|
||||||
|
""" Stuff to do before stop home assistant. """
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
def prepare_gpio(event):
|
||||||
|
""" Stuff to do when home assistant starts. """
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
|
class RPiGPIOSensor(Entity):
|
||||||
|
""" Sets up the Raspberry PI GPIO ports. """
|
||||||
|
def __init__(self, port_name, port_num, pull_mode,
|
||||||
|
value_high, value_low, bouncetime):
|
||||||
|
# pylint: disable=no-member
|
||||||
|
self._name = port_name or DEVICE_DEFAULT_NAME
|
||||||
|
self._port = port_num
|
||||||
|
self._pull = GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP
|
||||||
|
self._vhigh = value_high
|
||||||
|
self._vlow = value_low
|
||||||
|
self._bouncetime = bouncetime
|
||||||
|
GPIO.setup(self._port, GPIO.IN, pull_up_down=self._pull)
|
||||||
|
self._state = self._vhigh if GPIO.input(self._port) else self._vlow
|
||||||
|
|
||||||
|
def edge_callback(channel):
|
||||||
|
""" port changed state """
|
||||||
|
# pylint: disable=no-member
|
||||||
|
self._state = self._vhigh if GPIO.input(channel) else self._vlow
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
GPIO.add_event_detect(
|
||||||
|
self._port,
|
||||||
|
GPIO.BOTH,
|
||||||
|
callback=edge_callback,
|
||||||
|
bouncetime=self._bouncetime)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the sensor """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the entity. """
|
||||||
|
return self._state
|
@ -66,7 +66,7 @@ import homeassistant.util.dt as dt_util
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
|
|
||||||
REQUIREMENTS = ['psutil>=3.0.0']
|
REQUIREMENTS = ['psutil==3.0.0']
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'disk_use_percent': ['Disk Use', '%'],
|
'disk_use_percent': ['Disk Use', '%'],
|
||||||
'disk_use': ['Disk Use', 'GiB'],
|
'disk_use': ['Disk Use', 'GiB'],
|
||||||
|
@ -35,6 +35,8 @@ import homeassistant.util as util
|
|||||||
|
|
||||||
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
|
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
|
||||||
|
|
||||||
|
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
71
homeassistant/components/sensor/temper.py
Normal file
71
homeassistant/components/sensor/temper.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.temper
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for getting temperature from TEMPer devices.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
To use the temper sensors you will need to add something like the following to
|
||||||
|
your config/configuration.yaml
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: temper
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/' +
|
||||||
|
'3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip']
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Find and return Temper sensors. """
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
from temperusb.temper import TemperHandler
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.error('Failed to import temperusb')
|
||||||
|
return False
|
||||||
|
|
||||||
|
temp_unit = hass.config.temperature_unit
|
||||||
|
name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME)
|
||||||
|
temper_devices = TemperHandler().get_devices()
|
||||||
|
add_devices_callback([TemperSensor(dev, temp_unit, name + '_' + str(idx))
|
||||||
|
for idx, dev in enumerate(temper_devices)])
|
||||||
|
|
||||||
|
|
||||||
|
class TemperSensor(Entity):
|
||||||
|
""" Represents an Temper temperature sensor. """
|
||||||
|
def __init__(self, temper_device, temp_unit, name):
|
||||||
|
self.temper_device = temper_device
|
||||||
|
self.temp_unit = temp_unit
|
||||||
|
self.current_value = None
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the temperature sensor. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the entity. """
|
||||||
|
return self.current_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement of this entity, if any. """
|
||||||
|
return self.temp_unit
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Retrieve latest state. """
|
||||||
|
try:
|
||||||
|
self.current_value = self.temper_device.get_temperature()
|
||||||
|
except IOError:
|
||||||
|
_LOGGER.error('Failed to get temperature due to insufficient '
|
||||||
|
'permissions. Try running with "sudo"')
|
@ -67,7 +67,7 @@ from transmissionrpc.error import TransmissionError
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
REQUIREMENTS = ['transmissionrpc>=0.11']
|
REQUIREMENTS = ['transmissionrpc==0.11']
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'current_status': ['Status', ''],
|
'current_status': ['Status', ''],
|
||||||
'download_speed': ['Down Speed', 'MB/s'],
|
'download_speed': ['Down Speed', 'MB/s'],
|
||||||
|
127
homeassistant/components/sensor/verisure.py
Normal file
127
homeassistant/components/sensor/verisure.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.verisure
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Interfaces with Verisure sensors.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.components.verisure as verisure
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.const import TEMP_CELCIUS
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Verisure platform. """
|
||||||
|
|
||||||
|
if not verisure.MY_PAGES:
|
||||||
|
_LOGGER.error('A connection has not been made to Verisure mypages.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
|
||||||
|
sensors.extend([
|
||||||
|
VerisureThermometer(value)
|
||||||
|
for value in verisure.get_climate_status().values()
|
||||||
|
if verisure.SHOW_THERMOMETERS and
|
||||||
|
hasattr(value, 'temperature') and value.temperature
|
||||||
|
])
|
||||||
|
|
||||||
|
sensors.extend([
|
||||||
|
VerisureHygrometer(value)
|
||||||
|
for value in verisure.get_climate_status().values()
|
||||||
|
if verisure.SHOW_HYGROMETERS and
|
||||||
|
hasattr(value, 'humidity') and value.humidity
|
||||||
|
])
|
||||||
|
|
||||||
|
sensors.extend([
|
||||||
|
VerisureAlarm(value)
|
||||||
|
for value in verisure.get_alarm_status().values()
|
||||||
|
if verisure.SHOW_ALARM
|
||||||
|
])
|
||||||
|
|
||||||
|
add_devices(sensors)
|
||||||
|
|
||||||
|
|
||||||
|
class VerisureThermometer(Entity):
|
||||||
|
""" represents a Verisure thermometer within home assistant. """
|
||||||
|
|
||||||
|
def __init__(self, climate_status):
|
||||||
|
self._id = climate_status.id
|
||||||
|
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return '{} {}'.format(
|
||||||
|
verisure.STATUS[self._device][self._id].location,
|
||||||
|
"Temperature")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
# remove ° character
|
||||||
|
return verisure.STATUS[self._device][self._id].temperature[:-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement of this entity """
|
||||||
|
return TEMP_CELCIUS # can verisure report in fahrenheit?
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
''' update sensor '''
|
||||||
|
verisure.update()
|
||||||
|
|
||||||
|
|
||||||
|
class VerisureHygrometer(Entity):
|
||||||
|
""" represents a Verisure hygrometer within home assistant. """
|
||||||
|
|
||||||
|
def __init__(self, climate_status):
|
||||||
|
self._id = climate_status.id
|
||||||
|
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return '{} {}'.format(
|
||||||
|
verisure.STATUS[self._device][self._id].location,
|
||||||
|
"Humidity")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
# remove % character
|
||||||
|
return verisure.STATUS[self._device][self._id].humidity[:-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement of this entity """
|
||||||
|
return "%"
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
''' update sensor '''
|
||||||
|
verisure.update()
|
||||||
|
|
||||||
|
|
||||||
|
class VerisureAlarm(Entity):
|
||||||
|
""" represents a Verisure alarm status within home assistant. """
|
||||||
|
|
||||||
|
def __init__(self, alarm_status):
|
||||||
|
self._id = alarm_status.id
|
||||||
|
self._device = verisure.MY_PAGES.DEVICE_ALARM
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return 'Alarm {}'.format(self._id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return verisure.STATUS[self._device][self._id].label
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
''' update sensor '''
|
||||||
|
verisure.update()
|
@ -1,9 +1,16 @@
|
|||||||
""" Support for Wink sensors. """
|
"""
|
||||||
|
homeassistant.components.sensor.wink
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Wink sensors.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
||||||
|
|
||||||
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
|
||||||
|
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the Wink platform. """
|
""" Sets up the Wink platform. """
|
||||||
@ -24,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class WinkSensorDevice(Entity):
|
class WinkSensorDevice(Entity):
|
||||||
""" represents a wink sensor within home assistant. """
|
""" Represents a wink sensor. """
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
self.wink = wink
|
self.wink = wink
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.zwave
|
homeassistant.components.sensor.zwave
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Interfaces with Z-Wave sensors.
|
Interfaces with Z-Wave sensors.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
|
@ -9,6 +9,7 @@ Provides a simple alarm feature:
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME
|
from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME
|
||||||
|
|
||||||
DOMAIN = "simple_alarm"
|
DOMAIN = "simple_alarm"
|
||||||
@ -83,8 +84,8 @@ def setup(hass, config):
|
|||||||
if not device_tracker.is_on(hass):
|
if not device_tracker.is_on(hass):
|
||||||
unknown_alarm()
|
unknown_alarm()
|
||||||
|
|
||||||
hass.states.track_change(
|
track_state_change(
|
||||||
light.ENTITY_ID_ALL_LIGHTS,
|
hass, light.ENTITY_ID_ALL_LIGHTS,
|
||||||
unknown_alarm_if_lights_on, STATE_OFF, STATE_ON)
|
unknown_alarm_if_lights_on, STATE_OFF, STATE_ON)
|
||||||
|
|
||||||
def ring_known_alarm(entity_id, old_state, new_state):
|
def ring_known_alarm(entity_id, old_state, new_state):
|
||||||
@ -93,8 +94,8 @@ def setup(hass, config):
|
|||||||
known_alarm()
|
known_alarm()
|
||||||
|
|
||||||
# Track home coming of each device
|
# Track home coming of each device
|
||||||
hass.states.track_change(
|
track_state_change(
|
||||||
hass.states.entity_ids(device_tracker.DOMAIN),
|
hass, hass.states.entity_ids(device_tracker.DOMAIN),
|
||||||
ring_known_alarm, STATE_NOT_HOME, STATE_HOME)
|
ring_known_alarm, STATE_NOT_HOME, STATE_HOME)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -21,14 +21,17 @@ The sun event need to have the type 'sun', which service to call, which event
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import urllib
|
||||||
|
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.helpers.event import (
|
||||||
|
track_point_in_utc_time, track_point_in_time)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.components.scheduler import ServiceEventListener
|
from homeassistant.components.scheduler import ServiceEventListener
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['astral>=0.8.1']
|
REQUIREMENTS = ['astral==0.8.1']
|
||||||
DOMAIN = "sun"
|
DOMAIN = "sun"
|
||||||
ENTITY_ID = "sun.sun"
|
ENTITY_ID = "sun.sun"
|
||||||
|
|
||||||
@ -129,8 +132,13 @@ def setup(hass, config):
|
|||||||
|
|
||||||
if elevation is None:
|
if elevation is None:
|
||||||
google = GoogleGeocoder()
|
google = GoogleGeocoder()
|
||||||
|
try:
|
||||||
google._get_elevation(location) # pylint: disable=protected-access
|
google._get_elevation(location) # pylint: disable=protected-access
|
||||||
_LOGGER.info('Retrieved elevation from Google: %s', location.elevation)
|
_LOGGER.info(
|
||||||
|
'Retrieved elevation from Google: %s', location.elevation)
|
||||||
|
except urllib.error.URLError:
|
||||||
|
# If no internet connection available etc.
|
||||||
|
pass
|
||||||
|
|
||||||
sun = Sun(hass, location)
|
sun = Sun(hass, location)
|
||||||
sun.point_in_time_listener(dt_util.utcnow())
|
sun.point_in_time_listener(dt_util.utcnow())
|
||||||
@ -203,8 +211,8 @@ class Sun(Entity):
|
|||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
# Schedule next update at next_change+1 second so sun state has changed
|
# Schedule next update at next_change+1 second so sun state has changed
|
||||||
self.hass.track_point_in_utc_time(
|
track_point_in_utc_time(
|
||||||
self.point_in_time_listener,
|
self.hass, self.point_in_time_listener,
|
||||||
self.next_change + timedelta(seconds=1))
|
self.next_change + timedelta(seconds=1))
|
||||||
|
|
||||||
|
|
||||||
@ -266,7 +274,7 @@ class SunEventListener(ServiceEventListener):
|
|||||||
""" Call the execute method. """
|
""" Call the execute method. """
|
||||||
self.execute(hass)
|
self.execute(hass)
|
||||||
|
|
||||||
hass.track_point_in_time(execute, next_time)
|
track_point_in_time(hass, execute, next_time)
|
||||||
|
|
||||||
return next_time
|
return next_time
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
|||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
from homeassistant.components import group, discovery, wink, isy994
|
from homeassistant.components import group, discovery, wink, isy994, verisure
|
||||||
|
|
||||||
DOMAIN = 'switch'
|
DOMAIN = 'switch'
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -34,6 +34,7 @@ DISCOVERY_PLATFORMS = {
|
|||||||
discovery.SERVICE_WEMO: 'wemo',
|
discovery.SERVICE_WEMO: 'wemo',
|
||||||
wink.DISCOVER_SWITCHES: 'wink',
|
wink.DISCOVER_SWITCHES: 'wink',
|
||||||
isy994.DISCOVER_SWITCHES: 'isy994',
|
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||||
|
verisure.DISCOVER_SWITCHES: 'verisure'
|
||||||
}
|
}
|
||||||
|
|
||||||
PROP_TO_ATTR = {
|
PROP_TO_ATTR = {
|
||||||
@ -49,21 +50,18 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def is_on(hass, entity_id=None):
|
def is_on(hass, entity_id=None):
|
||||||
""" Returns if the switch is on based on the statemachine. """
|
""" Returns if the switch is on based on the statemachine. """
|
||||||
entity_id = entity_id or ENTITY_ID_ALL_SWITCHES
|
entity_id = entity_id or ENTITY_ID_ALL_SWITCHES
|
||||||
|
|
||||||
return hass.states.is_state(entity_id, STATE_ON)
|
return hass.states.is_state(entity_id, STATE_ON)
|
||||||
|
|
||||||
|
|
||||||
def turn_on(hass, entity_id=None):
|
def turn_on(hass, entity_id=None):
|
||||||
""" Turns all or specified switch on. """
|
""" Turns all or specified switch on. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
||||||
|
|
||||||
|
|
||||||
def turn_off(hass, entity_id=None):
|
def turn_off(hass, entity_id=None):
|
||||||
""" Turns all or specified switch off. """
|
""" Turns all or specified switch off. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +86,6 @@ def setup(hass, config):
|
|||||||
switch.update_ha_state(True)
|
switch.update_ha_state(True)
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)
|
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service)
|
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
117
homeassistant/components/switch/edimax.py
Normal file
117
homeassistant/components/switch/edimax.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.edimax
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Edimax switches.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Edimax switch you will need to add something like the following to
|
||||||
|
your config/configuration.yaml.
|
||||||
|
|
||||||
|
switch:
|
||||||
|
platform: edimax
|
||||||
|
host: 192.168.1.32
|
||||||
|
username: YOUR_USERNAME
|
||||||
|
password: YOUR_PASSWORD
|
||||||
|
name: Edimax Smart Plug
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
This is the IP address of your Edimax switch. Example: 192.168.1.32
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
Your username to access your Edimax switch.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
Your password.
|
||||||
|
|
||||||
|
name
|
||||||
|
*Optional
|
||||||
|
The name to use when displaying this switch instance.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.switch import SwitchDevice, DOMAIN
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD,\
|
||||||
|
CONF_NAME
|
||||||
|
|
||||||
|
# constants
|
||||||
|
DEFAULT_USERNAME = 'admin'
|
||||||
|
DEFAULT_PASSWORD = '1234'
|
||||||
|
DEVICE_DEFAULT_NAME = 'Edimax Smart Plug'
|
||||||
|
REQUIREMENTS = ['https://github.com/rkabadi/pyedimax/archive/' +
|
||||||
|
'365301ce3ff26129a7910c501ead09ea625f3700.zip']
|
||||||
|
|
||||||
|
# setup logger
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Find and return Edimax Smart Plugs. """
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
from pyedimax.smartplug import SmartPlug
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.error('Failed to import pyedimax')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# pylint: disable=global-statement
|
||||||
|
# check for required values in configuration file
|
||||||
|
if not validate_config({DOMAIN: config},
|
||||||
|
{DOMAIN: [CONF_HOST]},
|
||||||
|
_LOGGER):
|
||||||
|
return False
|
||||||
|
|
||||||
|
host = config.get(CONF_HOST)
|
||||||
|
auth = (config.get(CONF_USERNAME, DEFAULT_USERNAME),
|
||||||
|
config.get(CONF_PASSWORD, DEFAULT_PASSWORD))
|
||||||
|
name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME)
|
||||||
|
|
||||||
|
add_devices_callback([SmartPlugSwitch(SmartPlug(host, auth), name)])
|
||||||
|
|
||||||
|
|
||||||
|
class SmartPlugSwitch(SwitchDevice):
|
||||||
|
""" Represents an Edimax Smart Plug switch. """
|
||||||
|
def __init__(self, smartplug, name):
|
||||||
|
self.smartplug = smartplug
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the Smart Plug, if any. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_power_mwh(self):
|
||||||
|
""" Current power usage in mwh. """
|
||||||
|
try:
|
||||||
|
return float(self.smartplug.now_power) / 1000000.0
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def today_power_mw(self):
|
||||||
|
""" Today total power usage in mw. """
|
||||||
|
try:
|
||||||
|
return float(self.smartplug.now_energy_day) / 1000.0
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if switch is on. """
|
||||||
|
return self.smartplug.state == 'ON'
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turns the switch on. """
|
||||||
|
self.smartplug.state = 'ON'
|
||||||
|
|
||||||
|
def turn_off(self):
|
||||||
|
""" Turns the switch off. """
|
||||||
|
self.smartplug.state = 'OFF'
|
@ -1,24 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.switch.hikvision
|
homeassistant.components.switch.hikvision
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Support turning on/off motion detection on Hikvision cameras.
|
Support turning on/off motion detection on Hikvision cameras.
|
||||||
|
|
||||||
Note: Currently works using default https port only.
|
Note: Currently works using default https port only.
|
||||||
|
|
||||||
CGI API Guide:
|
CGI API Guide: http://bit.ly/1RuyUuF
|
||||||
http://bit.ly/1RuyUuF
|
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
To use the Hikvision motion detection
|
To use the Hikvision motion detection switch you will need to add something
|
||||||
switch you will need to add something like the
|
like the following to your config/configuration.yaml
|
||||||
following to your config/configuration.yaml
|
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
platform: hikvisioncam
|
platform: hikvisioncam
|
||||||
name: Hikvision Cam 1 Motion Detection
|
name: Hikvision Cam 1 Motion Detection
|
||||||
host: 192.168.1.26
|
host: 192.168.1.32
|
||||||
username: YOUR_USERNAME
|
username: YOUR_USERNAME
|
||||||
password: YOUR_PASSWORD
|
password: YOUR_PASSWORD
|
||||||
|
|
||||||
@ -30,16 +27,15 @@ This is the IP address of your Hikvision camera. Example: 192.168.1.32
|
|||||||
|
|
||||||
username
|
username
|
||||||
*Required
|
*Required
|
||||||
Your Hikvision camera username
|
Your Hikvision camera username.
|
||||||
|
|
||||||
password
|
password
|
||||||
*Required
|
*Required
|
||||||
Your Hikvision camera username
|
Your Hikvision camera username.
|
||||||
|
|
||||||
name
|
name
|
||||||
*Optional
|
*Optional
|
||||||
The name to use when displaying this switch instance.
|
The name to use when displaying this switch instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
@ -53,7 +49,7 @@ except ImportError:
|
|||||||
hikvision.api = None
|
hikvision.api = None
|
||||||
|
|
||||||
_LOGGING = logging.getLogger(__name__)
|
_LOGGING = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['hikvision>=0.4']
|
REQUIREMENTS = ['hikvision==0.4']
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
|
@ -18,12 +18,16 @@ sensor:
|
|||||||
name: My switch
|
name: My switch
|
||||||
2:
|
2:
|
||||||
name: My other switch
|
name: My other switch
|
||||||
|
coils:
|
||||||
|
0:
|
||||||
|
name: My coil switch
|
||||||
|
|
||||||
VARIABLES:
|
VARIABLES:
|
||||||
|
|
||||||
- "slave" = slave number (ignored and can be omitted if not serial Modbus)
|
- "slave" = slave number (ignored and can be omitted if not serial Modbus)
|
||||||
- "registers" contains a list of relevant registers to read from
|
- "registers" contains a list of relevant registers to read from
|
||||||
- it must contain a "bits" section, listing relevant bits
|
- it must contain a "bits" section, listing relevant bits
|
||||||
|
- "coils" contains a list of relevant coils to read from/write to
|
||||||
|
|
||||||
- each named bit will create a switch
|
- each named bit will create a switch
|
||||||
"""
|
"""
|
||||||
@ -34,6 +38,7 @@ import homeassistant.components.modbus as modbus
|
|||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
DEPENDENCIES = ['modbus']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -44,6 +49,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
_LOGGER.error("No slave number provided for serial Modbus")
|
_LOGGER.error("No slave number provided for serial Modbus")
|
||||||
return False
|
return False
|
||||||
registers = config.get("registers")
|
registers = config.get("registers")
|
||||||
|
if registers:
|
||||||
for regnum, register in registers.items():
|
for regnum, register in registers.items():
|
||||||
bits = register.get("bits")
|
bits = register.get("bits")
|
||||||
for bitnum, bit in bits.items():
|
for bitnum, bit in bits.items():
|
||||||
@ -52,17 +58,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
slave,
|
slave,
|
||||||
regnum,
|
regnum,
|
||||||
bitnum))
|
bitnum))
|
||||||
|
coils = config.get("coils")
|
||||||
|
if coils:
|
||||||
|
for coilnum, coil in coils.items():
|
||||||
|
switches.append(ModbusSwitch(coil.get("name"),
|
||||||
|
slave,
|
||||||
|
coilnum,
|
||||||
|
0,
|
||||||
|
coil=True))
|
||||||
add_devices(switches)
|
add_devices(switches)
|
||||||
|
|
||||||
|
|
||||||
class ModbusSwitch(ToggleEntity):
|
class ModbusSwitch(ToggleEntity):
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
""" Represents a Modbus switch. """
|
""" Represents a Modbus switch. """
|
||||||
|
|
||||||
def __init__(self, name, slave, register, bit):
|
def __init__(self, name, slave, register, bit, coil=False):
|
||||||
self._name = name
|
self._name = name
|
||||||
self.slave = int(slave) if slave else 1
|
self.slave = int(slave) if slave else 1
|
||||||
self.register = int(register)
|
self.register = int(register)
|
||||||
self.bit = int(bit)
|
self.bit = int(bit)
|
||||||
|
self._coil = coil
|
||||||
self._is_on = None
|
self._is_on = None
|
||||||
self.register_value = None
|
self.register_value = None
|
||||||
|
|
||||||
@ -92,30 +108,41 @@ class ModbusSwitch(ToggleEntity):
|
|||||||
""" Get the name of the switch. """
|
""" Get the name of the switch. """
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
attr = super().state_attributes
|
|
||||||
return attr
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
|
""" Set switch on. """
|
||||||
if self.register_value is None:
|
if self.register_value is None:
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
if self._coil:
|
||||||
|
modbus.NETWORK.write_coil(self.register, True)
|
||||||
|
else:
|
||||||
val = self.register_value | (0x0001 << self.bit)
|
val = self.register_value | (0x0001 << self.bit)
|
||||||
modbus.NETWORK.write_register(unit=self.slave,
|
modbus.NETWORK.write_register(unit=self.slave,
|
||||||
address=self.register,
|
address=self.register,
|
||||||
value=val)
|
value=val)
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
|
""" Set switch off. """
|
||||||
if self.register_value is None:
|
if self.register_value is None:
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
if self._coil:
|
||||||
|
modbus.NETWORK.write_coil(self.register, False)
|
||||||
|
else:
|
||||||
val = self.register_value & ~(0x0001 << self.bit)
|
val = self.register_value & ~(0x0001 << self.bit)
|
||||||
modbus.NETWORK.write_register(unit=self.slave,
|
modbus.NETWORK.write_register(unit=self.slave,
|
||||||
address=self.register,
|
address=self.register,
|
||||||
value=val)
|
value=val)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
result = modbus.NETWORK.read_holding_registers(unit=self.slave,
|
""" Update the state of the switch. """
|
||||||
address=self.register,
|
if self._coil:
|
||||||
|
result = modbus.NETWORK.read_coils(self.register, 1)
|
||||||
|
self.register_value = result.bits[0]
|
||||||
|
self._is_on = self.register_value
|
||||||
|
else:
|
||||||
|
result = modbus.NETWORK.read_holding_registers(
|
||||||
|
unit=self.slave, address=self.register,
|
||||||
count=1)
|
count=1)
|
||||||
val = 0
|
val = 0
|
||||||
for i, res in enumerate(result.registers):
|
for i, res in enumerate(result.registers):
|
||||||
|
153
homeassistant/components/switch/mqtt.py
Normal file
153
homeassistant/components/switch/mqtt.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
homeassistant.components.switch.mqtt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure a MQTT switch.
|
||||||
|
|
||||||
|
In an ideal scenario, the MQTT device will have a state topic to publish
|
||||||
|
state changes. If these messages are published with RETAIN flag, the MQTT
|
||||||
|
switch will receive an instant state update after subscription and will
|
||||||
|
start with correct state. Otherwise, the initial state of the switch will
|
||||||
|
be false/off.
|
||||||
|
|
||||||
|
When a state topic is not available, the switch will work in optimistic mode.
|
||||||
|
In this mode, the switch will immediately change state after every command.
|
||||||
|
Otherwise, the switch will wait for state confirmation from device
|
||||||
|
(message from state_topic).
|
||||||
|
|
||||||
|
Optimistic mode can be forced, even if state topic is available.
|
||||||
|
Try to enable it, if experiencing incorrect switch operation.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
switch:
|
||||||
|
platform: mqtt
|
||||||
|
name: "Bedroom Switch"
|
||||||
|
state_topic: "home/bedroom/switch1"
|
||||||
|
command_topic: "home/bedroom/switch1/set"
|
||||||
|
payload_on: "ON"
|
||||||
|
payload_off: "OFF"
|
||||||
|
optimistic: false
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
name
|
||||||
|
*Optional
|
||||||
|
The name of the switch. Default is 'MQTT Switch'.
|
||||||
|
|
||||||
|
state_topic
|
||||||
|
*Optional
|
||||||
|
The MQTT topic subscribed to receive state updates.
|
||||||
|
If not specified, optimistic mode will be forced.
|
||||||
|
|
||||||
|
command_topic
|
||||||
|
*Required
|
||||||
|
The MQTT topic to publish commands to change the switch state.
|
||||||
|
|
||||||
|
payload_on
|
||||||
|
*Optional
|
||||||
|
The payload that represents enabled state. Default is "ON".
|
||||||
|
|
||||||
|
payload_off
|
||||||
|
*Optional
|
||||||
|
The payload that represents disabled state. Default is "OFF".
|
||||||
|
|
||||||
|
optimistic
|
||||||
|
*Optional
|
||||||
|
Flag that defines if switch works in optimistic mode. Default is false.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import homeassistant.components.mqtt as mqtt
|
||||||
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_NAME = "MQTT Switch"
|
||||||
|
DEFAULT_PAYLOAD_ON = "ON"
|
||||||
|
DEFAULT_PAYLOAD_OFF = "OFF"
|
||||||
|
DEFAULT_OPTIMISTIC = False
|
||||||
|
|
||||||
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Add MQTT Switch """
|
||||||
|
|
||||||
|
if config.get('command_topic') is None:
|
||||||
|
_LOGGER.error("Missing required variable: command_topic")
|
||||||
|
return False
|
||||||
|
|
||||||
|
add_devices_callback([MqttSwitch(
|
||||||
|
hass,
|
||||||
|
config.get('name', DEFAULT_NAME),
|
||||||
|
config.get('state_topic'),
|
||||||
|
config.get('command_topic'),
|
||||||
|
config.get('payload_on', DEFAULT_PAYLOAD_ON),
|
||||||
|
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
|
||||||
|
config.get('optimistic', DEFAULT_OPTIMISTIC))])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
|
class MqttSwitch(SwitchDevice):
|
||||||
|
""" Represents a switch that can be togggled using MQTT """
|
||||||
|
def __init__(self, hass, name, state_topic, command_topic,
|
||||||
|
payload_on, payload_off, optimistic):
|
||||||
|
self._state = False
|
||||||
|
self._hass = hass
|
||||||
|
self._name = name
|
||||||
|
self._state_topic = state_topic
|
||||||
|
self._command_topic = command_topic
|
||||||
|
self._payload_on = payload_on
|
||||||
|
self._payload_off = payload_off
|
||||||
|
self._optimistic = optimistic
|
||||||
|
|
||||||
|
def message_received(topic, payload, qos):
|
||||||
|
""" A new MQTT message has been received. """
|
||||||
|
if payload == self._payload_on:
|
||||||
|
self._state = True
|
||||||
|
self.update_ha_state()
|
||||||
|
elif payload == self._payload_off:
|
||||||
|
self._state = False
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
if self._state_topic is None:
|
||||||
|
# force optimistic mode
|
||||||
|
self._optimistic = True
|
||||||
|
else:
|
||||||
|
# subscribe the state_topic
|
||||||
|
mqtt.subscribe(hass, self._state_topic, message_received)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the switch """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turn the device on. """
|
||||||
|
mqtt.publish(self.hass, self._command_topic, self._payload_on)
|
||||||
|
if self._optimistic:
|
||||||
|
# optimistically assume that switch has changed state
|
||||||
|
self._state = True
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Turn the device off. """
|
||||||
|
mqtt.publish(self.hass, self._command_topic, self._payload_off)
|
||||||
|
if self._optimistic:
|
||||||
|
# optimistically assume that switch has changed state
|
||||||
|
self._state = False
|
||||||
|
self.update_ha_state()
|
134
homeassistant/components/switch/rpi_gpio.py
Normal file
134
homeassistant/components/switch/rpi_gpio.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.rpi_gpio
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to control the GPIO pins of a Raspberry Pi.
|
||||||
|
Note: To use RPi GPIO, Home Assistant must be run as root.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
switch:
|
||||||
|
platform: rpi_gpio
|
||||||
|
invert_logic: false
|
||||||
|
ports:
|
||||||
|
11: Fan Office
|
||||||
|
12: Light Desk
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
invert_logic
|
||||||
|
*Optional
|
||||||
|
If true, inverts the output logic to ACTIVE LOW. Default is false (ACTIVE HIGH)
|
||||||
|
|
||||||
|
ports
|
||||||
|
*Required
|
||||||
|
An array specifying the GPIO ports to use and the name to use in the frontend.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
try:
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
except ImportError:
|
||||||
|
GPIO = None
|
||||||
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
from homeassistant.const import (DEVICE_DEFAULT_NAME,
|
||||||
|
EVENT_HOMEASSISTANT_START,
|
||||||
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
|
DEFAULT_INVERT_LOGIC = False
|
||||||
|
|
||||||
|
REQUIREMENTS = ['RPi.GPIO==0.5.11']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Raspberry PI GPIO ports. """
|
||||||
|
if GPIO is None:
|
||||||
|
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
|
||||||
|
return
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
switches = []
|
||||||
|
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
|
||||||
|
ports = config.get('ports')
|
||||||
|
for port_num, port_name in ports.items():
|
||||||
|
switches.append(RPiGPIOSwitch(port_name, port_num, invert_logic))
|
||||||
|
add_devices(switches)
|
||||||
|
|
||||||
|
def cleanup_gpio(event):
|
||||||
|
""" Stuff to do before stop home assistant. """
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
def prepare_gpio(event):
|
||||||
|
""" Stuff to do when home assistant starts. """
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
|
||||||
|
|
||||||
|
|
||||||
|
class RPiGPIOSwitch(ToggleEntity):
|
||||||
|
""" Represents a port that can be toggled using Raspberry Pi GPIO. """
|
||||||
|
|
||||||
|
def __init__(self, name, gpio, invert_logic):
|
||||||
|
self._name = name or DEVICE_DEFAULT_NAME
|
||||||
|
self._gpio = gpio
|
||||||
|
self._active_state = not invert_logic
|
||||||
|
self._state = not self._active_state
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.setup(gpio, GPIO.OUT)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the port. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turn the device on. """
|
||||||
|
if self._switch(self._active_state):
|
||||||
|
self._state = True
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Turn the device off. """
|
||||||
|
if self._switch(not self._active_state):
|
||||||
|
self._state = False
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def _switch(self, new_state):
|
||||||
|
""" Change the output value to Raspberry Pi GPIO port. """
|
||||||
|
_LOGGER.info('Setting GPIO %s to %s', self._gpio, new_state)
|
||||||
|
# pylint: disable=bare-except
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.output(self._gpio, 1 if new_state else 0)
|
||||||
|
except:
|
||||||
|
_LOGGER.error('GPIO "%s" output failed', self._gpio)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
""" Returns device specific state attributes. """
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_attributes(self):
|
||||||
|
""" Returns optional state attributes. """
|
||||||
|
data = {}
|
||||||
|
device_attr = self.device_state_attributes
|
||||||
|
if device_attr is not None:
|
||||||
|
data.update(device_attr)
|
||||||
|
return data
|
@ -19,6 +19,8 @@ import tellcore.constants as tellcore_constants
|
|||||||
|
|
||||||
SINGAL_REPETITIONS = 1
|
SINGAL_REPETITIONS = 1
|
||||||
|
|
||||||
|
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.switch.transmission
|
homeassistant.components.switch.transmission
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Enable or disable Transmission BitTorrent client Turtle Mode.
|
||||||
Enable or disable Transmission BitTorrent client Turtle Mode
|
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
@ -29,37 +28,32 @@ The port your Transmission daemon uses, defaults to 9091. Example: 8080
|
|||||||
|
|
||||||
username
|
username
|
||||||
*Optional
|
*Optional
|
||||||
Your Transmission username, if you use authentication
|
Your Transmission username, if you use authentication.
|
||||||
|
|
||||||
password
|
password
|
||||||
*Optional
|
*Optional
|
||||||
Your Transmission username, if you use authentication
|
Your Transmission username, if you use authentication.
|
||||||
|
|
||||||
name
|
name
|
||||||
*Optional
|
*Optional
|
||||||
The name to use when displaying this Transmission instance.
|
The name to use when displaying this Transmission instance.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
|
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
# pylint: disable=no-name-in-module, import-error
|
# pylint: disable=no-name-in-module, import-error
|
||||||
import transmissionrpc
|
import transmissionrpc
|
||||||
|
|
||||||
from transmissionrpc.error import TransmissionError
|
from transmissionrpc.error import TransmissionError
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
_LOGGING = logging.getLogger(__name__)
|
_LOGGING = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['transmissionrpc>=0.11']
|
REQUIREMENTS = ['transmissionrpc==0.11']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Sets up the sensors. """
|
""" Sets up the transmission sensor. """
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
username = config.get(CONF_USERNAME, None)
|
username = config.get(CONF_USERNAME, None)
|
||||||
password = config.get(CONF_PASSWORD, None)
|
password = config.get(CONF_PASSWORD, None)
|
||||||
@ -87,7 +81,6 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class TransmissionSwitch(ToggleEntity):
|
class TransmissionSwitch(ToggleEntity):
|
||||||
|
|
||||||
""" A Transmission sensor. """
|
""" A Transmission sensor. """
|
||||||
|
|
||||||
def __init__(self, transmission_client, name):
|
def __init__(self, transmission_client, name):
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.switch.vera
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Support for Vera switches.
|
Support for Vera switches.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
To use the Vera lights you will need to add something like the following to
|
To use the Vera lights you will need to add something like the following to
|
||||||
your config/configuration.yaml
|
your config/configuration.yaml.
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
platform: vera
|
platform: vera
|
||||||
@ -15,38 +17,32 @@ switch:
|
|||||||
13:
|
13:
|
||||||
name: Another Switch
|
name: Another Switch
|
||||||
|
|
||||||
VARIABLES:
|
Variables:
|
||||||
|
|
||||||
vera_controller_url
|
vera_controller_url
|
||||||
*Required
|
*Required
|
||||||
This is the base URL of your vera controller including the port number if not
|
This is the base URL of your vera controller including the port number if not
|
||||||
running on 80
|
running on 80. Example: http://192.168.1.21:3480/
|
||||||
Example: http://192.168.1.21:3480/
|
|
||||||
|
|
||||||
|
|
||||||
device_data
|
device_data
|
||||||
*Optional
|
*Optional
|
||||||
This contains an array additional device info for your Vera devices. It is not
|
This contains an array additional device info for your Vera devices. It is not
|
||||||
required and if not specified all lights configured in your Vera controller
|
required and if not specified all lights configured in your Vera controller
|
||||||
will be added with default values. You should use the id of your vera device
|
will be added with default values. You should use the id of your vera device
|
||||||
as the key for the device within device_data
|
as the key for the device within device_data.
|
||||||
|
|
||||||
|
|
||||||
These are the variables for the device_data array:
|
These are the variables for the device_data array:
|
||||||
|
|
||||||
|
|
||||||
name
|
name
|
||||||
*Optional
|
*Optional
|
||||||
This parameter allows you to override the name of your Vera device in the HA
|
This parameter allows you to override the name of your Vera device in the HA
|
||||||
interface, if not specified the value configured for the device in your Vera
|
interface, if not specified the value configured for the device in your Vera
|
||||||
will be used
|
will be used.
|
||||||
|
|
||||||
|
|
||||||
exclude
|
exclude
|
||||||
*Optional
|
*Optional
|
||||||
This parameter allows you to exclude the specified device from homeassistant,
|
This parameter allows you to exclude the specified device from homeassistant,
|
||||||
it should be set to "true" if you want this device excluded
|
it should be set to "true" if you want this device excluded.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
@ -82,7 +78,7 @@ def get_devices(hass, config):
|
|||||||
devices = vera_controller.get_devices([
|
devices = vera_controller.get_devices([
|
||||||
'Switch', 'Armable Sensor', 'On/Off Switch'])
|
'Switch', 'Armable Sensor', 'On/Off Switch'])
|
||||||
except RequestException:
|
except RequestException:
|
||||||
# There was a network related error connecting to the vera controller
|
# There was a network related error connecting to the vera controller.
|
||||||
_LOGGER.exception("Error communicating with Vera API")
|
_LOGGER.exception("Error communicating with Vera API")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -103,7 +99,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class VeraSwitch(ToggleEntity):
|
class VeraSwitch(ToggleEntity):
|
||||||
""" Represents a Vera Switch """
|
""" Represents a Vera Switch. """
|
||||||
|
|
||||||
def __init__(self, vera_device, extra_data=None):
|
def __init__(self, vera_device, extra_data=None):
|
||||||
self.vera_device = vera_device
|
self.vera_device = vera_device
|
||||||
|
63
homeassistant/components/switch/verisure.py
Normal file
63
homeassistant/components/switch/verisure.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.verisure
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Verisure Smartplugs
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.components.verisure as verisure
|
||||||
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Arduino platform. """
|
||||||
|
|
||||||
|
if not verisure.MY_PAGES:
|
||||||
|
_LOGGER.error('A connection has not been made to Verisure mypages.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
switches = []
|
||||||
|
|
||||||
|
switches.extend([
|
||||||
|
VerisureSmartplug(value)
|
||||||
|
for value in verisure.get_smartplug_status().values()
|
||||||
|
if verisure.SHOW_SMARTPLUGS
|
||||||
|
])
|
||||||
|
|
||||||
|
add_devices(switches)
|
||||||
|
|
||||||
|
|
||||||
|
class VerisureSmartplug(SwitchDevice):
|
||||||
|
""" Represents a Verisure smartplug. """
|
||||||
|
def __init__(self, smartplug_status):
|
||||||
|
self._id = smartplug_status.id
|
||||||
|
self.status_on = verisure.MY_PAGES.SMARTPLUG_ON
|
||||||
|
self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Get the name (location) of the smartplug. """
|
||||||
|
return verisure.get_smartplug_status()[self._id].location
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" Returns True if on """
|
||||||
|
plug_status = verisure.get_smartplug_status()[self._id].status
|
||||||
|
return plug_status == self.status_on
|
||||||
|
|
||||||
|
def turn_on(self):
|
||||||
|
""" Set smartplug status on """
|
||||||
|
verisure.MY_PAGES.set_smartplug_status(
|
||||||
|
self._id,
|
||||||
|
self.status_on)
|
||||||
|
|
||||||
|
def turn_off(self):
|
||||||
|
""" Set smartplug status off """
|
||||||
|
verisure.MY_PAGES.set_smartplug_status(
|
||||||
|
self._id,
|
||||||
|
self.status_off)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
verisure.update()
|
@ -8,6 +8,8 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
|
REQUIREMENTS = ['pywemo==0.2']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
@ -16,7 +18,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
import pywemo.discovery as discovery
|
import pywemo.discovery as discovery
|
||||||
|
|
||||||
if discovery_info is not None:
|
if discovery_info is not None:
|
||||||
device = discovery.device_from_description(discovery_info)
|
device = discovery.device_from_description(discovery_info[2])
|
||||||
|
|
||||||
if device:
|
if device:
|
||||||
add_devices_callback([WemoSwitch(device)])
|
add_devices_callback([WemoSwitch(device)])
|
||||||
|
@ -9,6 +9,9 @@ import logging
|
|||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
|
||||||
|
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the Wink platform. """
|
""" Sets up the Wink platform. """
|
||||||
|
@ -10,6 +10,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
|||||||
|
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.temperature import convert
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS)
|
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS)
|
||||||
|
|
||||||
@ -86,7 +87,9 @@ def setup(hass, config):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for thermostat in target_thermostats:
|
for thermostat in target_thermostats:
|
||||||
thermostat.set_temperature(temperature)
|
thermostat.set_temperature(convert(
|
||||||
|
temperature, hass.config.temperature_unit,
|
||||||
|
thermostat.unit_of_measurement))
|
||||||
|
|
||||||
for thermostat in target_thermostats:
|
for thermostat in target_thermostats:
|
||||||
thermostat.update_ha_state(True)
|
thermostat.update_ha_state(True)
|
||||||
@ -118,9 +121,20 @@ class ThermostatDevice(Entity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns optional state attributes. """
|
""" Returns optional state attributes. """
|
||||||
|
|
||||||
|
thermostat_unit = self.unit_of_measurement
|
||||||
|
user_unit = self.hass.config.temperature_unit
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
ATTR_CURRENT_TEMPERATURE: self.hass.config.temperature(
|
ATTR_CURRENT_TEMPERATURE: round(convert(self.current_temperature,
|
||||||
self.current_temperature, self.unit_of_measurement)[0]
|
thermostat_unit,
|
||||||
|
user_unit), 1),
|
||||||
|
ATTR_MIN_TEMP: round(convert(self.min_temp,
|
||||||
|
thermostat_unit,
|
||||||
|
user_unit), 0),
|
||||||
|
ATTR_MAX_TEMP: round(convert(self.max_temp,
|
||||||
|
thermostat_unit,
|
||||||
|
user_unit), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
is_away = self.is_away_mode_on
|
is_away = self.is_away_mode_on
|
||||||
@ -133,11 +147,13 @@ class ThermostatDevice(Entity):
|
|||||||
if device_attr is not None:
|
if device_attr is not None:
|
||||||
data.update(device_attr)
|
data.update(device_attr)
|
||||||
|
|
||||||
data[ATTR_MIN_TEMP] = self.min_temp
|
|
||||||
data[ATTR_MAX_TEMP] = self.max_temp
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement this thermostat expresses itself in. """
|
||||||
|
return NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
""" Returns the current temperature. """
|
""" Returns the current temperature. """
|
||||||
@ -171,9 +187,9 @@ class ThermostatDevice(Entity):
|
|||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
""" Return minimum temperature. """
|
""" Return minimum temperature. """
|
||||||
return self.hass.config.temperature(7, TEMP_CELCIUS)[0]
|
return convert(7, TEMP_CELCIUS, self.unit_of_measurement)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
""" Return maxmum temperature. """
|
""" Return maxmum temperature. """
|
||||||
return self.hass.config.temperature(35, TEMP_CELCIUS)[0]
|
return convert(35, TEMP_CELCIUS, self.unit_of_measurement)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user