Merge pull request #251 from balloob/dev

Update master with latest changes
pull/267/head
Paulus Schoutsen 2015-08-09 23:50:52 -07:00
commit ee73bd7dea
109 changed files with 3035 additions and 1647 deletions

View File

@ -10,36 +10,47 @@ 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/mqtt.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/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/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
@ -48,12 +59,18 @@ omit =
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rfxtrx.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

12
.gitmodules vendored
View File

@ -1,12 +1,3 @@
[submodule "homeassistant/external/pynetgear"]
path = homeassistant/external/pynetgear
url = https://github.com/balloob/pynetgear.git
[submodule "homeassistant/external/pywemo"]
path = homeassistant/external/pywemo
url = https://github.com/balloob/pywemo.git
[submodule "homeassistant/external/netdisco"]
path = homeassistant/external/netdisco
url = https://github.com/balloob/netdisco.git
[submodule "homeassistant/external/noop"]
path = homeassistant/external/noop
url = https://github.com/balloob/noop.git
@ -16,9 +7,6 @@
[submodule "homeassistant/external/nzbclients"]
path = homeassistant/external/nzbclients
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
[submodule "homeassistant/external/pymysensors"]
path = homeassistant/external/pymysensors
url = https://github.com/theolind/pymysensors
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
path = homeassistant/components/frontend/www_static/home-assistant-polymer
url = https://github.com/balloob/home-assistant-polymer.git

View File

@ -8,7 +8,7 @@ Check out [the website](https://home-assistant.io) for installation instructions
Examples of devices it can interface it:
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/)
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
* [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/)
@ -22,7 +22,7 @@ Built home automation on top of your devices:
* 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)
* 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).

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

@ -13,6 +13,7 @@ import threading
import enum
import re
import functools as ft
from collections import namedtuple
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
@ -32,7 +33,7 @@ TIMER_INTERVAL = 1 # seconds
SERVICE_CALL_LIMIT = 10 # seconds
# Define number of MINIMUM worker threads.
# During bootstrap of HA (see bootstrap.from_config_dict()) worker threads
# During bootstrap of HA (see bootstrap._setup_component()) worker threads
# will be added for each component that polls devices.
MIN_WORKER_THREAD = 2
@ -41,6 +42,9 @@ ENTITY_ID_PATTERN = re.compile(r"^(?P<domain>\w+)\.(?P<entity>\w+)$")
_LOGGER = logging.getLogger(__name__)
# Temporary to support deprecated methods
_MockHA = namedtuple("MockHomeAssistant", ['bus'])
class HomeAssistant(object):
""" Core class to route all communication to right components. """
@ -52,40 +56,12 @@ class HomeAssistant(object):
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)
create_timer(self)
self.bus.fire(EVENT_HOMEASSISTANT_START)
def block_till_stopped(self):
@ -93,110 +69,21 @@ class HomeAssistant(object):
will block until called. """
request_shutdown = threading.Event()
self.services.register(DOMAIN, SERVICE_HOMEASSISTANT_STOP,
lambda service: request_shutdown.set())
def stop_homeassistant(service):
""" Stops Home Assistant. """
request_shutdown.set()
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
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")
@ -208,76 +95,45 @@ class HomeAssistant(object):
self.pool.stop()
def get_entity_ids(self, domain_filter=None):
"""
Returns known entity ids.
THIS METHOD IS DEPRECATED. Use hass.states.entity_ids
"""
def track_point_in_time(self, action, point_in_time):
"""Deprecated method as of 8/4/2015 to track point in time."""
_LOGGER.warning(
"hass.get_entiy_ids is deprecated. Use hass.states.entity_ids")
'hass.track_point_in_time is deprecated. '
'Please use homeassistant.helpers.event.track_point_in_time')
import homeassistant.helpers.event as helper
helper.track_point_in_time(self, action, point_in_time)
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.
"""
def track_point_in_utc_time(self, action, point_in_time):
"""Deprecated method as of 8/4/2015 to track point in UTC time."""
_LOGGER.warning(
"hass.listen_once_event is deprecated. Use hass.bus.listen_once")
'hass.track_point_in_utc_time is deprecated. '
'Please use homeassistant.helpers.event.track_point_in_utc_time')
import homeassistant.helpers.event as helper
helper.track_point_in_utc_time(self, action, point_in_time)
self.bus.listen_once(event_type, listener)
def track_utc_time_change(self, action,
year=None, month=None, day=None,
hour=None, minute=None, second=None):
"""Deprecated method as of 8/4/2015 to track UTC time change."""
# pylint: disable=too-many-arguments
_LOGGER.warning(
'hass.track_utc_time_change is deprecated. '
'Please use homeassistant.helpers.event.track_utc_time_change')
import homeassistant.helpers.event as helper
helper.track_utc_time_change(self, action, year, month, day, hour,
minute, second)
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
def track_time_change(self, action,
year=None, month=None, day=None,
hour=None, minute=None, second=None, utc=False):
"""Deprecated method as of 8/4/2015 to track time change."""
# pylint: disable=too-many-arguments
_LOGGER.warning(
'hass.track_time_change is deprecated. '
'Please use homeassistant.helpers.event.track_time_change')
import homeassistant.helpers.event as helper
helper.track_time_change(self, action, year, month, day, hour,
minute, second)
class JobPriority(util.OrderedEnum):
@ -305,33 +161,6 @@ class JobPriority(util.OrderedEnum):
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
@ -354,7 +183,7 @@ class Event(object):
self.event_type = event_type
self.data = data or {}
self.origin = origin
self.time_fired = util.strip_microseconds(
self.time_fired = date_util.strip_microseconds(
time_fired or date_util.utcnow())
def as_dict(self):
@ -446,25 +275,28 @@ class EventBus(object):
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.
Returns registered listener that can be used with remove_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
if hasattr(onetime_listener, 'run'):
return
# 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)
self.remove_listener(event_type, onetime_listener)
listener(event)
listener(event)
self.listen(event_type, onetime_listener)
return onetime_listener
def remove_listener(self, event_type, listener):
""" Removes a listener of a specific event_type. """
with self._lock:
@ -596,18 +428,19 @@ class StateMachine(object):
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:
if domain_filter is None:
return list(self._states.keys())
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]
def all(self):
""" Returns a list of all states. """
return [state.copy() for state in self._states.values()]
with self._lock:
return [state.copy() for state in self._states.values()]
def get(self, entity_id):
""" Returns the state of the specified entity. """
@ -616,16 +449,6 @@ class StateMachine(object):
# 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()
@ -661,59 +484,32 @@ class StateMachine(object):
same_state = is_existing and old_state.state == new_state
same_attr = is_existing and old_state.attributes == attributes
if same_state and same_attr:
return
# 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
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
state = State(entity_id, new_state, attributes, last_changed)
self._states[entity_id] = state
event_data = {'entity_id': entity_id, 'new_state': state}
event_data = {'entity_id': entity_id, 'new_state': state}
if old_state:
event_data['old_state'] = old_state
if old_state:
event_data['old_state'] = old_state
self._bus.fire(EVENT_STATE_CHANGED, event_data)
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.
DEPRECATED AS OF 8/4/2015
"""
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
_LOGGER.warning(
'hass.states.track_change is deprecated. '
'Use homeassistant.helpers.event.track_state_change instead.')
import homeassistant.helpers.event as helper
helper.track_state_change(_MockHA(self._bus), entity_ids, action,
from_state, to_state)
# pylint: disable=too-few-public-methods
@ -802,23 +598,15 @@ class ServiceRegistry(object):
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
success = executed_event.wait(SERVICE_CALL_LIMIT)
self._bus.remove_listener(
EVENT_SERVICE_EXECUTED, service_executed)
return success
def _event_to_service_call(self, event):
""" Calls a service from an event. """
@ -826,15 +614,16 @@ class ServiceRegistry(object):
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)
if not self.has_service(domain, service):
return
# 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)))
service_handler = self._services[domain][service]
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,
(service_handler, service_call)))
def _execute_service(self, service_and_call):
""" Executes a service and fires a SERVICE_EXECUTED event. """
@ -843,9 +632,8 @@ class ServiceRegistry(object):
service(call)
self._bus.fire(
EVENT_SERVICE_EXECUTED, {
ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]
})
EVENT_SERVICE_EXECUTED,
{ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
def _generate_unique_id(self):
""" Generates a unique service call id. """
@ -853,70 +641,6 @@ class ServiceRegistry(object):
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. """
@ -943,8 +667,8 @@ class Config(object):
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):
if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
self.temperature_unit and unit != self.temperature_unit):
return value, unit
try:
@ -986,3 +710,93 @@ class InvalidEntityFormatError(HomeAssistantError):
class NoEntitySpecifiedError(HomeAssistantError):
""" When no entity is specified. """
pass
def create_timer(hass, interval=TIMER_INTERVAL):
""" Creates a timer. Timer will start on HOMEASSISTANT_START. """
# 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 % interval == 0, "60 % TIMER_INTERVAL should be 0!"
def timer():
"""Send an EVENT_TIME_CHANGED on interval."""
stop_event = threading.Event()
def stop_timer(event):
"""Stop the timer."""
stop_event.set()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_timer)
_LOGGER.info("Timer:starting")
last_fired_on_second = -1
calc_now = date_util.utcnow
while not 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 stop_event.isSet():
try:
hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
except HomeAssistantError:
# HA raises error if firing event after it has shut down
break
def start_timer(event):
"""Start the timer."""
thread = threading.Thread(target=timer)
thread.daemon = True
thread.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_timer)
def create_worker_pool(worker_count=MIN_WORKER_THREAD):
""" 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, worker_count, busy_callback)

View File

@ -63,12 +63,15 @@ def setup_component(hass, domain, config=None):
def _handle_requirements(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):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
return False
return True
@ -83,33 +86,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):
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):

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
@ -27,7 +27,7 @@ def setup(hass, config):
""" Sets up automation. """
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)

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

@ -15,6 +15,7 @@ from homeassistant.helpers import validate_config
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)
@ -134,7 +135,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,

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

@ -43,6 +43,7 @@ from homeassistant.components.device_tracker import DOMAIN
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear>=0.1']
def get_scanner(hass, config):
@ -64,22 +65,10 @@ class NetgearDeviceScanner(object):
""" This class queries a Netgear wireless router using the SOAP-API. """
def __init__(self, host, username, password):
import pynetgear
self.last_results = []
try:
# Pylint does not play nice if not every folders has an __init__.py
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.pynetgear.pynetgear as pynetgear
except ImportError:
_LOGGER.exception(
("Failed to import pynetgear. "
"Did you maybe not run `git submodule init` "
"and `git submodule update`?"))
self.success_init = False
return
self._api = pynetgear.Netgear(host, username, password)
self.lock = threading.Lock()

View File

@ -43,6 +43,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.2']
def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """

