commit
ee73bd7dea
29
.coveragerc
29
.coveragerc
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
"""
|
||||
homeassistant.components.automation.mqtt
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Offers MQTT listening automation rules.
|
||||
"""
|
||||
import logging
|
||||
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
CONF_TOPIC = 'mqtt_topic'
|
||||
CONF_PAYLOAD = 'mqtt_payload'
|
||||
|
||||
|
||||
def register(hass, config, action):
|
||||
""" Listen for state changes based on `config`. """
|
||||
topic = config.get(CONF_TOPIC)
|
||||
payload = config.get(CONF_PAYLOAD)
|
||||
|
||||
if topic is None:
|
||||
logging.getLogger(__name__).error(
|
||||
"Missing configuration key %s", CONF_TOPIC)
|
||||
return False
|
||||
|
||||
def mqtt_automation_listener(msg_topic, msg_payload, qos):
|
||||
""" Listens for MQTT messages. """
|
||||
if payload is None or payload == msg_payload:
|
||||
action()
|
||||
|
||||
mqtt.subscribe(hass, topic, mqtt_automation_listener)
|
||||
|
||||
return True
|
|
@ -6,6 +6,7 @@ Offers state listening automation rules.
|
|||
"""
|
||||
import logging
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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. """
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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. """
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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__)
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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. """
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
"""
|
||||
homeassistant.components.media_player.squeezebox
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides an interface to the Logitech SqueezeBox API
|
||||
|
||||
Configuration:
|
||||
|
||||
To use SqueezeBox add something like this to your configuration:
|
||||
|
||||
media_player:
|
||||
platform: squeezebox
|
||||
host: 192.168.1.21
|
||||
port: 9090
|
||||
username: user
|
||||
password: password
|
||||
|
||||
Variables:
|
||||
|
||||
host
|
||||
*Required
|
||||
The host name or address of the Logitech Media Server
|
||||
|
||||
port
|
||||
*Optional
|
||||
Telnet port to Logitech Media Server, default 9090
|
||||
|
||||
usermame
|
||||
*Optional
|
||||
Username, if password protection is enabled
|
||||
|
||||
password
|
||||
*Optional
|
||||
Password, if password protection is enabled
|
||||
"""
|
||||
|
||||
import logging
|
||||
import telnetlib
|
||||
import urllib.parse
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
|
||||
MEDIA_TYPE_MUSIC, DOMAIN)
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
|
||||
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK |\
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the squeezebox platform. """
|
||||
if not config.get(CONF_HOST):
|
||||
_LOGGER.error(
|
||||
"Missing required configuration items in %s: %s",
|
||||
DOMAIN,
|
||||
CONF_HOST)
|
||||
return False
|
||||
|
||||
lms = LogitechMediaServer(
|
||||
config.get(CONF_HOST),
|
||||
config.get('port', '9090'),
|
||||
config.get(CONF_USERNAME),
|
||||
config.get(CONF_PASSWORD))
|
||||
|
||||
if not lms.init_success:
|
||||
return False
|
||||
|
||||
add_devices(lms.create_players())
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class LogitechMediaServer(object):
|
||||
""" Represents a Logitech media server. """
|
||||
|
||||
def __init__(self, host, port, username, password):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self._username = username
|
||||
self._password = password
|
||||
self.http_port = self._get_http_port()
|
||||
self.init_success = True if self.http_port else False
|
||||
|
||||
def _get_http_port(self):
|
||||
""" Get http port from media server, it is used to get cover art """
|
||||
http_port = None
|
||||
try:
|
||||
http_port = self.query('pref', 'httpport', '?')
|
||||
if not http_port:
|
||||
_LOGGER.error(
|
||||
"Unable to read data from server %s:%s",
|
||||
self.host,
|
||||
self.port)
|
||||
return
|
||||
return http_port
|
||||
except ConnectionError as ex:
|
||||
_LOGGER.error(
|
||||
"Failed to connect to server %s:%s - %s",
|
||||
self.host,
|
||||
self.port,
|
||||
ex)
|
||||
return
|
||||
|
||||
def create_players(self):
|
||||
""" Create a list of SqueezeBoxDevices connected to the LMS """
|
||||
players = []
|
||||
count = self.query('player', 'count', '?')
|
||||
for index in range(0, int(count)):
|
||||
player_id = self.query('player', 'id', str(index), '?')
|
||||
player = SqueezeBoxDevice(self, player_id)
|
||||
players.append(player)
|
||||
return players
|
||||
|
||||
def query(self, *parameters):
|
||||
""" Send request and await response from server """
|
||||
telnet = telnetlib.Telnet(self.host, self.port)
|
||||
if self._username and self._password:
|
||||
telnet.write('login {username} {password}\n'.format(
|
||||
username=self._username,
|
||||
password=self._password).encode('UTF-8'))
|
||||
telnet.read_until(b'\n', timeout=3)
|
||||
message = '{}\n'.format(' '.join(parameters))
|
||||
telnet.write(message.encode('UTF-8'))
|
||||
response = telnet.read_until(b'\n', timeout=3)\
|
||||
.decode('UTF-8')\
|
||||
.split(' ')[-1]\
|
||||
.strip()
|
||||
telnet.write(b'exit\n')
|
||||
return urllib.parse.unquote(response)
|
||||
|
||||
def get_player_status(self, player):
|
||||
""" Get ithe status of a player """
|
||||
# (title) : Song title
|
||||
# Requested Information
|
||||
# a (artist): Artist name 'artist'
|
||||
# d (duration): Song duration in seconds 'duration'
|
||||
# K (artwork_url): URL to remote artwork
|
||||
tags = 'adK'
|
||||
new_status = {}
|
||||
telnet = telnetlib.Telnet(self.host, self.port)
|
||||
telnet.write('{player} status - 1 tags:{tags}\n'.format(
|
||||
player=player,
|
||||
tags=tags
|
||||
).encode('UTF-8'))
|
||||
response = telnet.read_until(b'\n', timeout=3)\
|
||||
.decode('UTF-8')\
|
||||
.split(' ')
|
||||
telnet.write(b'exit\n')
|
||||
for item in response:
|
||||
parts = urllib.parse.unquote(item).partition(':')
|
||||
new_status[parts[0]] = parts[2]
|
||||
return new_status
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-public-methods
|
||||
class SqueezeBoxDevice(MediaPlayerDevice):
|
||||
""" Represents a SqueezeBox device. """
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, lms, player_id):
|
||||
super(SqueezeBoxDevice, self).__init__()
|
||||
self._lms = lms
|
||||
self._id = player_id
|
||||
self._name = self._lms.query(self._id, 'name', '?')
|
||||
self._status = self._lms.get_player_status(self._id)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
if 'power' in self._status and self._status['power'] == '0':
|
||||
return STATE_OFF
|
||||
if 'mode' in self._status:
|
||||
if self._status['mode'] == 'pause':
|
||||
return STATE_PAUSED
|
||||
if self._status['mode'] == 'play':
|
||||
return STATE_PLAYING
|
||||
if self._status['mode'] == 'stop':
|
||||
return STATE_IDLE
|
||||
return STATE_UNKNOWN
|
||||
|
||||
def update(self):
|
||||
""" Retrieve latest state. """
|
||||
self._status = self._lms.get_player_status(self._name)
|
||||
|
||||
@property
|
||||
def volume_level(self):
|
||||
""" Volume level of the media player (0..1). """
|
||||
if 'mixer volume' in self._status:
|
||||
return int(self._status['mixer volume']) / 100.0
|
||||
|
||||
@property
|
||||
def is_volume_muted(self):
|
||||
if 'mixer volume' in self._status:
|
||||
return self._status['mixer volume'].startswith('-')
|
||||
|
||||
@property
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
if 'current_title' in self._status:
|
||||
return self._status['current_title']
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
return MEDIA_TYPE_MUSIC
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
if 'duration' in self._status:
|
||||
return int(float(self._status['duration']))
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
if 'artwork_url' in self._status:
|
||||
return self._status['artwork_url']
|
||||
return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\
|
||||
.format(
|
||||
server=self._lms.host,
|
||||
port=self._lms.http_port,
|
||||
player=self._id)
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
if 'artist' in self._status and 'title' in self._status:
|
||||
return '{artist} - {title}'.format(
|
||||
artist=self._status['artist'],
|
||||
title=self._status['title']
|
||||
)
|
||||
if 'current_title' in self._status:
|
||||
return self._status['current_title']
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return SUPPORT_SQUEEZEBOX
|
||||
|
||||
def turn_off(self):
|
||||
""" turn_off media player. """
|
||||
self._lms.query(self._id, 'power', '0')
|
||||
self.update_ha_state()
|
||||
|
||||
def volume_up(self):
|
||||
""" volume_up media player. """
|
||||
self._lms.query(self._id, 'mixer', 'volume', '+5')
|
||||
self.update_ha_state()
|
||||
|
||||
def volume_down(self):
|
||||
""" volume_down media player. """
|
||||
self._lms.query(self._id, 'mixer', 'volume', '-5')
|
||||
self.update_ha_state()
|
||||
|
||||
def set_volume_level(self, volume):
|
||||
""" set volume level, range 0..1. """
|
||||
volume_percent = str(int(volume*100))
|
||||
self._lms.query(self._id, 'mixer', 'volume', volume_percent)
|
||||
self.update_ha_state()
|
||||
|
||||
def mute_volume(self, mute):
|
||||
""" mute (true) or unmute (false) media player. """
|
||||
mute_numeric = '1' if mute else '0'
|
||||
self._lms.query(self._id, 'mixer', 'muting', mute_numeric)
|
||||
self.update_ha_state()
|
||||
|
||||
def media_play_pause(self):
|
||||
""" media_play_pause media player. """
|
||||
self._lms.query(self._id, 'pause')
|
||||
self.update_ha_state()
|
||||
|
||||
def media_play(self):
|
||||
""" media_play media player. """
|
||||
self._lms.query(self._id, 'play')
|
||||
self.update_ha_state()
|
||||
|
||||
def media_pause(self):
|
||||
""" media_pause media player. """
|
||||
self._lms.query(self._id, 'pause', '0')
|
||||
self.update_ha_state()
|
||||
|
||||
def media_next_track(self):
|
||||
""" Send next track command. """
|
||||
self._lms.query(self._id, 'playlist', 'index', '+1')
|
||||
self.update_ha_state()
|
||||
|
||||
def media_previous_track(self):
|
||||
""" Send next track command. """
|
||||
self._lms.query(self._id, 'playlist', 'index', '-1')
|
||||
self.update_ha_state()
|
||||
|
||||
def media_seek(self, position):
|
||||
""" Send seek command. """
|
||||
self._lms.query(self._id, 'time', position)
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_on(self):
|
||||
""" turn the media player on. """
|
||||
self._lms.query(self._id, 'power', '1')
|
||||
self.update_ha_state()
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
raise NotImplementedError()
|
|
@ -5,6 +5,11 @@ Modbus component, using pymodbus (python3 branch)
|
|||
|
||||
Configuration:
|
||||
|
||||
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"
|
||||
|
|
|
@ -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))
|
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
homeassistant.components.notify.slack
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Slack platform for notify component.
|
||||
|
||||
Configuration:
|
||||
|
||||
To use the Slack notifier you will need to add something like the following
|
||||
to your config/configuration.yaml
|
||||
|
||||
notify:
|
||||
platform: slack
|
||||
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
||||
default_channel: '#general'
|
||||
|
||||
Variables:
|
||||
|
||||
api_key
|
||||
*Required
|
||||
The slack API token to use for sending slack messages.
|
||||
You can get your slack API token here https://api.slack.com/web?sudo=1
|
||||
|
||||
default_channel
|
||||
*Required
|
||||
The default channel to post to if no channel is explicitly specified when
|
||||
sending the notification message.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.components.notify import (
|
||||
DOMAIN, BaseNotificationService)
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
|
||||
REQUIREMENTS = ['slacker>=0.6.8']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def get_service(hass, config):
|
||||
""" Get the slack notification service. """
|
||||
|
||||
if not validate_config(config,
|
||||
{DOMAIN: ['default_channel', CONF_API_KEY]},
|
||||
_LOGGER):
|
||||
return None
|
||||
|
||||
try:
|
||||
# pylint: disable=no-name-in-module, unused-variable
|
||||
from slacker import Error as SlackError
|
||||
|
||||
except ImportError:
|
||||
_LOGGER.exception(
|
||||
"Unable to import slacker. "
|
||||
"Did you maybe not install the 'slacker.py' package?")
|
||||
|
||||
return None
|
||||
|
||||
try:
|
||||
api_token = config[DOMAIN].get(CONF_API_KEY)
|
||||
|
||||
return SlackNotificationService(
|
||||
config[DOMAIN]['default_channel'],
|
||||
api_token)
|
||||
|
||||
except SlackError as ex:
|
||||
_LOGGER.error(
|
||||
"Slack authentication failed")
|
||||
_LOGGER.exception(ex)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class SlackNotificationService(BaseNotificationService):
|
||||
""" Implements notification service for Slack. """
|
||||
|
||||
def __init__(self, default_channel, api_token):
|
||||
from slacker import Slacker
|
||||
self._default_channel = default_channel
|
||||
self._api_token = api_token
|
||||
self.slack = Slacker(self._api_token)
|
||||
self.slack.auth.test()
|
||||
|
||||
def send_message(self, message="", **kwargs):
|
||||
""" Send a message to a user. """
|
||||
|
||||
from slacker import Error as SlackError
|
||||
channel = kwargs.get('channel', self._default_channel)
|
||||
try:
|
||||
self.slack.chat.post_message(channel, message)
|
||||
except SlackError as ex:
|
||||
_LOGGER.exception("Could not send slack notification")
|
||||
_LOGGER.exception(ex)
|
|
@ -1,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
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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', ''],
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"')
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
homeassistant.components.sensor.zwave
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Interfaces with Z-Wave sensors.
|
||||
"""
|
||||
# pylint: disable=import-error
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
homeassistant.components.zwave
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Connects Home Assistant to a Z-Wave network.
|
||||
"""
|
||||
from pprint import pprint
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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. """
|
||||
|
|
|
@ -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')
|
|
@ -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')
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
|
@ -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):
|
|
@ -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)
|
||||
|
|
@ -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
|
|
@ -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'
|
||||
}
|
||||
}))
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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):
|
|
@ -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):
|
|
@ -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
|
|
@ -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):
|
|
@ -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):
|
|
@ -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 = []
|
||||
|
|
|
@ -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 = []
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue