Merge remote-tracking branch 'balloob/dev' into feature/enhance_wemo

This commit is contained in:
pavoni 2015-08-30 22:47:49 +01:00
commit bfbaaa8e9f
167 changed files with 6705 additions and 2728 deletions

View File

@ -10,50 +10,72 @@ omit =
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/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/camera/*
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/thomson.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/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/notify/file.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushover.py
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/mysensors.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/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.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/rpi_gpio.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/nest.py

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ homeassistant/components/frontend/www_static/polymer/bower_components/*
config/custom_components/*
!config/custom_components/example.py
!config/custom_components/hello_world.py
!config/custom_components/mqtt_example.py
# Hide sublime text stuff
*.sublime-project

View File

@ -3,7 +3,7 @@ language: python
python:
- "3.4"
install:
- pip install -r requirements.txt
- pip install -r requirements_all.txt
- pip install flake8 pylint coveralls
script:
- flake8 homeassistant --exclude bower_components,external

View File

@ -3,10 +3,12 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
VOLUME /config
RUN apt-get update && \
apt-get install -y cython3 libudev-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
pip3 install cython && \
scripts/build_python_openzwave
RUN pip3 install --no-cache-dir -r requirements_all.txt
#RUN apt-get update && \
# apt-get install -y cython3 libudev-dev && \
# 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" ]

View File

@ -1,18 +1,26 @@
# Home Assistant [![Build Status](https://travis-ci.org/balloob/home-assistant.svg?branch=master)](https://travis-ci.org/balloob/home-assistant) [![Coverage Status](https://img.shields.io/coveralls/balloob/home-assistant.svg)](https://coveralls.io/r/balloob/home-assistant?branch=master) [![Join the chat at https://gitter.im/balloob/home-assistant](https://badges.gitter.im/Join%20Chat.svg)](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.
[![screenshot-states](https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png)](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.
[![screenshot-states](https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png)][demo]
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/)
* [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
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/) 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/)
* 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).
* 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, [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/), [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/), [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, 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/)
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 lights slowly during sun set to compensate for less light
* 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
* 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)
* 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/), [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).
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.
## 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.
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.

View 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

View File

@ -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

View File

@ -4,120 +4,61 @@ from __future__ import print_function
import sys
import os
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
return bootstrap
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()
from homeassistant import bootstrap
import homeassistant.config as config_util
from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START
def ensure_config_path(config_dir):
""" Gets the path to the configuration file.
Creates one if it not exists. """
""" Validates configuration directory. """
lib_dir = os.path.join(config_dir, 'lib')
# Test if configuration directory exists
if not os.path.isdir(config_dir):
print(('Fatal Error: Unable to find specified configuration '
'directory {} ').format(config_dir))
sys.exit()
if config_dir != config_util.get_default_config_dir():
print(('Fatal Error: Specified configuration directory does '
'not exist {} ').format(config_dir))
sys.exit(1)
import homeassistant.config as config_util
try:
os.mkdir(config_dir)
except OSError:
print(('Fatal Error: Unable to create default configuration '
'directory {} ').format(config_dir))
sys.exit(1)
# 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)
if config_path is None:
print('Error getting configuration path')
sys.exit()
sys.exit(1)
return config_path
def get_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(
'-c', '--config',
metavar='path_to_config_dir',
default="config",
default=config_util.get_default_config_dir(),
help="Directory that contains the Home Assistant configuration")
parser.add_argument(
'--demo-mode',
@ -133,34 +74,21 @@ def get_arguments():
def main():
""" 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()
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:
from homeassistant.components import frontend, demo
hass = bootstrap.from_config_dict({
frontend.DOMAIN: {},
demo.DOMAIN: {}
})
'frontend': {},
'demo': {}
}, config_dir=config_dir)
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:
from homeassistant.const import EVENT_HOMEASSISTANT_START
def open_browser(event):
""" Open the webinterface in a browser. """
if hass.config.api is not None:

View File

@ -10,10 +10,11 @@ start by calling homeassistant.start_home_assistant(bus)
"""
import os
import sys
import logging
from collections import defaultdict
import homeassistant
import homeassistant.core as core
import homeassistant.util.dt as date_util
import homeassistant.util.package as pkg_util
import homeassistant.util.location as loc_util
@ -61,14 +62,17 @@ def setup_component(hass, domain, config=None):
return True
def _handle_requirements(component, name):
def _handle_requirements(hass, component, name):
""" Installs requirements for component. """
if hasattr(component, 'REQUIREMENTS'):
for req in component.REQUIREMENTS:
if not pkg_util.install_package(req):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
return False
if not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS:
if not pkg_util.install_package(req, target=hass.config.path('lib')):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
return False
return True
@ -83,33 +87,30 @@ def _setup_component(hass, domain, config):
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return False
if not _handle_requirements(component, domain):
if not _handle_requirements(hass, component, domain):
return False
try:
if component.setup(hass, config):
hass.config.components.append(component.DOMAIN)
# Assumption: if a component does not depend on groups
# it communicates with devices
if group.DOMAIN not in component.DEPENDENCIES:
hass.pool.add_worker()
hass.bus.fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
return True
else:
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
return False
hass.config.components.append(component.DOMAIN)
# Assumption: if a component does not depend on groups
# it communicates with devices
if group.DOMAIN not in component.DEPENDENCIES:
hass.pool.add_worker()
hass.bus.fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
return True
def prepare_setup_platform(hass, config, domain, platform_name):
@ -138,25 +139,35 @@ def prepare_setup_platform(hass, config, domain, platform_name):
component)
return None
if not _handle_requirements(platform, platform_path):
if not _handle_requirements(hass, platform, platform_path):
return None
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
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.
Dynamically loads required components and its dependencies.
"""
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, {}))
enable_logging(hass)
if enable_log:
enable_logging(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]
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):
_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.
"""
if hass is None:
hass = homeassistant.HomeAssistant()
hass = core.HomeAssistant()
# 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)
return from_config_dict(config_dict, hass)
return from_config_dict(config_dict, hass, enable_log=False)
def enable_logging(hass):
@ -222,7 +237,8 @@ def enable_logging(hass):
}
))
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
err_log_path = hass.config.path('home-assistant.log')

View File

@ -17,7 +17,7 @@ Each component should publish services only under its own domain.
import itertools as it
import logging
import homeassistant as ha
import homeassistant.core as ha
import homeassistant.util as util
from homeassistant.helpers import extract_entity_ids
from homeassistant.loader import get_component

View File

@ -9,7 +9,7 @@ import logging
import threading
import json
import homeassistant as ha
import homeassistant.core as ha
from homeassistant.helpers.state import TrackStates
import homeassistant.remote as rem
from homeassistant.const import (

View File

@ -6,7 +6,7 @@ Allows to setup simple automation rules via the config file.
"""
import logging
from homeassistant.loader import get_component
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers import config_per_platform
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID
@ -25,9 +25,10 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Sets up automation. """
success = False
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:
_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)):
_LOGGER.info(
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
success = True
else:
_LOGGER.error(
"Error setting up rule %s", p_config.get(CONF_ALIAS, ""))
return True
return success
def _get_action(hass, config):
@ -56,13 +58,16 @@ def _get_action(hass, config):
service_data = config.get(CONF_SERVICE_DATA, {})
if not isinstance(service_data, dict):
_LOGGER.error(
"%s should be a serialized JSON object", CONF_SERVICE_DATA)
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
if CONF_SERVICE_ENTITY_ID in config:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID].split(",")
try:
service_data[ATTR_ENTITY_ID] = \
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)

View 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

View File

@ -6,6 +6,7 @@ Offers state listening automation rules.
"""
import logging
from homeassistant.helpers.event import track_state_change
from homeassistant.const import MATCH_ALL
@ -30,7 +31,7 @@ def register(hass, config, action):
""" Listens for state changes and calls action. """
action()
hass.states.track_change(
entity_id, state_automation_listener, from_state, to_state)
track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
return True

View File

@ -5,6 +5,7 @@ homeassistant.components.automation.time
Offers time listening automation rules.
"""
from homeassistant.util import convert
from homeassistant.helpers.event import track_time_change
CONF_HOURS = "time_hours"
CONF_MINUTES = "time_minutes"
@ -21,8 +22,7 @@ def register(hass, config, action):
""" Listens for time changes and calls action. """
action()
hass.track_time_change(
time_automation_listener,
hour=hours, minute=minutes, second=seconds)
track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)
return True

View File

@ -8,7 +8,7 @@ This is more a proof of concept.
import logging
import re
import homeassistant
from homeassistant import core
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
@ -52,16 +52,14 @@ def setup(hass, config):
return
if command == 'on':
hass.services.call(
homeassistant.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
hass.services.call(core.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
elif command == 'off':
hass.services.call(
homeassistant.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
hass.services.call(core.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
else:
logger.error(

View File

@ -6,15 +6,15 @@ Sets up a demo environment that mimics interaction with devices.
"""
import time
import homeassistant as ha
import homeassistant.core as ha
import homeassistant.bootstrap as bootstrap
import homeassistant.loader as loader
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"
DEPENDENCIES = []
DEPENDENCIES = ['introduction', 'conversation']
COMPONENTS_WITH_DEMO_PLATFORM = [
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
@ -48,8 +48,11 @@ def setup(hass, config):
# Setup room groups
lights = hass.states.entity_ids('light')
switches = hass.states.entity_ids('switch')
group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0]])
group.setup_group(hass, 'bedroom', [lights[2], switches[1]])
media_players = sorted(hass.states.entity_ids('media_player'))
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
bootstrap.setup_component(
@ -102,10 +105,10 @@ def setup(hass, config):
# Setup fake device tracker
hass.states.set("device_tracker.paulus", "home",
{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",
{ATTR_ENTITY_PICTURE:
"http://graph.facebook.com/621994601/picture"})
{ATTR_FRIENDLY_NAME: 'Anne Therese'})
hass.states.set("group.all_devices", "home",
{

View File

@ -8,6 +8,7 @@ the state of the sun and devices.
import logging
from datetime import timedelta
from homeassistant.helpers.event import track_point_in_time, track_state_change
import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from . import light, sun, device_tracker, group
@ -91,14 +92,14 @@ def setup(hass, config):
if start_point:
for index, light_id in enumerate(light_ids):
hass.track_point_in_time(turn_on(light_id),
(start_point +
index * LIGHT_TRANSITION_TIME))
track_point_in_time(
hass, turn_on(light_id),
(start_point + index * LIGHT_TRANSITION_TIME))
# Track every time sun rises so we can schedule a time-based
# pre-sun set event
hass.states.track_change(sun.ENTITY_ID, schedule_light_on_sun_rise,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
# If the sun is already above horizon
# schedule the time-based pre-sun set event
@ -157,13 +158,13 @@ def setup(hass, config):
light.turn_off(hass, light_ids)
# Track home coming of each device
hass.states.track_change(
device_entity_ids, check_light_on_dev_state_change,
track_state_change(
hass, device_entity_ids, check_light_on_dev_state_change,
STATE_NOT_HOME, STATE_HOME)
# Track when all devices are gone to shut down lights
hass.states.track_change(
device_group, check_light_on_dev_state_change,
track_state_change(
hass, device_group, check_light_on_dev_state_change,
STATE_HOME, STATE_NOT_HOME)
return True

View File

@ -12,9 +12,11 @@ from datetime import timedelta
from homeassistant.loader import get_component
from homeassistant.helpers import validate_config
from homeassistant.helpers.entity import _OVERWRITE
import homeassistant.util as util
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM, DEVICE_DEFAULT_NAME)
@ -65,14 +67,15 @@ def setup(hass, config):
'device_tracker.{}'.format(tracker_type))
if tracker_implementation is None:
_LOGGER.error("Unknown device_tracker type specified.")
_LOGGER.error("Unknown device_tracker type specified: %s.",
tracker_type)
return False
device_scanner = tracker_implementation.get_scanner(hass, config)
if device_scanner is None:
_LOGGER.error("Failed to initialize device scanner for %s",
_LOGGER.error("Failed to initialize device scanner: %s",
tracker_type)
return False
@ -134,7 +137,7 @@ class DeviceTracker(object):
seconds = range(0, 60, 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,
SERVICE_DEVICE_TRACKER_RELOAD,
@ -160,9 +163,12 @@ class DeviceTracker(object):
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(
dev_info['entity_id'], state,
dev_info['state_attr'])
dev_info['entity_id'], state, attr)
def update_devices(self, now):
""" Update device states based on the found devices. """

View 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

View 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

View File

@ -35,7 +35,6 @@ from datetime import timedelta
import threading
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
@ -43,20 +42,21 @@ from homeassistant.components.device_tracker import DOMAIN
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear>=0.1']
REQUIREMENTS = ['pynetgear==0.3']
def get_scanner(hass, config):
""" Validates config and returns a Netgear scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
info = config[DOMAIN]
host = info.get(CONF_HOST)
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
info = config[DOMAIN]
scanner = NetgearDeviceScanner(
info[CONF_HOST], info[CONF_USERNAME], info[CONF_PASSWORD])
scanner = NetgearDeviceScanner(host, username, password)
return scanner if scanner.success_init else None
@ -68,16 +68,24 @@ class NetgearDeviceScanner(object):
import pynetgear
self.last_results = []
self._api = pynetgear.Netgear(host, username, password)
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")
self.success_init = self._api.login()
results = self._api.get_attached_devices()
self.success_init = results is not None
if self.success_init:
self._update_info()
self.last_results = results
else:
_LOGGER.error("Failed to Login")

View File

@ -26,8 +26,12 @@ from collections import namedtuple
import subprocess
import re
from libnmap.process import NmapProcess
from libnmap.parser import NmapParser, NmapParserException
try:
from libnmap.process import NmapProcess
from libnmap.parser import NmapParser, NmapParserException
LIB_LOADED = True
except ImportError:
LIB_LOADED = False
import homeassistant.util.dt as dt_util
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
CONF_HOME_INTERVAL = "home_interval"
REQUIREMENTS = ['python-libnmap==0.6.1']
def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """
@ -50,6 +56,10 @@ def get_scanner(hass, config):
_LOGGER):
return None
if not LIB_LOADED:
_LOGGER.error("Error while importing dependency python-libnmap.")
return False
scanner = NmapDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None

View 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

View File

@ -31,6 +31,7 @@ password
The password for your given admin account.
"""
import base64
import logging
from datetime import timedelta
import re
@ -55,7 +56,10 @@ def get_scanner(hass, config):
_LOGGER):
return None
scanner = TplinkDeviceScanner(config[DOMAIN])
scanner = Tplink2DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -115,3 +119,63 @@ class TplinkDeviceScanner(object):
return True
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

View File

@ -19,7 +19,7 @@ from homeassistant.const import (
DOMAIN = "discovery"
DEPENDENCIES = []
REQUIREMENTS = ['netdisco>=0.1']
REQUIREMENTS = ['netdisco==0.3']
SCAN_INTERVAL = 300 # seconds
@ -28,11 +28,13 @@ SCAN_INTERVAL = 300 # seconds
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_HANDLERS = {
SERVICE_WEMO: "switch",
SERVICE_CAST: "media_player",
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
}
@ -77,6 +79,13 @@ def setup(hass, config):
if not component:
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.
if not bootstrap.setup_component(hass, component, config):
return

View File

@ -5,24 +5,46 @@
<title>Home Assistant</title>
<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='icon' type='image/png'
href='/static/favicon-192x192.png' sizes='192x192'>
<link rel='apple-touch-icon' sizes='180x180'
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'>
</head>
<body fullbleed>
<h3 id='init' align='center'>Initializing Home Assistant</h3>
<script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />
<home-assistant auth='{{ auth }}'></home-assistant>
<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>
<body fullbleed>
<div id='init'>
<img src='/static/splash.png' height='230' />
<p>Initializing</p>
</div>
<script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />
<home-assistant auth='{{ auth }}'></home-assistant>
</body>
</html>

View File

@ -1,2 +1,2 @@
""" 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -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

View File

@ -5,8 +5,9 @@ homeassistant.components.group
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.event import track_state_change
from homeassistant.helpers.entity import Entity
import homeassistant.util as util
from homeassistant.const import (
@ -102,10 +103,8 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config):
""" Sets up all groups found definded in the configuration. """
for name, entity_ids in config.get(DOMAIN, {}).items():
# Support old deprecated method - 2/28/2015
if isinstance(entity_ids, str):
entity_ids = entity_ids.split(",")
setup_group(hass, name, entity_ids)
return True
@ -162,8 +161,8 @@ class Group(Entity):
def start(self):
""" Starts the tracking. """
self.hass.states.track_change(
self.tracking, self._state_changed_listener)
track_state_change(
self.hass, self.tracking, self._state_changed_listener)
def stop(self):
""" Unregisters the group from Home Assistant. """

View File

@ -86,7 +86,7 @@ from http import cookies
from socketserver import ThreadingMixIn
from urllib.parse import urlparse, parse_qs
import homeassistant as ha
import homeassistant.core as ha
from homeassistant.const import (
SERVER_PORT, CONTENT_TYPE_JSON,
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):
""" Sets up the HTTP API and debug interface. """
if config is None or DOMAIN not in config:
config = {DOMAIN: {}}
@ -139,9 +138,14 @@ def setup(hass, config=None):
sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True)
server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password,
development, no_password_set, sessions_enabled)
try:
server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password,
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(
ha.EVENT_HOMEASSISTANT_START,
@ -183,10 +187,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
_LOGGER.info("running http in development mode")
def start(self):
""" Starts the server. """
self.hass.bus.listen_once(
ha.EVENT_HOMEASSISTANT_STOP,
lambda event: self.shutdown())
""" Starts the HTTP server. """
def stop_http(event):
""" Stops the HTTP server. """
self.shutdown()
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
_LOGGER.info(
"Starting web interface at http://%s:%d", *self.server_address)
@ -199,7 +205,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.serve_forever()
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))

View 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

View File

@ -1,15 +1,15 @@
"""
homeassistant.components.isy994
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
For configuration details please visit the documentation for this component at
https://home-assistant.io/components/isy994.html
"""
# system imports
import logging
from urllib.parse import urlparse
# homeassistant imports
from homeassistant import bootstrap
from homeassistant.loader import get_component
from homeassistant.helpers import validate_config
@ -19,10 +19,9 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED,
ATTR_FRIENDLY_NAME)
# homeassistant constants
DOMAIN = "isy994"
DEPENDENCIES = []
REQUIREMENTS = ['PyISY>=1.0.5']
REQUIREMENTS = ['PyISY==1.0.5']
DISCOVER_LIGHTS = "isy994.lights"
DISCOVER_SWITCHES = "isy994.switches"
DISCOVER_SENSORS = "isy994.sensors"
@ -31,7 +30,6 @@ SENSOR_STRING = 'Sensor'
HIDDEN_STRING = '{HIDE ME}'
CONF_TLS_VER = 'tls'
# setup logger
_LOGGER = logging.getLogger(__name__)
@ -158,6 +156,12 @@ class ISYDeviceABC(ToggleEntity):
attr = {ATTR_FRIENDLY_NAME: self.name}
for name, prop in self._attrs.items():
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
@property

View File

@ -14,7 +14,7 @@ from homeassistant.const import (
DOMAIN = "keyboard"
DEPENDENCIES = []
REQUIREMENTS = ['pyuserinput>=0.1.9']
REQUIREMENTS = ['pyuserinput==0.1.9']
def volume_up(hass):

View File

@ -1,6 +1,6 @@
"""
homeassistant.components.light
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with lights.
@ -166,26 +166,25 @@ def setup(hass, config):
profiles = {}
for profile_path in profile_paths:
if not os.path.isfile(profile_path):
continue
with open(profile_path) as inp:
reader = csv.reader(inp)
if os.path.isfile(profile_path):
with open(profile_path) as inp:
reader = csv.reader(inp)
# Skip the header
next(reader, None)
# Skip the header
next(reader, None)
try:
for profile_id, color_x, color_y, brightness in reader:
profiles[profile_id] = (float(color_x), float(color_y),
int(brightness))
except ValueError:
# ValueError if not 4 values per row
# ValueError if convert to float/int failed
_LOGGER.error(
"Error parsing light profiles from %s", profile_path)
try:
for profile_id, color_x, color_y, brightness in reader:
profiles[profile_id] = (float(color_x), float(color_y),
int(brightness))
except ValueError:
# ValueError if not 4 values per row
# ValueError if convert to float/int failed
_LOGGER.error(
"Error parsing light profiles from %s", profile_path)
return False
return False
def handle_light_service(service):
""" Hande a turn light on or off service call. """
@ -206,66 +205,70 @@ def setup(hass, config):
for light in target_lights:
light.turn_off(**params)
else:
# Processing extra data for turn light on request
# We process the profile first so that we get the desired
# behavior that extra service data attributes overwrite
# profile values
profile = profiles.get(dat.get(ATTR_PROFILE))
if profile:
*params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile
if ATTR_BRIGHTNESS in dat:
# We pass in the old value as the default parameter if parsing
# of the new one goes wrong.
params[ATTR_BRIGHTNESS] = util.convert(
dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS))
if ATTR_XY_COLOR in dat:
try:
# xy_color should be a list containing 2 floats
xycolor = dat.get(ATTR_XY_COLOR)
# Without this check, a xycolor with value '99' would work
if not isinstance(xycolor, str):
params[ATTR_XY_COLOR] = [float(val) for val in xycolor]
except (TypeError, ValueError):
# TypeError if xy_color is not iterable
# ValueError if value could not be converted to float
pass
if ATTR_RGB_COLOR in dat:
try:
# rgb_color should be a list containing 3 ints
rgb_color = dat.get(ATTR_RGB_COLOR)
if len(rgb_color) == 3:
params[ATTR_XY_COLOR] = \
color_util.color_RGB_to_xy(int(rgb_color[0]),
int(rgb_color[1]),
int(rgb_color[2]))
except (TypeError, ValueError):
# TypeError if rgb_color is not iterable
# ValueError if not all values can be converted to int
pass
if ATTR_FLASH in dat:
if dat[ATTR_FLASH] == FLASH_SHORT:
params[ATTR_FLASH] = FLASH_SHORT
elif dat[ATTR_FLASH] == FLASH_LONG:
params[ATTR_FLASH] = FLASH_LONG
if ATTR_EFFECT in dat:
if dat[ATTR_EFFECT] == EFFECT_COLORLOOP:
params[ATTR_EFFECT] = EFFECT_COLORLOOP
for light in target_lights:
light.turn_on(**params)
if light.should_poll:
light.update_ha_state(True)
return
# Processing extra data for turn light on request
# We process the profile first so that we get the desired
# behavior that extra service data attributes overwrite
# profile values
profile = profiles.get(dat.get(ATTR_PROFILE))
if profile:
*params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile
if ATTR_BRIGHTNESS in dat:
# We pass in the old value as the default parameter if parsing
# of the new one goes wrong.
params[ATTR_BRIGHTNESS] = util.convert(
dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS))
if ATTR_XY_COLOR in dat:
try:
# xy_color should be a list containing 2 floats
xycolor = dat.get(ATTR_XY_COLOR)
# Without this check, a xycolor with value '99' would work
if not isinstance(xycolor, str):
params[ATTR_XY_COLOR] = [float(val) for val in xycolor]
except (TypeError, ValueError):
# TypeError if xy_color is not iterable
# ValueError if value could not be converted to float
pass
if ATTR_RGB_COLOR in dat:
try:
# rgb_color should be a list containing 3 ints
rgb_color = dat.get(ATTR_RGB_COLOR)
if len(rgb_color) == 3:
params[ATTR_XY_COLOR] = \
color_util.color_RGB_to_xy(int(rgb_color[0]),
int(rgb_color[1]),
int(rgb_color[2]))
except (TypeError, ValueError):
# TypeError if rgb_color is not iterable
# ValueError if not all values can be converted to int
pass
if ATTR_FLASH in dat:
if dat[ATTR_FLASH] == FLASH_SHORT:
params[ATTR_FLASH] = FLASH_SHORT
elif dat[ATTR_FLASH] == FLASH_LONG:
params[ATTR_FLASH] = FLASH_LONG
if ATTR_EFFECT in dat:
if dat[ATTR_EFFECT] == EFFECT_COLORLOOP:
params[ATTR_EFFECT] = EFFECT_COLORLOOP
for light in target_lights:
light.turn_on(**params)
for light in target_lights:
if light.should_poll:

View File

@ -1,4 +1,8 @@
""" Support for Hue lights. """
"""
homeassistant.components.light.hue
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Hue lights.
"""
import logging
import socket
from datetime import timedelta
@ -12,7 +16,7 @@ from homeassistant.components.light import (
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT,
EFFECT_COLORLOOP)
REQUIREMENTS = ['phue>=0.8']
REQUIREMENTS = ['phue==0.8']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
@ -35,7 +39,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
return
if discovery_info is not None:
host = urlparse(discovery_info).hostname
host = urlparse(discovery_info[1]).hostname
else:
host = config.get(CONF_HOST, None)

View File

@ -1,8 +1,10 @@
""" Support for ISY994 lights. """
# system imports
"""
homeassistant.components.light.isy994
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for ISY994 lights.
"""
import logging
# homeassistant imports
from homeassistant.components.isy994 import (ISYDeviceABC, ISY, SENSOR_STRING,
HIDDEN_STRING)
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):
""" Sets up the isy994 platform. """
""" Sets up the ISY994 platform. """
logger = logging.getLogger(__name__)
devs = []
# verify connection
@ -29,10 +31,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ISYLightDevice(ISYDeviceABC):
""" represents as isy light within home assistant. """
""" Represents as ISY light. """
_domain = 'light'
_dtype = 'analog'
_attrs = {ATTR_BRIGHTNESS: 'value'}
_onattrs = [ATTR_BRIGHTNESS]
_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

View File

@ -1,16 +1,21 @@
"""
homeassistant.components.light.limitlessled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for LimitlessLED bulbs, also known as...
EasyBulb
AppLight
AppLamp
MiLight
LEDme
dekolight
iLight
- EasyBulb
- AppLight
- AppLamp
- MiLight
- LEDme
- dekolight
- iLight
Configuration:
To use limitlessled you will need to add the following to your
config/configuration.yaml.
light:
platform: limitlessled
@ -24,10 +29,12 @@ light:
import logging
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__)
REQUIREMENTS = ['ledcontroller>=1.0.7']
REQUIREMENTS = ['ledcontroller==1.0.7']
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._state = False
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
def should_poll(self):
@ -72,6 +102,22 @@ class LimitlessLED(Light):
def brightness(self):
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
def is_on(self):
""" True if device is on. """
@ -84,7 +130,11 @@ class LimitlessLED(Light):
if ATTR_BRIGHTNESS in kwargs:
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()
def turn_off(self, **kwargs):

View File

@ -1,13 +1,19 @@
""" Support for Tellstick lights. """
"""
homeassistant.components.light.tellstick
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Tellstick lights.
"""
import logging
# pylint: disable=no-name-in-module, import-error
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.const import ATTR_FRIENDLY_NAME
import tellcore.constants as tellcore_constants
REQUIREMENTS = ['tellcore-py==1.0.4']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return tellstick lights. """
""" Find and return Tellstick lights. """
try:
import tellcore.telldus as telldus
@ -27,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class TellstickLight(Light):
""" Represents a tellstick light """
""" Represents a Tellstick light. """
last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
tellcore_constants.TELLSTICK_TURNOFF |
tellcore_constants.TELLSTICK_DIM |

View File

@ -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:
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
your config/configuration.yaml
your config/configuration.yaml.
light:
platform: vera
@ -19,22 +21,19 @@ light:
13:
name: Another switch
VARIABLES:
Variables:
vera_controller_url
*Required
This is the base URL of your vera controller including the port number if not
running on 80
Example: http://192.168.1.21:3480/
running on 80. Example: http://192.168.1.21:3480/
device_data
*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
will be added with default values. You should use the id of your vera device
as the key for the device within device_data
will be added with default values. You should use the id of your vera device
as the key for the device within device_data.
These are the variables for the device_data array:
@ -42,13 +41,12 @@ name
*Optional
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
will be used
will be used.
exclude
*Optional
This parameter allows you to exclude the specified device from homeassistant,
it should be set to "true" if you want this device excluded
This parameter allows you to exclude the specified device from Home Assistant,
it should be set to "true" if you want this device excluded.
"""
import logging

View File

@ -1,10 +1,17 @@
""" Support for Wink lights. """
"""
homeassistant.components.light.wink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Wink lights.
"""
import logging
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.wink import WinkToggleDevice
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):
""" Find and return Wink lights. """
@ -26,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class WinkLight(WinkToggleDevice):
""" Represents a Wink light """
""" Represents a Wink light. """
# pylint: disable=too-few-public-methods
def turn_on(self, **kwargs):

View File

@ -8,7 +8,7 @@ from datetime import timedelta
from itertools import groupby
import re
from homeassistant import State, DOMAIN as HA_DOMAIN
from homeassistant.core import State, DOMAIN as HA_DOMAIN
from homeassistant.const import (
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)

View File

@ -98,9 +98,7 @@ ATTR_TO_PROPERTY = [
def is_on(hass, entity_id=None):
""" Returns true if specified media player entity_id is on.
Will check all media player if no entity_id specified. """
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(not hass.states.is_state(entity_id, STATE_OFF)
for entity_id in entity_ids)
@ -108,28 +106,24 @@ def is_on(hass, entity_id=None):
def turn_on(hass, entity_id=None):
""" Will turn on specified media player or all. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
def turn_off(hass, entity_id=None):
""" Will turn off specified media player or all. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def volume_up(hass, entity_id=None):
""" Send the media player the command for volume up. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
def volume_down(hass, entity_id=None):
""" Send the media player the command for volume down. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
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):
""" Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
def media_play(hass, entity_id=None):
""" Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
def media_pause(hass, entity_id=None):
""" Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
def media_next_track(hass, entity_id=None):
""" Send the media player the command for next track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
def media_previous_track(hass, entity_id=None):
""" Send the media player the command for prev track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
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)
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. """
target_players = component.extract_from_service(service)
if media_id is None:
service.data.get('video')
if media_id:
for player in target_players:
player.play_youtube(media_id)
if media_id is None:
return
if player.should_poll:
player.update_ha_state(True)
for player in component.extract_from_service(service):
player.play_youtube(media_id)
hass.services.register(DOMAIN, "start_fireplace",
lambda service:
play_youtube_video_service(service, "eyU3bRy2x44"))
if player.should_poll:
player.update_ha_state(True)
hass.services.register(DOMAIN, "start_epic_sax",
lambda service:
play_youtube_video_service(service, "kxopViU98Xo"))
hass.services.register(
DOMAIN, "start_fireplace",
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"))
hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO,
lambda service:
play_youtube_video_service(
service, service.data.get('video')))
hass.services.register(
DOMAIN, "start_epic_sax",
lambda service: play_youtube_video_service(service, "kxopViU98Xo"))
hass.services.register(
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service)
return True

View File

@ -8,14 +8,9 @@ WARNING: This platform is currently not working due to a changed Cast API
"""
import logging
try:
import pychromecast
except ImportError:
pychromecast = None
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
STATE_UNKNOWN)
STATE_UNKNOWN, CONF_HOST)
from homeassistant.components.media_player import (
MediaPlayerDevice,
@ -24,7 +19,7 @@ from homeassistant.components.media_player import (
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
REQUIREMENTS = ['pychromecast>=0.6.9']
REQUIREMENTS = ['pychromecast==0.6.12']
CONF_IGNORE_CEC = 'ignore_cec'
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
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
KNOWN_HOSTS = []
# pylint: disable=invalid-name
cast = None
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the cast platform. """
global pychromecast # pylint: disable=invalid-name
if pychromecast is None:
import pychromecast as pychromecast_
pychromecast = pychromecast_
global cast
import pychromecast
cast = pychromecast
logger = logging.getLogger(__name__)
# import CEC IGNORE attributes
ignore_cec = config.get(CONF_IGNORE_CEC, [])
if isinstance(ignore_cec, list):
pychromecast.IGNORE_CEC += ignore_cec
cast.IGNORE_CEC += ignore_cec
else:
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:
hosts = [discovery_info[0]]
elif CONF_HOST in config:
hosts = [config[CONF_HOST]]
else:
hosts = (host_port[0] for host_port
in pychromecast.discover_chromecasts()
in cast.discover_chromecasts()
if host_port[0] not in KNOWN_HOSTS)
casts = []
@ -65,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for host in hosts:
try:
casts.append(CastDevice(host))
except pychromecast.ChromecastConnectionError:
except cast.ChromecastConnectionError:
pass
else:
KNOWN_HOSTS.append(host)
@ -80,7 +80,7 @@ class CastDevice(MediaPlayerDevice):
def __init__(self, host):
import pychromecast.controllers.youtube as youtube
self.cast = pychromecast.Chromecast(host)
self.cast = cast.Chromecast(host)
self.youtube = youtube.YouTubeController()
self.cast.register_handler(self.youtube)
@ -226,7 +226,7 @@ class CastDevice(MediaPlayerDevice):
self.cast.quit_app()
self.cast.play_media(
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
CAST_SPLASH, cast.STREAM_TYPE_BUFFERED)
def turn_off(self):
""" Turns Chromecast off. """

View File

@ -1,12 +1,12 @@
"""
homeassistant.components.media_player.kodi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the XBMC/Kodi JSON-RPC API
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:
platform: kodi
@ -19,7 +19,7 @@ Variables:
name
*Optional
The name of the device
The name of the device.
url
*Required
@ -27,13 +27,12 @@ The URL of the XBMC/Kodi JSON-RPC API. Example: http://192.168.0.123/jsonrpc
user
*Optional
The XBMC/Kodi HTTP username
The XBMC/Kodi HTTP username.
password
*Optional
The XBMC/Kodi HTTP password
The XBMC/Kodi HTTP password.
"""
import urllib
import logging
@ -49,7 +48,7 @@ except ImportError:
jsonrpc_requests = None
_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_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK

View File

@ -48,7 +48,7 @@ from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC)
_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_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK

View 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()

View File

@ -5,6 +5,11 @@ Modbus component, using pymodbus (python3 branch)
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 your config/configuration.yaml
@ -33,6 +38,8 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
DOMAIN = "modbus"
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' +
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip']
# Type of network
MEDIUM = "type"

View 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 != '+'))

View File

@ -4,12 +4,13 @@ homeassistant.components.notify
Provides functionality to notify people.
"""
from functools import partial
import logging
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"
DEPENDENCIES = []
@ -33,42 +34,45 @@ def send_message(hass, message):
def setup(hass, config):
""" Sets up notify services. """
success = False
if not validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER):
return False
for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER):
# get platform
notify_implementation = get_component(
'notify.{}'.format(platform))
platform = config[DOMAIN].get(CONF_PLATFORM)
if notify_implementation is None:
_LOGGER.error("Unknown notification service specified.")
continue
notify_implementation = get_component(
'notify.{}'.format(platform))
# create platform service
notify_service = notify_implementation.get_service(
hass, {DOMAIN: p_config})
if notify_implementation is None:
_LOGGER.error("Unknown notification service specified.")
if notify_service is None:
_LOGGER.error("Failed to initialize notification service %s",
platform)
continue
return False
# create service handler
def notify_message(notify_service, call):
""" Handle sending notification message service calls. """
message = call.data.get(ATTR_MESSAGE)
notify_service = notify_implementation.get_service(hass, config)
if message is None:
return
if notify_service is None:
_LOGGER.error("Failed to initialize notification service %s",
platform)
title = call.data.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
return False
notify_service.send_message(message, title=title)
def notify_message(call):
""" Handle sending notification message service calls. """
message = call.data.get(ATTR_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
if message is None:
return
title = call.data.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
notify_service.send_message(message, title=title)
hass.services.register(DOMAIN, SERVICE_NOTIFY, notify_message)
return True
return success
# pylint: disable=too-few-public-methods

View File

@ -28,7 +28,7 @@ from homeassistant.components.notify import (
from homeassistant.const import CONF_API_KEY
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pushbullet.py>=0.7.1']
REQUIREMENTS = ['pushbullet.py==0.7.1']
def get_service(hass, config):

View File

@ -42,7 +42,7 @@ from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService)
from homeassistant.const import CONF_API_KEY
REQUIREMENTS = ['python-pushover>=0.2']
REQUIREMENTS = ['python-pushover==0.2']
_LOGGER = logging.getLogger(__name__)

View 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)

View File

@ -1,7 +1,6 @@
"""
homeassistant.components.notify.xmpp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jabber (XMPP) notification service.
Configuration:
@ -29,7 +28,6 @@ The password for your given Jabber account.
recipient
*Required
The Jabber ID (JID) that will receive the messages.
"""
import logging
@ -47,7 +45,7 @@ from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService)
REQUIREMENTS = ['sleekxmpp>=1.3.1']
REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0']
def get_service(hass, config):

View File

@ -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

View File

@ -13,7 +13,7 @@ from datetime import datetime, date
import json
import atexit
from homeassistant import Event, EventOrigin, State
from homeassistant.core import Event, EventOrigin, State
import homeassistant.util.dt as date_util
from homeassistant.remote import JSONEncoder
from homeassistant.const import (

View File

@ -18,7 +18,8 @@ old state will not be restored when it is being deactivated.
import logging
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_component import EntityComponent
from homeassistant.helpers.state import reproduce_state
@ -104,8 +105,8 @@ class Scene(ToggleEntity):
self.prev_states = None
self.ignore_updates = False
self.hass.states.track_change(
self.entity_ids, self.entity_state_changed)
track_state_change(
self.hass, self.entity_ids, self.entity_state_changed)
self.update()

View File

@ -131,7 +131,7 @@ class ServiceEventListener(EventListener):
def execute(self, hass):
""" Call the service. """
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
self.schedule(hass)

View File

@ -17,6 +17,7 @@ from datetime import timedelta
import logging
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components.scheduler import ServiceEventListener
_LOGGER = logging.getLogger(__name__)
@ -62,7 +63,7 @@ class TimeEventListener(ServiceEventListener):
""" Call the execute method """
self.execute(hass)
hass.track_point_in_time(execute, next_time)
track_point_in_time(hass, execute, next_time)
_LOGGER.info(
'TimeEventListener scheduled for %s, will call service %s.%s',

View File

@ -10,6 +10,7 @@ from datetime import timedelta
import homeassistant.util.dt as date_util
import threading
from homeassistant.helpers.event import track_point_in_time
from homeassistant.util import split_entity_id
from homeassistant.const import (
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:
delay = timedelta(**action[CONF_DELAY])
point_in_time = date_util.now() + delay
self.listener = self.hass.track_point_in_time(
self, point_in_time)
self.listener = track_point_in_time(
self.hass, self, point_in_time)
return False
return True

View File

@ -6,7 +6,7 @@ Component to interface with various sensors that can be monitored.
import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import wink, zwave, isy994
from homeassistant.components import wink, zwave, isy994, verisure
DOMAIN = 'sensor'
DEPENDENCIES = []
@ -18,7 +18,8 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = {
wink.DISCOVER_SENSORS: 'wink',
zwave.DISCOVER_SENSORS: 'zwave',
isy994.DISCOVER_SENSORS: 'isy994'
isy994.DISCOVER_SENSORS: 'isy994',
verisure.DISCOVER_SENSORS: 'verisure'
}

View File

@ -71,7 +71,7 @@ from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['blockchain>=1.1.2']
REQUIREMENTS = ['blockchain==1.1.2']
_LOGGER = logging.getLogger(__name__)
OPTION_TYPES = {
'wallet': ['Wallet balance', 'BTC'],

View File

@ -15,6 +15,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12),
DemoSensor('Outside Humidity', 54, '%', None),
DemoSensor('Alarm back', 'Armed', None, None),
])

View 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

View File

@ -1,14 +1,13 @@
"""
homeassistant.components.sensor.efergy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Monitors home energy use as measured by an efergy
engage hub using its (unofficial, undocumented) API.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Monitors home energy use as measured by an efergy engage hub using its
(unofficial, undocumented) API.
Configuration:
To use the efergy sensor you will need to add something
like the following to your config/configuration.yaml
To use the efergy sensor you will need to add something like the following
to your config/configuration.yaml
sensor:
platform: efergy
@ -39,13 +38,13 @@ An array specifying the variables to monitor.
period
*Optional
Some variables take a period argument. Valid options are "day",
1"week", "month", and "year"
Some variables take a period argument. Valid options are "day", "week",
"month", and "year".
currency
*Optional
This is used to display the cost/period as the unit when monitoring the
cost. It should correspond to the actual currency used in your dashboard.
cost. It should correspond to the actual currency used in your dashboard.
"""
import logging
from requests import get

View File

@ -1,7 +1,6 @@
"""
homeassistant.components.sensor.forecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Forecast.io service.
Configuration:
@ -50,6 +49,8 @@ Details for the API : https://developer.forecast.io/docs/v2
import logging
from datetime import timedelta
REQUIREMENTS = ['python-forecastio==1.3.3']
try:
import forecastio
except ImportError:
@ -121,10 +122,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-few-public-methods
class ForeCastSensor(Entity):
""" Implements an OpenWeatherMap sensor. """
""" Implements an Forecast.io sensor. """
def __init__(self, weather_data, sensor_type, unit):
self.client_name = 'Forecast'
self.client_name = 'Weather'
self._name = SENSOR_TYPES[sensor_type][0]
self.forecast_client = weather_data
self._unit = unit
@ -135,7 +136,7 @@ class ForeCastSensor(Entity):
@property
def name(self):
return '{} - {}'.format(self.client_name, self._name)
return '{} {}'.format(self.client_name, self._name)
@property
def state(self):
@ -157,10 +158,6 @@ class ForeCastSensor(Entity):
try:
if self.type == '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':
if data.precipIntensity == 0:
self._state = 'None'
@ -220,5 +217,6 @@ class ForeCastData(object):
forecast = forecastio.load_forecast(self._api_key,
self.latitude,
self.longitude)
self.longitude,
units='si')
self.data = forecast.currently()

View File

@ -1,4 +1,6 @@
"""
homeassistant.components.modbus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Modbus sensors.
Configuration:
@ -18,16 +20,32 @@ sensor:
name: My boolean sensor
2:
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)
- "unit" = unit to attach to value (optional, ignored for boolean sensors)
- "registers" contains a list of relevant registers to read from
it can contain a "bits" section, listing relevant bits
slave
*Required
Slave number (ignored and can be omitted if not serial Modbus).
- each named register will create an integer sensor
- each named bit will create a boolean sensor
unit
*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
@ -39,6 +57,7 @@ from homeassistant.const import (
STATE_ON, STATE_OFF)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['modbus']
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -49,21 +68,30 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error("No slave number provided for serial Modbus")
return False
registers = config.get("registers")
for regnum, register in registers.items():
if register.get("name"):
sensors.append(ModbusSensor(register.get("name"),
if registers:
for regnum, register in registers.items():
if register.get("name"):
sensors.append(ModbusSensor(register.get("name"),
slave,
regnum,
None,
register.get("unit")))
if register.get("bits"):
bits = register.get("bits")
for bitnum, bit in bits.items():
if bit.get("name"):
sensors.append(ModbusSensor(bit.get("name"),
slave,
regnum,
bitnum))
coils = config.get("coils")
if coils:
for coilnum, coil in coils.items():
sensors.append(ModbusSensor(coil.get("name"),
slave,
regnum,
None,
register.get("unit")))
if register.get("bits"):
bits = register.get("bits")
for bitnum, bit in bits.items():
if bit.get("name"):
sensors.append(ModbusSensor(bit.get("name"),
slave,
regnum,
bitnum))
coilnum,
coil=True))
add_devices(sensors)
@ -71,13 +99,14 @@ class ModbusSensor(Entity):
# pylint: disable=too-many-arguments
""" 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.slave = int(slave) if slave else 1
self.register = int(register)
self.bit = int(bit) if bit else None
self._value = None
self._unit = unit
self._coil = coil
def __str__(self):
return "%s: %s" % (self.name, self.state)
@ -118,19 +147,19 @@ class ModbusSensor(Entity):
else:
return self._unit
@property
def state_attributes(self):
attr = super().state_attributes
return attr
def update(self):
result = modbus.NETWORK.read_holding_registers(unit=self.slave,
address=self.register,
count=1)
val = 0
for i, res in enumerate(result.registers):
val += res * (2**(i*16))
if self.bit:
self._value = val & (0x0001 << self.bit)
""" Update the state of the sensor. """
if self._coil:
result = modbus.NETWORK.read_coils(self.register, 1)
self._value = result.bits[0]
else:
self._value = val
result = modbus.NETWORK.read_holding_registers(
unit=self.slave, address=self.register,
count=1)
val = 0
for i, res in enumerate(result.registers):
val += res * (2**(i*16))
if self.bit:
self._value = val & (0x0001 << self.bit)
else:
self._value = val

View 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

View File

@ -36,15 +36,15 @@ ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/master.zip'
'#egg=pymysensors-0.1']
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/' +
'35b87d880147a34107da0d40cb815d75e6cb4af7.zip']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the mysensors platform. """
import mysensors.mysensors as mysensors
import mysensors.const as const
import mysensors.const_14 as const
devices = {} # keep track of devices added to HA
# Just assume celcius means that the user wants metric for now.

View File

@ -48,7 +48,7 @@ from homeassistant.util import Throttle
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pywm>=2.2.1']
REQUIREMENTS = ['pyowm==2.2.1']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'weather': ['Condition', ''],

View File

@ -1,17 +1,24 @@
"""
homeassistant.components.sensor.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from rfxtrx sensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from RFXtrx sensors.
Possible config keys:
device="path to rfxtrx device"
Configuration:
To use the rfxtrx sensors you will need to add something like the following to
your config/configuration.yaml
Example:
sensor 2:
platform: rfxtrx
device : /dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0
sensor:
platform: rfxtrx
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
from collections import OrderedDict
@ -19,8 +26,8 @@ from collections import OrderedDict
from homeassistant.const import (TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip'
'#RFXtrx>=0.15']
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' +
'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip']
DATA_TYPES = OrderedDict([
('Temperature', TEMP_CELCIUS),
@ -31,7 +38,7 @@ DATA_TYPES = OrderedDict([
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the rfxtrx platform. """
""" Setup the RFXtrx platform. """
logger = logging.getLogger(__name__)
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):
""" Represents a Rfxtrx Sensor. """
""" Represents a RFXtrx sensor. """
def __init__(self, event):
self.event = event

View 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

View File

@ -66,7 +66,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.const import STATE_ON, STATE_OFF
REQUIREMENTS = ['psutil>=3.0.0']
REQUIREMENTS = ['psutil==3.0.0']
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],

View File

@ -35,6 +35,8 @@ import homeassistant.util as util
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
REQUIREMENTS = ['tellcore-py==1.0.4']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):

View 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"')

View File

@ -67,7 +67,7 @@ from transmissionrpc.error import TransmissionError
import logging
REQUIREMENTS = ['transmissionrpc>=0.11']
REQUIREMENTS = ['transmissionrpc==0.11']
SENSOR_TYPES = {
'current_status': ['Status', ''],
'download_speed': ['Down Speed', 'MB/s'],

View 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()

View File

@ -1,9 +1,16 @@
""" Support for Wink sensors. """
"""
homeassistant.components.sensor.wink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Wink sensors.
"""
import logging
from homeassistant.helpers.entity import Entity
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):
""" Sets up the Wink platform. """
@ -24,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkSensorDevice(Entity):
""" represents a wink sensor within home assistant. """
""" Represents a wink sensor. """
def __init__(self, wink):
self.wink = wink

View File

@ -1,7 +1,6 @@
"""
homeassistant.components.sensor.zwave
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Z-Wave sensors.
"""
# pylint: disable=import-error

View File

@ -9,6 +9,7 @@ Provides a simple alarm feature:
import logging
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
DOMAIN = "simple_alarm"
@ -83,8 +84,8 @@ def setup(hass, config):
if not device_tracker.is_on(hass):
unknown_alarm()
hass.states.track_change(
light.ENTITY_ID_ALL_LIGHTS,
track_state_change(
hass, light.ENTITY_ID_ALL_LIGHTS,
unknown_alarm_if_lights_on, STATE_OFF, STATE_ON)
def ring_known_alarm(entity_id, old_state, new_state):
@ -93,8 +94,8 @@ def setup(hass, config):
known_alarm()
# Track home coming of each device
hass.states.track_change(
hass.states.entity_ids(device_tracker.DOMAIN),
track_state_change(
hass, hass.states.entity_ids(device_tracker.DOMAIN),
ring_known_alarm, STATE_NOT_HOME, STATE_HOME)
return True

View File

@ -21,14 +21,17 @@ The sun event need to have the type 'sun', which service to call, which event
"""
import logging
from datetime import timedelta
import urllib
import homeassistant.util as 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.components.scheduler import ServiceEventListener
DEPENDENCIES = []
REQUIREMENTS = ['astral>=0.8.1']
REQUIREMENTS = ['astral==0.8.1']
DOMAIN = "sun"
ENTITY_ID = "sun.sun"
@ -129,8 +132,13 @@ def setup(hass, config):
if elevation is None:
google = GoogleGeocoder()
google._get_elevation(location) # pylint: disable=protected-access
_LOGGER.info('Retrieved elevation from Google: %s', location.elevation)
try:
google._get_elevation(location) # pylint: disable=protected-access
_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.point_in_time_listener(dt_util.utcnow())
@ -203,8 +211,8 @@ class Sun(Entity):
self.update_ha_state()
# Schedule next update at next_change+1 second so sun state has changed
self.hass.track_point_in_utc_time(
self.point_in_time_listener,
track_point_in_utc_time(
self.hass, self.point_in_time_listener,
self.next_change + timedelta(seconds=1))
@ -266,7 +274,7 @@ class SunEventListener(ServiceEventListener):
""" Call the execute method. """
self.execute(hass)
hass.track_point_in_time(execute, next_time)
track_point_in_time(hass, execute, next_time)
return next_time

View File

@ -11,7 +11,7 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (
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'
DEPENDENCIES = []
@ -34,6 +34,7 @@ DISCOVERY_PLATFORMS = {
discovery.SERVICE_WEMO: 'wemo',
wink.DISCOVER_SWITCHES: 'wink',
isy994.DISCOVER_SWITCHES: 'isy994',
verisure.DISCOVER_SWITCHES: 'verisure'
}
PROP_TO_ATTR = {
@ -49,21 +50,18 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None):
""" Returns if the switch is on based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_SWITCHES
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id=None):
""" Turns all or specified switch on. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
def turn_off(hass, entity_id=None):
""" Turns all or specified switch off. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
@ -88,7 +86,6 @@ def setup(hass, config):
switch.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service)
return True

View 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'

View File

@ -1,24 +1,21 @@
"""
homeassistant.components.switch.hikvision
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support turning on/off motion detection on Hikvision cameras.
Note: Currently works using default https port only.
CGI API Guide:
http://bit.ly/1RuyUuF
CGI API Guide: http://bit.ly/1RuyUuF
Configuration:
To use the Hikvision motion detection
switch you will need to add something like the
following to your config/configuration.yaml
To use the Hikvision motion detection switch you will need to add something
like the following to your config/configuration.yaml
switch:
platform: hikvisioncam
name: Hikvision Cam 1 Motion Detection
host: 192.168.1.26
host: 192.168.1.32
username: YOUR_USERNAME
password: YOUR_PASSWORD
@ -30,16 +27,15 @@ This is the IP address of your Hikvision camera. Example: 192.168.1.32
username
*Required
Your Hikvision camera username
Your Hikvision camera username.
password
*Required
Your Hikvision camera username
Your Hikvision camera username.
name
*Optional
The name to use when displaying this switch instance.
"""
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import STATE_ON, STATE_OFF
@ -53,7 +49,7 @@ except ImportError:
hikvision.api = None
_LOGGING = logging.getLogger(__name__)
REQUIREMENTS = ['hikvision>=0.4']
REQUIREMENTS = ['hikvision==0.4']
# pylint: disable=too-many-arguments
# pylint: disable=too-many-instance-attributes

View File

@ -18,12 +18,16 @@ sensor:
name: My switch
2:
name: My other switch
coils:
0:
name: My coil switch
VARIABLES:
- "slave" = slave number (ignored and can be omitted if not serial Modbus)
- "registers" contains a list of relevant registers to read from
- 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
"""
@ -34,6 +38,7 @@ import homeassistant.components.modbus as modbus
from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['modbus']
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -44,25 +49,36 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error("No slave number provided for serial Modbus")
return False
registers = config.get("registers")
for regnum, register in registers.items():
bits = register.get("bits")
for bitnum, bit in bits.items():
if bit.get("name"):
switches.append(ModbusSwitch(bit.get("name"),
slave,
regnum,
bitnum))
if registers:
for regnum, register in registers.items():
bits = register.get("bits")
for bitnum, bit in bits.items():
if bit.get("name"):
switches.append(ModbusSwitch(bit.get("name"),
slave,
regnum,
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)
class ModbusSwitch(ToggleEntity):
# pylint: disable=too-many-arguments
""" Represents a Modbus switch. """
def __init__(self, name, slave, register, bit):
def __init__(self, name, slave, register, bit, coil=False):
self._name = name
self.slave = int(slave) if slave else 1
self.register = int(register)
self.bit = int(bit)
self._coil = coil
self._is_on = None
self.register_value = None
@ -92,33 +108,44 @@ class ModbusSwitch(ToggleEntity):
""" Get the name of the switch. """
return self._name
@property
def state_attributes(self):
attr = super().state_attributes
return attr
def turn_on(self, **kwargs):
""" Set switch on. """
if self.register_value is None:
self.update()
val = self.register_value | (0x0001 << self.bit)
modbus.NETWORK.write_register(unit=self.slave,
address=self.register,
value=val)
if self._coil:
modbus.NETWORK.write_coil(self.register, True)
else:
val = self.register_value | (0x0001 << self.bit)
modbus.NETWORK.write_register(unit=self.slave,
address=self.register,
value=val)
def turn_off(self, **kwargs):
""" Set switch off. """
if self.register_value is None:
self.update()
val = self.register_value & ~(0x0001 << self.bit)
modbus.NETWORK.write_register(unit=self.slave,
address=self.register,
value=val)
if self._coil:
modbus.NETWORK.write_coil(self.register, False)
else:
val = self.register_value & ~(0x0001 << self.bit)
modbus.NETWORK.write_register(unit=self.slave,
address=self.register,
value=val)
def update(self):
result = modbus.NETWORK.read_holding_registers(unit=self.slave,
address=self.register,
count=1)
val = 0
for i, res in enumerate(result.registers):
val += res * (2**(i*16))
self.register_value = val
self._is_on = (val & (0x0001 << self.bit) > 0)
""" Update the state of the switch. """
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)
val = 0
for i, res in enumerate(result.registers):
val += res * (2**(i*16))
self.register_value = val
self._is_on = (val & (0x0001 << self.bit) > 0)

View 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()

View 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

View File

@ -19,6 +19,8 @@ import tellcore.constants as tellcore_constants
SINGAL_REPETITIONS = 1
REQUIREMENTS = ['tellcore-py==1.0.4']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):

View File

@ -1,8 +1,7 @@
"""
homeassistant.components.switch.transmission
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Enable or disable Transmission BitTorrent client Turtle Mode
Enable or disable Transmission BitTorrent client Turtle Mode.
Configuration:
@ -29,37 +28,32 @@ The port your Transmission daemon uses, defaults to 9091. Example: 8080
username
*Optional
Your Transmission username, if you use authentication
Your Transmission username, if you use authentication.
password
*Optional
Your Transmission username, if you use authentication
Your Transmission username, if you use authentication.
name
*Optional
The name to use when displaying this Transmission instance.
"""
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.entity import ToggleEntity
# pylint: disable=no-name-in-module, import-error
import transmissionrpc
from transmissionrpc.error import TransmissionError
import logging
_LOGGING = logging.getLogger(__name__)
REQUIREMENTS = ['transmissionrpc>=0.11']
REQUIREMENTS = ['transmissionrpc==0.11']
# pylint: disable=unused-argument
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)
username = config.get(CONF_USERNAME, 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):
""" A Transmission sensor. """
def __init__(self, transmission_client, name):

View File

@ -1,9 +1,11 @@
"""
homeassistant.components.switch.vera
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Vera switches.
Configuration:
To use the Vera lights you will need to add something like the following to
your config/configuration.yaml
your config/configuration.yaml.
switch:
platform: vera
@ -15,38 +17,32 @@ switch:
13:
name: Another Switch
VARIABLES:
Variables:
vera_controller_url
*Required
This is the base URL of your vera controller including the port number if not
running on 80
Example: http://192.168.1.21:3480/
running on 80. Example: http://192.168.1.21:3480/
device_data
*Optional
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
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:
name
*Optional
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
will be used
will be used.
exclude
*Optional
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 time
@ -82,7 +78,7 @@ def get_devices(hass, config):
devices = vera_controller.get_devices([
'Switch', 'Armable Sensor', 'On/Off Switch'])
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")
return False
@ -103,7 +99,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class VeraSwitch(ToggleEntity):
""" Represents a Vera Switch """
""" Represents a Vera Switch. """
def __init__(self, vera_device, extra_data=None):
self.vera_device = vera_device

View 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()

View File

@ -8,6 +8,8 @@ import logging
from homeassistant.components.switch import SwitchDevice
REQUIREMENTS = ['pywemo==0.2']
# pylint: disable=unused-argument
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
if discovery_info is not None:
device = discovery.device_from_description(discovery_info)
device = discovery.device_from_description(discovery_info[2])
if device:
add_devices_callback([WemoSwitch(device)])

View File

@ -9,6 +9,9 @@ import logging
from homeassistant.components.wink import WinkToggleDevice
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):
""" Sets up the Wink platform. """

View File

@ -10,6 +10,7 @@ from homeassistant.helpers.entity_component import EntityComponent
import homeassistant.util as util
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.temperature import convert
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS)
@ -86,7 +87,9 @@ def setup(hass, config):
return
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:
thermostat.update_ha_state(True)
@ -118,9 +121,20 @@ class ThermostatDevice(Entity):
@property
def state_attributes(self):
""" Returns optional state attributes. """
thermostat_unit = self.unit_of_measurement
user_unit = self.hass.config.temperature_unit
data = {
ATTR_CURRENT_TEMPERATURE: self.hass.config.temperature(
self.current_temperature, self.unit_of_measurement)[0]
ATTR_CURRENT_TEMPERATURE: round(convert(self.current_temperature,
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
@ -133,11 +147,13 @@ class ThermostatDevice(Entity):
if device_attr is not None:
data.update(device_attr)
data[ATTR_MIN_TEMP] = self.min_temp
data[ATTR_MAX_TEMP] = self.max_temp
return data
@property
def unit_of_measurement(self):
""" Unit of measurement this thermostat expresses itself in. """
return NotImplementedError
@property
def current_temperature(self):
""" Returns the current temperature. """
@ -171,9 +187,9 @@ class ThermostatDevice(Entity):
@property
def min_temp(self):
""" Return minimum temperature. """
return self.hass.config.temperature(7, TEMP_CELCIUS)[0]
return convert(7, TEMP_CELCIUS, self.unit_of_measurement)
@property
def max_temp(self):
""" 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