View File

@ -12,9 +12,6 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
import logging
import threading
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.netdisco.netdisco.const as services
from homeassistant import bootstrap
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_PLATFORM_DISCOVERED,
@ -22,14 +19,20 @@ from homeassistant.const import (
DOMAIN = "discovery"
DEPENDENCIES = []
REQUIREMENTS = ['zeroconf>=0.16.0']
REQUIREMENTS = ['netdisco>=0.1']
SCAN_INTERVAL = 300 # seconds
# Next 3 lines for now a mirror from netdisco.const
# Should setup a mapping netdisco.const -> own constants
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_HANDLERS = {
services.BELKIN_WEMO: "switch",
services.GOOGLE_CAST: "media_player",
services.PHILIPS_HUE: "light",
SERVICE_WEMO: "switch",
SERVICE_CAST: "media_player",
SERVICE_HUE: "light",
}
@ -56,14 +59,7 @@ def setup(hass, config):
""" Starts a discovery service. """
logger = logging.getLogger(__name__)
try:
from homeassistant.external.netdisco.netdisco.service import \
DiscoveryService
except ImportError:
logger.exception(
"Unable to import netdisco. "
"Did you install all the zeroconf dependency?")
return False
from netdisco.service import DiscoveryService
# Disable zeroconf logging, it spams
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "4f94fd4404583fbf27cc899c024d26ff"
VERSION = "d3d94fe65a29aa438887d368bca61d68"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@ Provides functionality to group devices that can be turned on or off.
import homeassistant 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

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

@ -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,7 +19,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED,
ATTR_FRIENDLY_NAME)
# homeassistant constants
DOMAIN = "isy994"
DEPENDENCIES = []
REQUIREMENTS = ['PyISY>=1.0.5']
@ -31,7 +30,6 @@ SENSOR_STRING = 'Sensor'
HIDDEN_STRING = '{HIDE ME}'
CONF_TLS_VER = 'tls'
# setup logger
_LOGGER = logging.getLogger(__name__)

View File

@ -99,7 +99,7 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
DISCOVERY_PLATFORMS = {
wink.DISCOVER_LIGHTS: 'wink',
isy994.DISCOVER_LIGHTS: 'isy994',
discovery.services.PHILIPS_HUE: 'hue',
discovery.SERVICE_HUE: 'hue',
}
PROP_TO_ATTR = {
@ -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

@ -24,7 +24,9 @@ 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']
@ -57,6 +59,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 +97,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 +125,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

@ -5,6 +5,8 @@ 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. """

View File

@ -1,16 +1,18 @@
""" Support for Hue lights. """
""" Support for Wink lights. """
import logging
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.wink.pywink as pywink
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/master.zip'
'#pywink>=0.1']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return Wink lights. """
import pywink
token = config.get(CONF_ACCESS_TOKEN)
if not pywink.is_token_set() and token is None:

View File

@ -24,7 +24,7 @@ SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = {
discovery.services.GOOGLE_CAST: 'cast',
discovery.SERVICE_CAST: 'cast',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
@ -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

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

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/python3.zip'
'#pymodbus>=1.2.0']
# Type of network
MEDIUM = "type"

View File

@ -0,0 +1,242 @@
"""
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 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):
""" Subscribes to a specific MQTT topic. """
if event.data[ATTR_TOPIC] == topic:
callback(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):
""" 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):
""" Raise error if error result. """
if result != 0:
raise HomeAssistantError('Error talking to MQTT: {}'.format(result))

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

@ -19,6 +19,7 @@ import logging
from collections import namedtuple
from homeassistant 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

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

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

@ -21,9 +21,6 @@ Port of your connection to your MySensors device.
"""
import logging
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.pymysensors.mysensors.mysensors as mysensors
import homeassistant.external.pymysensors.mysensors.const as const
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
@ -39,12 +36,16 @@ ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyserial>=2.7']
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/master.zip'
'#egg=pymysensors-0.1']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the mysensors platform. """
import mysensors.mysensors as mysensors
import mysensors.const as const
devices = {} # keep track of devices added to HA
# Just assume celcius means that the user wants metric for now.
# It may make more sense to make this a global config option in the future.
@ -69,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id)
node[child_id][value_type] = \
MySensorsNodeValue(
nid, child_id, name, value_type, is_metric)
nid, child_id, name, value_type, is_metric, const)
new_devices.append(node[child_id][value_type])
else:
node[child_id][value_type].update_sensor(
@ -102,8 +103,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class MySensorsNodeValue(Entity):
""" Represents the value of a MySensors child node. """
# pylint: disable=too-many-arguments
def __init__(self, node_id, child_id, name, value_type, metric):
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, node_id, child_id, name, value_type, metric, const):
self._name = name
self.node_id = node_id
self.child_id = child_id
@ -111,6 +112,7 @@ class MySensorsNodeValue(Entity):
self.value_type = value_type
self.metric = metric
self._value = ''
self.const = const
@property
def should_poll(self):
@ -130,11 +132,11 @@ class MySensorsNodeValue(Entity):
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity. """
if self.value_type == const.SetReq.V_TEMP:
if self.value_type == self.const.SetReq.V_TEMP:
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
elif self.value_type == const.SetReq.V_HUM or \
self.value_type == const.SetReq.V_DIMMER or \
self.value_type == const.SetReq.V_LIGHT_LEVEL:
elif self.value_type == self.const.SetReq.V_HUM or \
self.value_type == self.const.SetReq.V_DIMMER or \
self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
return '%'
return None
@ -150,8 +152,8 @@ class MySensorsNodeValue(Entity):
def update_sensor(self, value, battery_level):
""" Update a sensor with the latest value from the controller. """
_LOGGER.info("%s value = %s", self._name, value)
if self.value_type == const.SetReq.V_TRIPPED or \
self.value_type == const.SetReq.V_ARMED:
if self.value_type == self.const.SetReq.V_TRIPPED or \
self.value_type == self.const.SetReq.V_ARMED:
self._value = STATE_ON if int(value) == 1 else STATE_OFF
else:
self._value = value

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

@ -0,0 +1,105 @@
"""
homeassistant.components.sensor.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from RFXtrx sensors.
Configuration:
To use the rfxtrx sensors you will need to add something like the following to
your config/configuration.yaml
Example:
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
from homeassistant.const import (TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip'
'#RFXtrx>=0.15']
DATA_TYPES = OrderedDict([
('Temperature', TEMP_CELCIUS),
('Humidity', '%'),
('Barometer', ''),
('Wind direction', ''),
('Rain rate', '')])
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the RFXtrx platform. """
logger = logging.getLogger(__name__)
sensors = {} # keep track of sensors added to HA
def sensor_update(event):
""" Callback for sensor updates from the RFXtrx gateway. """
if event.device.id_string in sensors:
sensors[event.device.id_string].event = event
else:
logger.info("adding new sensor: %s", event.device.type_string)
new_sensor = RfxtrxSensor(event)
sensors[event.device.id_string] = new_sensor
add_devices([new_sensor])
try:
import RFXtrx as rfxtrx
except ImportError:
logger.exception(
"Failed to import rfxtrx")
return False
device = config.get("device", "")
rfxtrx.Core(device, sensor_update)
class RfxtrxSensor(Entity):
""" Represents a RFXtrx sensor. """
def __init__(self, event):
self.event = event
self._unit_of_measurement = None
self._data_type = None
for data_type in DATA_TYPES:
if data_type in self.event.values:
self._unit_of_measurement = DATA_TYPES[data_type]
self._data_type = data_type
break
id_string = int(event.device.id_string.replace(":", ""), 16)
self._name = "{} {} ({})".format(self._data_type,
self.event.device.type_string,
id_string)
def __str__(self):
return self._name
@property
def state(self):
if self._data_type:
return self.event.values[self._data_type]
return None
@property
def name(self):
""" Get the mame of the sensor. """
return self._name
@property
def state_attributes(self):
return self.event.values
@property
def unit_of_measurement(self):
return self._unit_of_measurement

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):
@ -77,7 +79,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
sensor_name = config[ts_sensor.id]
except KeyError:
if 'only_named' in config:
if util.convert(config.get('only_named'), bool, False):
continue
sensor_name = str(ts_sensor.id)

View File

@ -0,0 +1,70 @@
"""
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/master.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

@ -1,15 +1,21 @@
""" Support for Wink sensors. """
"""
homeassistant.components.sensor.wink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Wink sensors.
"""
import logging
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.wink.pywink as pywink
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/master.zip'
'#pywink>=0.1']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Wink platform. """
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
@ -25,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,9 +21,12 @@ 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
@ -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

@ -29,7 +29,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
discovery.services.BELKIN_WEMO: 'wemo',
discovery.SERVICE_WEMO: 'wemo',
wink.DISCOVER_SWITCHES: 'wink',
isy994.DISCOVER_SWITCHES: 'isy994',
}
@ -45,21 +45,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)
@ -84,7 +81,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,116 @@
"""
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/master.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

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,121 @@
"""
homeassistant.components.switch.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to control the GPIO pins of a Raspberry Pi.
Configuration:
switch:
platform: rpi_gpio
ports:
11: Fan Office
12: Light Desk
Variables:
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)
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
switches = []
ports = config.get('ports')
for port_num, port_name in ports.items():
switches.append(RPiGPIOSwitch(port_name, port_num))
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):
self._name = name or DEVICE_DEFAULT_NAME
self._state = False
self._gpio = gpio
# 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(True):
self._state = True
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turn the device off. """
if self._switch(False):
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,28 +28,23 @@ 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__)
@ -59,7 +53,7 @@ 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

@ -8,21 +8,14 @@ import logging
from homeassistant.components.switch import SwitchDevice
REQUIREMENTS = ['pywemo>=0.1']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return WeMo switches. """
try:
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.pywemo.pywemo as pywemo
import homeassistant.external.pywemo.pywemo.discovery as discovery
except ImportError:
logging.getLogger(__name__).exception((
"Failed to import pywemo. "
"Did you maybe not run `git submodule init` "
"and `git submodule update`?"))
return
import pywemo
import pywemo.discovery as discovery
if discovery_info is not None:
device = discovery.device_from_description(discovery_info)

View File

@ -6,15 +6,17 @@ Support for Wink switches.
"""
import logging
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.wink.pywink as pywink
from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
'#pywink>=0.1']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Wink platform. """
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)

View File

@ -11,7 +11,7 @@ from homeassistant.helpers.entity_component import EntityComponent
import homeassistant.util as util
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF)
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS)
DOMAIN = "thermostat"
DEPENDENCIES = []
@ -24,6 +24,8 @@ SERVICE_SET_TEMPERATURE = "set_temperature"
ATTR_CURRENT_TEMPERATURE = "current_temperature"
ATTR_AWAY_MODE = "away_mode"
ATTR_MAX_TEMP = "max_temp"
ATTR_MIN_TEMP = "min_temp"
_LOGGER = logging.getLogger(__name__)
@ -131,6 +133,9 @@ 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
@ -162,3 +167,13 @@ class ThermostatDevice(Entity):
def turn_away_mode_off(self):
""" Turns away mode off. """
pass
@property
def min_temp(self):
""" Return minimum temperature. """
return self.hass.config.temperature(7, TEMP_CELCIUS)[0]
@property
def max_temp(self):
""" Return maxmum temperature. """
return self.hass.config.temperature(35, TEMP_CELCIUS)[0]

View File

@ -62,7 +62,9 @@ import logging
import datetime
import homeassistant.components as core
import homeassistant.util as util
from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.helpers.event import track_state_change
from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF
TOL_TEMP = 0.3
@ -90,27 +92,29 @@ class HeatControl(ThermostatDevice):
self.target_sensor_entity_id = config.get("target_sensor")
self.time_temp = []
for time_temp in list(config.get("time_temp").split(",")):
time, temp = time_temp.split(':')
time_start, time_end = time.split('-')
start_time = datetime.datetime.time(datetime.datetime.
strptime(time_start, '%H%M'))
end_time = datetime.datetime.time(datetime.datetime.
strptime(time_end, '%H%M'))
self.time_temp.append((start_time, end_time, float(temp)))
if config.get("time_temp"):
for time_temp in list(config.get("time_temp").split(",")):
time, temp = time_temp.split(':')
time_start, time_end = time.split('-')
start_time = datetime.datetime.time(
datetime.datetime.strptime(time_start, '%H%M'))
end_time = datetime.datetime.time(
datetime.datetime.strptime(time_end, '%H%M'))
self.time_temp.append((start_time, end_time, float(temp)))
self.min_temp = float(config.get("min_temp"))
self._min_temp = util.convert(config.get("min_temp"), float, 0)
self._max_temp = util.convert(config.get("max_temp"), float, 100)
self._manual_sat_temp = None
self._away = False
self._heater_manual_changed = True
hass.states.track_change(self.heater_entity_id,
self._heater_turned_on,
STATE_OFF, STATE_ON)
hass.states.track_change(self.heater_entity_id,
self._heater_turned_off,
STATE_ON, STATE_OFF)
track_state_change(hass, self.heater_entity_id,
self._heater_turned_on,
STATE_OFF, STATE_ON)
track_state_change(hass, self.heater_entity_id,
self._heater_turned_off,
STATE_ON, STATE_OFF)
@property
def name(self):
@ -178,7 +182,7 @@ class HeatControl(ThermostatDevice):
if not self._heater_manual_changed:
pass
else:
self.set_temperature(100)
self.set_temperature(self.max_temp)
self._heater_manual_changed = True
@ -194,3 +198,13 @@ class HeatControl(ThermostatDevice):
def turn_away_mode_off(self):
""" Turns away mode off. """
self._away = False
@property
def min_temp(self):
""" Return minimum temperature. """
return self._min_temp
@property
def max_temp(self):
""" Return maxmum temperature. """
return self._max_temp

View File

@ -6,9 +6,6 @@ Connects to a Wink hub and loads relevant components to control its devices.
"""
import logging
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.wink.pywink as pywink
from homeassistant import bootstrap
from homeassistant.loader import get_component
from homeassistant.helpers import validate_config
@ -19,6 +16,8 @@ from homeassistant.const import (
DOMAIN = "wink"
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
'#pywink>=0.1']
DISCOVER_LIGHTS = "wink.lights"
DISCOVER_SWITCHES = "wink.switches"
@ -32,6 +31,7 @@ def setup(hass, config):
if not validate_config(config, {DOMAIN: [CONF_ACCESS_TOKEN]}, logger):
return False
import pywink
pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN])
# Load components for the devices in the Wink that we support

View File

@ -1,7 +1,6 @@
"""
homeassistant.components.zwave
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connects Home Assistant to a Z-Wave network.
"""
from pprint import pprint

View File

@ -13,14 +13,11 @@ from homeassistant.const import (
CONF_TIME_ZONE)
import homeassistant.util.location as loc_util
_LOGGER = logging.getLogger(__name__)
YAML_CONFIG_FILE = 'configuration.yaml'
CONF_CONFIG_FILE = 'home-assistant.conf'
DEFAULT_CONFIG = [
DEFAULT_CONFIG = (
# Tuples (attribute, default, auto detect property, description)
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
'running'),
@ -30,9 +27,9 @@ DEFAULT_CONFIG = [
(CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celcius, F for Fahrenheit'),
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
'pedia.org/wiki/List_of_tz_database_time_zones'),
]
DEFAULT_COMPONENTS = [
'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun']
)
DEFAULT_COMPONENTS = (
'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun')
def ensure_config_exists(config_dir, detect_location=True):
@ -95,24 +92,14 @@ def create_default_config(config_dir, detect_location=True):
def find_config_file(config_dir):
""" Looks in given directory for supported config files. """
for filename in (YAML_CONFIG_FILE, CONF_CONFIG_FILE):
config_path = os.path.join(config_dir, filename)
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
if os.path.isfile(config_path):
return config_path
return None
return config_path if os.path.isfile(config_path) else None
def load_config_file(config_path):
""" Loads given config file. """
config_ext = os.path.splitext(config_path)[1]
if config_ext == '.yaml':
return load_yaml_config_file(config_path)
elif config_ext == '.conf':
return load_conf_config_file(config_path)
return load_yaml_config_file(config_path)
def load_yaml_config_file(config_path):
@ -120,17 +107,16 @@ def load_yaml_config_file(config_path):
import yaml
def parse(fname):
""" Actually parse the file. """
""" Parse a YAML file. """
try:
with open(fname) as conf_file:
# If configuration file is empty YAML returns None
# We convert that to an empty dict
conf_dict = yaml.load(conf_file) or {}
return yaml.load(conf_file) or {}
except yaml.YAMLError:
_LOGGER.exception('Error reading YAML configuration file %s',
fname)
raise HomeAssistantError()
return conf_dict
error = 'Error reading YAML configuration file {}'.format(fname)
_LOGGER.exception(error)
raise HomeAssistantError(error)
def yaml_include(loader, node):
"""
@ -153,21 +139,3 @@ def load_yaml_config_file(config_path):
raise HomeAssistantError()
return conf_dict
def load_conf_config_file(config_path):
""" Parse the old style conf configuration. """
import configparser
config_dict = {}
config = configparser.ConfigParser()
config.read(config_path)
for section in config.sections():
config_dict[section] = {}
for key, val in config.items(section):
config_dict[section][key] = val
return config_dict

@ -1 +0,0 @@
Subproject commit b2cad7c2b959efa8eee9b5ac62d87232bf0b5176

@ -1 +0,0 @@
Subproject commit cd5ef892eeec0ad027727f7e8f757e7f2927da97

@ -1 +0,0 @@
Subproject commit e946ecf7926b9b2adaa1e3127a9738201a1b1fc7

@ -1 +0,0 @@
Subproject commit ca94e41faa48c783f600a2efd550c6b7dae01b0d

View File

@ -1,408 +0,0 @@
__author__ = 'JOHNMCL'
import json
import time
import requests
baseUrl = "https://winkapi.quirky.com"
headers = {}
class wink_sensor_pod(object):
""" represents a wink.py sensor
json_obj holds the json stat at init (and if there is a refresh it's updated
it's the native format for this objects methods
and looks like so:
{
"data": {
"last_event": {
"brightness_occurred_at": None,
"loudness_occurred_at": None,
"vibration_occurred_at": None
},
"model_name": "Tripper",
"capabilities": {
"sensor_types": [
{
"field": "opened",
"type": "boolean"
},
{
"field": "battery",
"type": "percentage"
}
]
},
"manufacturer_device_model": "quirky_ge_tripper",
"location": "",
"radio_type": "zigbee",
"manufacturer_device_id": None,
"gang_id": None,
"sensor_pod_id": "37614",
"subscription": {
},
"units": {
},
"upc_id": "184",
"hidden_at": None,
"last_reading": {
"battery_voltage_threshold_2": 0,
"opened": False,
"battery_alarm_mask": 0,
"opened_updated_at": 1421697092.7347496,
"battery_voltage_min_threshold_updated_at": 1421697092.7347229,
"battery_voltage_min_threshold": 0,
"connection": None,
"battery_voltage": 25,
"battery_voltage_threshold_1": 25,
"connection_updated_at": None,
"battery_voltage_threshold_3": 0,
"battery_voltage_updated_at": 1421697092.7347066,
"battery_voltage_threshold_1_updated_at": 1421697092.7347302,
"battery_voltage_threshold_3_updated_at": 1421697092.7347434,
"battery_voltage_threshold_2_updated_at": 1421697092.7347374,
"battery": 1.0,
"battery_updated_at": 1421697092.7347553,
"battery_alarm_mask_updated_at": 1421697092.734716
},
"triggers": [
],
"name": "MasterBathroom",
"lat_lng": [
37.550773,
-122.279182
],
"uuid": "a2cb868a-dda3-4211-ab73-fc08087aeed7",
"locale": "en_us",
"device_manufacturer": "quirky_ge",
"created_at": 1421523277,
"local_id": "2",
"hub_id": "88264"
},
}
"""
def __init__(self, aJSonObj, objectprefix="sensor_pods"):
self.jsonState = aJSonObj
self.objectprefix = objectprefix
def __str__(self):
return "%s %s %s" % (self.name(), self.deviceId(), self.state())
def __repr__(self):
return "<Wink sensor %s %s %s>" % (self.name(), self.deviceId(), self.state())
@property
def _last_reading(self):
return self.jsonState.get('last_reading') or {}
def name(self):
return self.jsonState.get('name', "Unknown Name")
def state(self):
return self._last_reading.get('opened', False)
def deviceId(self):
return self.jsonState.get('sensor_pod_id', self.name())
def refresh_state_at_hub(self):
"""
Tell hub to query latest status from device and upload to Wink.
PS: Not sure if this even works..
"""
urlString = baseUrl + "/%s/%s/refresh" % (self.objectprefix, self.deviceId())
requests.get(urlString, headers=headers)
def updateState(self):
""" Update state with latest info from Wink API. """
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
arequest = requests.get(urlString, headers=headers)
self._updateStateFromResponse(arequest.json())
def _updateStateFromResponse(self, response_json):
"""
:param response_json: the json obj returned from query
:return:
"""
self.jsonState = response_json.get('data')
class wink_binary_switch(object):
""" represents a wink.py switch
json_obj holds the json stat at init (and if there is a refresh it's updated
it's the native format for this objects methods
and looks like so:
{
"data": {
"binary_switch_id": "4153",
"name": "Garage door indicator",
"locale": "en_us",
"units": {},
"created_at": 1411614982,
"hidden_at": null,
"capabilities": {},
"subscription": {},
"triggers": [],
"desired_state": {
"powered": false
},
"manufacturer_device_model": "leviton_dzs15",
"manufacturer_device_id": null,
"device_manufacturer": "leviton",
"model_name": "Switch",
"upc_id": "94",
"gang_id": null,
"hub_id": "11780",
"local_id": "9",
"radio_type": "zwave",
"last_reading": {
"powered": false,
"powered_updated_at": 1411614983.6153464,
"powering_mode": null,
"powering_mode_updated_at": null,
"consumption": null,
"consumption_updated_at": null,
"cost": null,
"cost_updated_at": null,
"budget_percentage": null,
"budget_percentage_updated_at": null,
"budget_velocity": null,
"budget_velocity_updated_at": null,
"summation_delivered": null,
"summation_delivered_updated_at": null,
"sum_delivered_multiplier": null,
"sum_delivered_multiplier_updated_at": null,
"sum_delivered_divisor": null,
"sum_delivered_divisor_updated_at": null,
"sum_delivered_formatting": null,
"sum_delivered_formatting_updated_at": null,
"sum_unit_of_measure": null,
"sum_unit_of_measure_updated_at": null,
"desired_powered": false,
"desired_powered_updated_at": 1417893563.7567682,
"desired_powering_mode": null,
"desired_powering_mode_updated_at": null
},
"current_budget": null,
"lat_lng": [
38.429996,
-122.653721
],
"location": "",
"order": 0
},
"errors": [],
"pagination": {}
}
"""
def __init__(self, aJSonObj, objectprefix="binary_switches"):
self.jsonState = aJSonObj
self.objectprefix = objectprefix
# Tuple (desired state, time)
self._last_call = (0, None)
def __str__(self):
return "%s %s %s" % (self.name(), self.deviceId(), self.state())
def __repr__(self):
return "<Wink switch %s %s %s>" % (self.name(), self.deviceId(), self.state())
@property
def _last_reading(self):
return self.jsonState.get('last_reading') or {}
def name(self):
return self.jsonState.get('name', "Unknown Name")
def state(self):
# Optimistic approach to setState:
# Within 15 seconds of a call to setState we assume it worked.
if self._recent_state_set():
return self._last_call[1]
return self._last_reading.get('powered', False)
def deviceId(self):
return self.jsonState.get('binary_switch_id', self.name())
def setState(self, state):
"""
:param state: a boolean of true (on) or false ('off')
:return: nothing
"""
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
values = {"desired_state": {"powered": state}}
arequest = requests.put(urlString, data=json.dumps(values), headers=headers)
self._updateStateFromResponse(arequest.json())
self._last_call = (time.time(), state)
def refresh_state_at_hub(self):
"""
Tell hub to query latest status from device and upload to Wink.
PS: Not sure if this even works..
"""
urlString = baseUrl + "/%s/%s/refresh" % (self.objectprefix, self.deviceId())
requests.get(urlString, headers=headers)
def updateState(self):
""" Update state with latest info from Wink API. """
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
arequest = requests.get(urlString, headers=headers)
self._updateStateFromResponse(arequest.json())
def wait_till_desired_reached(self):
""" Wait till desired state reached. Max 10s. """
if self._recent_state_set():
return
# self.refresh_state_at_hub()
tries = 1
while True:
self.updateState()
last_read = self._last_reading
if last_read.get('desired_powered') == last_read.get('powered') \
or tries == 5:
break
time.sleep(2)
tries += 1
self.updateState()
last_read = self._last_reading
def _updateStateFromResponse(self, response_json):
"""
:param response_json: the json obj returned from query
:return:
"""
self.jsonState = response_json.get('data')
def _recent_state_set(self):
return time.time() - self._last_call[0] < 15
class wink_bulb(wink_binary_switch):
""" represents a wink.py bulb
json_obj holds the json stat at init (and if there is a refresh it's updated
it's the native format for this objects methods
and looks like so:
"light_bulb_id": "33990",
"name": "downstaurs lamp",
"locale": "en_us",
"units":{},
"created_at": 1410925804,
"hidden_at": null,
"capabilities":{},
"subscription":{},
"triggers":[],
"desired_state":{"powered": true, "brightness": 1},
"manufacturer_device_model": "lutron_p_pkg1_w_wh_d",
"manufacturer_device_id": null,
"device_manufacturer": "lutron",
"model_name": "Caseta Wireless Dimmer & Pico",
"upc_id": "3",
"hub_id": "11780",
"local_id": "8",
"radio_type": "lutron",
"linked_service_id": null,
"last_reading":{
"brightness": 1,
"brightness_updated_at": 1417823487.490747,
"connection": true,
"connection_updated_at": 1417823487.4907365,
"powered": true,
"powered_updated_at": 1417823487.4907532,
"desired_powered": true,
"desired_powered_updated_at": 1417823485.054675,
"desired_brightness": 1,
"desired_brightness_updated_at": 1417409293.2591703
},
"lat_lng":[38.429962, -122.653715],
"location": "",
"order": 0
"""
jsonState = {}
def __init__(self, ajsonobj):
super().__init__(ajsonobj, "light_bulbs")
def deviceId(self):
return self.jsonState.get('light_bulb_id', self.name())
def brightness(self):
return self._last_reading.get('brightness')
def setState(self, state, brightness=None):
"""
:param state: a boolean of true (on) or false ('off')
:return: nothing
"""
urlString = baseUrl + "/light_bulbs/%s" % self.deviceId()
values = {
"desired_state": {
"powered": state
}
}
if brightness is not None:
values["desired_state"]["brightness"] = brightness
urlString = baseUrl + "/light_bulbs/%s" % self.deviceId()
arequest = requests.put(urlString, data=json.dumps(values), headers=headers)
self._updateStateFromResponse(arequest.json())
self._last_call = (time.time(), state)
def __repr__(self):
return "<Wink Bulb %s %s %s>" % (
self.name(), self.deviceId(), self.state())
def get_devices(filter, constructor):
arequestUrl = baseUrl + "/users/me/wink_devices"
j = requests.get(arequestUrl, headers=headers).json()
items = j.get('data')
devices = []
for item in items:
id = item.get(filter)
if (id is not None and item.get("hidden_at") is None):
devices.append(constructor(item))
return devices
def get_bulbs():
return get_devices('light_bulb_id', wink_bulb)
def get_switches():
return get_devices('binary_switch_id', wink_binary_switch)
def get_sensors():
return get_devices('sensor_pod_id', wink_sensor_pod)
def is_token_set():
""" Returns if an auth token has been set. """
return bool(headers)
def set_bearer_token(token):
global headers
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer {}".format(token)
}
if __name__ == "__main__":
sw = get_bulbs()
lamp = sw[3]
lamp.setState(False)

View File

@ -6,10 +6,6 @@ from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, DEVICE_DEFAULT_NAME)
from homeassistant.util import ensure_unique_string, slugify
# Deprecated 3/5/2015 - Moved to homeassistant.helpers.entity
# pylint: disable=unused-import
from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
""" Generate a unique entity ID based on given entity IDs or used ids. """

View File

@ -1,10 +0,0 @@
"""
Deprecated since 3/21/2015 - please use helpers.entity
"""
import logging
# pylint: disable=unused-import
from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa
logging.getLogger(__name__).warning(
'This file is deprecated. Please use helpers.entity')

View File

@ -1,10 +0,0 @@
"""
Deprecated since 3/21/2015 - please use helpers.entity_component
"""
import logging
# pylint: disable=unused-import
from .entity_component import EntityComponent as DeviceComponent # noqa
logging.getLogger(__name__).warning(
'This file is deprecated. Please use helpers.entity_component')

View File

@ -7,6 +7,7 @@ Provides helpers for components that manage entities.
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers import (
generate_entity_id, config_per_platform, extract_entity_ids)
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.components import group, discovery
from homeassistant.const import ATTR_ENTITY_ID
@ -115,8 +116,8 @@ class EntityComponent(object):
self.is_polling = True
self.hass.track_time_change(
self._update_entity_states,
track_utc_time_change(
self.hass, self._update_entity_states,
second=range(0, 60, self.scan_interval))
def _setup_platform(self, platform_type, platform_config,
@ -135,22 +136,6 @@ class EntityComponent(object):
self.hass, platform_config, self.add_entities, discovery_info)
self.hass.config.components.append(platform_name)
except AttributeError:
# AttributeError if setup_platform does not exist
# Support old deprecated method for now - 3/1/2015
if hasattr(platform, 'get_devices'):
self.logger.warning(
'Please upgrade %s to return new entities using '
'setup_platform. See %s/demo.py for an example.',
platform_name, self.domain)
self.add_entities(
platform.get_devices(self.hass, platform_config))
else:
self.logger.exception(
'Error while setting up platform %s', platform_type)
except Exception: # pylint: disable=broad-except
self.logger.exception(
'Error while setting up platform %s', platform_type)

View File

@ -0,0 +1,163 @@
"""
Helpers for listening to events
"""
import functools as ft
from ..util import dt as dt_util
from ..const import (
ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
def track_state_change(hass, 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_change_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'])
hass.bus.listen(EVENT_STATE_CHANGED, state_change_listener)
return state_change_listener
def track_point_in_time(hass, action, point_in_time):
"""
Adds a listener that fires once after a spefic point in time.
"""
utc_point_in_time = dt_util.as_utc(point_in_time)
@ft.wraps(action)
def utc_converter(utc_now):
""" Converts passed in UTC now to local now. """
action(dt_util.as_local(utc_now))
return track_point_in_utc_time(hass, utc_converter, utc_point_in_time)
def track_point_in_utc_time(hass, action, point_in_time):
"""
Adds a listener that fires once after a specific point in UTC time.
"""
# Ensure point_in_time is UTC
point_in_time = dt_util.as_utc(point_in_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
hass.bus.remove_listener(EVENT_TIME_CHANGED,
point_in_time_listener)
action(now)
hass.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
return point_in_time_listener
# pylint: disable=too-many-arguments
def track_utc_time_change(hass, action, year=None, month=None, day=None,
hour=None, minute=None, second=None, local=False):
""" Adds a listener that will fire if time matches a pattern. """
# We do not have to wrap the function with time pattern matching logic
# if no pattern given
if all(val is None for val in (year, month, day, hour, minute, second)):
@ft.wraps(action)
def time_change_listener(event):
""" Fires every time event that comes in. """
action(event.data[ATTR_NOW])
hass.bus.listen(EVENT_TIME_CHANGED, time_change_listener)
return time_change_listener
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 pattern_time_change_listener(event):
""" Listens for matching time_changed events. """
now = event.data[ATTR_NOW]
if local:
now = dt_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)
hass.bus.listen(EVENT_TIME_CHANGED, pattern_time_change_listener)
return pattern_time_change_listener
# pylint: disable=too-many-arguments
def track_time_change(hass, action, year=None, month=None, day=None,
hour=None, minute=None, second=None):
""" Adds a listener that will fire if UTC time matches a pattern. """
track_utc_time_change(hass, action, year, month, day, hour, minute, second,
local=True)
def _process_match_param(parameter):
""" Wraps parameter in a tuple 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 tuple of allowed subjects or a `MATCH_ALL`.
"""
return MATCH_ALL == pattern or subject in pattern

View File

@ -30,7 +30,16 @@ class TrackStates(object):
return self.states
def __exit__(self, exc_type, exc_value, traceback):
self.states.extend(self.hass.states.get_since(self.now))
self.states.extend(get_changed_since(self.hass.states.all(), self.now))
def get_changed_since(states, utc_point_in_time):
"""
Returns all states that have been changed since utc_point_in_time.
"""
point_in_time = dt_util.strip_microseconds(utc_point_in_time)
return [state for state in states if state.last_updated >= point_in_time]
def reproduce_state(hass, states, blocking=False):

View File

@ -61,11 +61,10 @@ def prepare(hass):
# python components. If this assumption is not true, HA won't break,
# just might output more errors.
for fil in os.listdir(custom_path):
if os.path.isdir(os.path.join(custom_path, fil)):
if fil != '__pycache__':
AVAILABLE_COMPONENTS.append(
'custom_components.{}'.format(fil))
if fil == '__pycache__':
continue
elif os.path.isdir(os.path.join(custom_path, fil)):
AVAILABLE_COMPONENTS.append('custom_components.{}'.format(fil))
else:
# For files we will strip out .py extension
AVAILABLE_COMPONENTS.append(
@ -195,24 +194,24 @@ def _load_order_component(comp_name, load_order, loading):
for dependency in component.DEPENDENCIES:
# Check not already loaded
if dependency not in load_order:
# If we are already loading it, we have a circular dependency
if dependency in loading:
_LOGGER.error('Circular dependency detected: %s -> %s',
comp_name, dependency)
if dependency in load_order:
continue
return OrderedSet()
# If we are already loading it, we have a circular dependency
if dependency in loading:
_LOGGER.error('Circular dependency detected: %s -> %s',
comp_name, dependency)
return OrderedSet()
dep_load_order = _load_order_component(
dependency, load_order, loading)
dep_load_order = _load_order_component(dependency, load_order, loading)
# length == 0 means error loading dependency or children
if len(dep_load_order) == 0:
_LOGGER.error('Error loading %s dependency: %s',
comp_name, dependency)
return OrderedSet()
# length == 0 means error loading dependency or children
if len(dep_load_order) == 0:
_LOGGER.error('Error loading %s dependency: %s',
comp_name, dependency)
return OrderedSet()
load_order.update(dep_load_order)
load_order.update(dep_load_order)
load_order.add(comp_name)
loading.remove(comp_name)

View File

@ -120,10 +120,11 @@ class HomeAssistant(ha.HomeAssistant):
def start(self):
# Ensure a local API exists to connect with remote
if self.config.api is None:
bootstrap.setup_component(self, 'http')
bootstrap.setup_component(self, 'api')
if not bootstrap.setup_component(self, 'api'):
raise ha.HomeAssistantError(
'Unable to setup local API to receive events')
ha.Timer(self)
ha.create_timer(self)
self.bus.fire(ha.EVENT_HOMEASSISTANT_START,
origin=ha.EventOrigin.remote)

View File

@ -16,11 +16,7 @@ import random
import string
from functools import wraps
# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package
# pylint: disable=unused-import
from .dt import ( # noqa
datetime_to_str, str_to_datetime, strip_microseconds,
datetime_to_local_str, utcnow)
from .dt import datetime_to_local_str, utcnow
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
@ -94,13 +90,12 @@ def get_local_ip():
# Use Google Public DNS server to determine own IP
sock.connect(('8.8.8.8', 80))
ip_addr = sock.getsockname()[0]
sock.close()
return ip_addr
return sock.getsockname()[0]
except socket.error:
return socket.gethostbyname(socket.gethostname())
finally:
sock.close()
# Taken from http://stackoverflow.com/a/23728630

View File

@ -5,9 +5,6 @@ pytz>=2015.2
# Optional, needed for specific components
# Discovery platform (discovery)
zeroconf>=0.16.0
# Sun (sun)
astral>=0.8.1
@ -77,5 +74,39 @@ python-forecastio>=1.3.3
# Firmata Bindings (*.arduino)
PyMata==2.07a
# Mysensors serial gateway
pyserial>=2.7
#Rfxtrx sensor
https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip
# Mysensors
https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1
# Netgear (device_tracker.netgear)
pynetgear>=0.1
# Netdisco (discovery)
netdisco>=0.1
# Wemo (switch.wemo)
pywemo>=0.1
# Wink (*.wink)
https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1
# Slack notifier
slacker>=0.6.8
# Temper sensors
https://github.com/rkabadi/temper-python/archive/master.zip
# PyEdimax
https://github.com/rkabadi/pyedimax/archive/master.zip
# RPI-GPIO platform
RPi.GPIO >=0.5.11
# PAHO MQTT Binding (protocol.mqtt)
paho-mqtt>=1.1
# PyModbus (modbus)
https://github.com/bashwork/pymodbus/archive/python3.zip#pymodbus>=1.2.0

View File

@ -52,30 +52,28 @@ def mock_service(hass, domain, service):
return calls
def fire_time_changed(hass, time):
hass.bus.fire(EVENT_TIME_CHANGED, {'now': time})
def trigger_device_tracker_scan(hass):
""" Triggers the device tracker to scan. """
hass.bus.fire(
EVENT_TIME_CHANGED,
{'now':
dt_util.utcnow().replace(second=0) + timedelta(hours=1)})
fire_time_changed(
hass, dt_util.utcnow().replace(second=0) + timedelta(hours=1))
def ensure_sun_risen(hass):
""" Trigger sun to rise if below horizon. """
if not sun.is_on(hass):
hass.bus.fire(
EVENT_TIME_CHANGED,
{'now':
sun.next_rising_utc(hass) + timedelta(seconds=10)})
if sun.is_on(hass):
return
fire_time_changed(hass, sun.next_rising_utc(hass) + timedelta(seconds=10))
def ensure_sun_set(hass):
""" Trigger sun to set if above horizon. """
if sun.is_on(hass):
hass.bus.fire(
EVENT_TIME_CHANGED,
{'now':
sun.next_setting_utc(hass) + timedelta(seconds=10)})
if not sun.is_on(hass):
return
fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10))
def mock_state_change_event(hass, new_state, old_state=None):

View File

View File

@ -9,7 +9,7 @@ import unittest
import homeassistant as ha
import homeassistant.components.demo as demo
from helpers import mock_http_component
from tests.common import mock_http_component
class TestDemo(unittest.TestCase):

View File

@ -14,7 +14,7 @@ from homeassistant.components import (
device_tracker, light, sun, device_sun_light_trigger)
from helpers import (
from tests.common import (
get_test_home_assistant, ensure_sun_risen, ensure_sun_set,
trigger_device_tracker_scan)

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME)
import homeassistant.components.device_tracker as device_tracker
from helpers import get_test_home_assistant
from tests.common import get_test_home_assistant
def setUpModule(): # pylint: disable=invalid-name

View File

@ -199,8 +199,7 @@ class TestComponentsGroup(unittest.TestCase):
self.hass,
{
group.DOMAIN: {
'second_group': ','.join((self.group_entity_id,
'light.Bowl'))
'second_group': self.group_entity_id + ',light.Bowl'
}
}))

View File

@ -13,7 +13,7 @@ import homeassistant as ha
import homeassistant.util.dt as dt_util
from homeassistant.components import history, recorder
from helpers import (
from tests.common import (
mock_http_component, mock_state_change_event, get_test_home_assistant)

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_OFF)
import homeassistant.components.light as light
from helpers import mock_service, get_test_home_assistant
from tests.common import mock_service, get_test_home_assistant
class TestLight(unittest.TestCase):

View File

@ -14,7 +14,7 @@ from homeassistant.const import (
import homeassistant.util.dt as dt_util
from homeassistant.components import logbook
from helpers import get_test_home_assistant, mock_http_component
from tests.common import get_test_home_assistant, mock_http_component
class TestComponentHistory(unittest.TestCase):

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID)
import homeassistant.components.media_player as media_player
from helpers import mock_service
from tests.common import mock_service
def setUpModule(): # pylint: disable=invalid-name

View File

@ -11,7 +11,7 @@ import os
from homeassistant.const import MATCH_ALL
from homeassistant.components import recorder
from helpers import get_test_home_assistant
from tests.common import get_test_home_assistant
class TestRecorder(unittest.TestCase):

View File

@ -11,7 +11,7 @@ import homeassistant.loader as loader
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
import homeassistant.components.switch as switch
from helpers import get_test_home_assistant
from tests.common import get_test_home_assistant
class TestSwitch(unittest.TestCase):

View File

@ -7,7 +7,7 @@ Provides a mock switch platform.
Call init before using it in your tests to ensure clean test data.
"""
from homeassistant.const import STATE_ON, STATE_OFF
from tests.helpers import MockToggleDevice
from tests.common import MockToggleDevice
DEVICES = []

View File

@ -7,7 +7,7 @@ Provides a mock switch platform.
Call init before using it in your tests to ensure clean test data.
"""
from homeassistant.const import STATE_ON, STATE_OFF
from tests.helpers import MockToggleDevice
from tests.common import MockToggleDevice
DEVICES = []

View File

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