mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Merge branch 'dev' into cleanup
Conflicts: .coveragerc
This commit is contained in:
commit
b2cfce7243
@ -27,6 +27,7 @@ omit =
|
|||||||
|
|
||||||
homeassistant/components/browser.py
|
homeassistant/components/browser.py
|
||||||
homeassistant/components/camera/*
|
homeassistant/components/camera/*
|
||||||
|
homeassistant/components/device_tracker/asuswrt.py
|
||||||
homeassistant/components/device_tracker/ddwrt.py
|
homeassistant/components/device_tracker/ddwrt.py
|
||||||
homeassistant/components/device_tracker/luci.py
|
homeassistant/components/device_tracker/luci.py
|
||||||
homeassistant/components/device_tracker/netgear.py
|
homeassistant/components/device_tracker/netgear.py
|
||||||
@ -50,6 +51,7 @@ omit =
|
|||||||
homeassistant/components/notify/syslog.py
|
homeassistant/components/notify/syslog.py
|
||||||
homeassistant/components/notify/xmpp.py
|
homeassistant/components/notify/xmpp.py
|
||||||
homeassistant/components/sensor/bitcoin.py
|
homeassistant/components/sensor/bitcoin.py
|
||||||
|
homeassistant/components/sensor/edimax.py
|
||||||
homeassistant/components/sensor/efergy.py
|
homeassistant/components/sensor/efergy.py
|
||||||
homeassistant/components/sensor/forecast.py
|
homeassistant/components/sensor/forecast.py
|
||||||
homeassistant/components/sensor/mysensors.py
|
homeassistant/components/sensor/mysensors.py
|
||||||
@ -57,6 +59,7 @@ omit =
|
|||||||
homeassistant/components/sensor/sabnzbd.py
|
homeassistant/components/sensor/sabnzbd.py
|
||||||
homeassistant/components/sensor/swiss_public_transport.py
|
homeassistant/components/sensor/swiss_public_transport.py
|
||||||
homeassistant/components/sensor/systemmonitor.py
|
homeassistant/components/sensor/systemmonitor.py
|
||||||
|
homeassistant/components/sensor/temper.py
|
||||||
homeassistant/components/sensor/time_date.py
|
homeassistant/components/sensor/time_date.py
|
||||||
homeassistant/components/sensor/transmission.py
|
homeassistant/components/sensor/transmission.py
|
||||||
homeassistant/components/switch/command_switch.py
|
homeassistant/components/switch/command_switch.py
|
||||||
|
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -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"]
|
[submodule "homeassistant/external/noop"]
|
||||||
path = homeassistant/external/noop
|
path = homeassistant/external/noop
|
||||||
url = https://github.com/balloob/noop.git
|
url = https://github.com/balloob/noop.git
|
||||||
@ -16,9 +7,6 @@
|
|||||||
[submodule "homeassistant/external/nzbclients"]
|
[submodule "homeassistant/external/nzbclients"]
|
||||||
path = homeassistant/external/nzbclients
|
path = homeassistant/external/nzbclients
|
||||||
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
|
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"]
|
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
|
||||||
path = homeassistant/components/frontend/www_static/home-assistant-polymer
|
path = homeassistant/components/frontend/www_static/home-assistant-polymer
|
||||||
url = https://github.com/balloob/home-assistant-polymer.git
|
url = https://github.com/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:
|
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
|
* [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/)
|
* [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/)
|
* 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/)
|
||||||
|
167
homeassistant/components/device_tracker/asuswrt.py
Normal file
167
homeassistant/components/device_tracker/asuswrt.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.device_tracker.asuswrt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Device tracker platform that supports scanning a ASUSWRT router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
This device tracker needs telnet to be enabled on the router
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the ASUSWRT tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: asuswrt
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_LEASES_REGEX = re.compile(
|
||||||
|
r'\w+\s' +
|
||||||
|
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' +
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
|
||||||
|
r'(?P<host>([^\s]+))')
|
||||||
|
|
||||||
|
_IP_NEIGH_REGEX = re.compile(
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
|
||||||
|
r'\w+\s' +
|
||||||
|
r'\w+\s' +
|
||||||
|
r'(\w+\s(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s' +
|
||||||
|
r'(?P<status>(\w+))')
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
""" Validates config and returns a DD-WRT scanner. """
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
scanner = AsusWrtDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
|
|
||||||
|
class AsusWrtDeviceScanner(object):
|
||||||
|
""" This class queries a router running ASUSWRT firmware
|
||||||
|
for connected devices. Adapted from DD-WRT scanner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.host = config[CONF_HOST]
|
||||||
|
self.username = config[CONF_USERNAME]
|
||||||
|
self.password = config[CONF_PASSWORD]
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
self.last_results = {}
|
||||||
|
|
||||||
|
# Test the router is accessible
|
||||||
|
data = self.get_asuswrt_data()
|
||||||
|
self.success_init = data is not None
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
return [client['mac'] for client in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" Returns the name of the given device or None if we don't know. """
|
||||||
|
if not self.last_results:
|
||||||
|
return None
|
||||||
|
for client in self.last_results:
|
||||||
|
if client['mac'] == device:
|
||||||
|
return client['host']
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def _update_info(self):
|
||||||
|
""" Ensures the information from the ASUSWRT router is up to date.
|
||||||
|
Returns boolean if scanning successful. """
|
||||||
|
if not self.success_init:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
_LOGGER.info("Checking ARP")
|
||||||
|
data = self.get_asuswrt_data()
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
active_clients = [client for client in data.values() if
|
||||||
|
client['status'] == 'REACHABLE' or
|
||||||
|
client['status'] == 'DELAY' or
|
||||||
|
client['status'] == 'STALE']
|
||||||
|
self.last_results = active_clients
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_asuswrt_data(self):
|
||||||
|
""" Retrieve data from ASUSWRT and return parsed result. """
|
||||||
|
try:
|
||||||
|
telnet = telnetlib.Telnet(self.host)
|
||||||
|
telnet.read_until(b'login: ')
|
||||||
|
telnet.write((self.username + '\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'Password: ')
|
||||||
|
telnet.write((self.password + '\n').encode('ascii'))
|
||||||
|
prompt_string = telnet.read_until(b'#').split(b'\n')[-1]
|
||||||
|
telnet.write('ip neigh\n'.encode('ascii'))
|
||||||
|
neighbors = telnet.read_until(prompt_string).split(b'\n')[1:-1]
|
||||||
|
telnet.write('cat /var/lib/misc/dnsmasq.leases\n'.encode('ascii'))
|
||||||
|
leases_result = telnet.read_until(prompt_string).split(b'\n')[1:-1]
|
||||||
|
telnet.write('exit\n'.encode('ascii'))
|
||||||
|
except EOFError:
|
||||||
|
_LOGGER.exception("Unexpected response from router")
|
||||||
|
return
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
_LOGGER.exception("Connection refused by router," +
|
||||||
|
" is telnet enabled?")
|
||||||
|
return
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for lease in leases_result:
|
||||||
|
match = _LEASES_REGEX.search(lease.decode('utf-8'))
|
||||||
|
devices[match.group('ip')] = {
|
||||||
|
'ip': match.group('ip'),
|
||||||
|
'mac': match.group('mac').upper(),
|
||||||
|
'host': match.group('host')
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
REQUIREMENTS = ['pynetgear>=0.1']
|
||||||
|
|
||||||
|
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
@ -64,22 +65,10 @@ class NetgearDeviceScanner(object):
|
|||||||
""" This class queries a Netgear wireless router using the SOAP-API. """
|
""" This class queries a Netgear wireless router using the SOAP-API. """
|
||||||
|
|
||||||
def __init__(self, host, username, password):
|
def __init__(self, host, username, password):
|
||||||
|
import pynetgear
|
||||||
|
|
||||||
self.last_results = []
|
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._api = pynetgear.Netgear(host, username, password)
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
|
||||||
import homeassistant.external.netdisco.netdisco.const as services
|
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_PLATFORM_DISCOVERED,
|
EVENT_HOMEASSISTANT_START, EVENT_PLATFORM_DISCOVERED,
|
||||||
@ -22,14 +19,20 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "discovery"
|
DOMAIN = "discovery"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['zeroconf>=0.16.0']
|
REQUIREMENTS = ['netdisco>=0.1']
|
||||||
|
|
||||||
SCAN_INTERVAL = 300 # seconds
|
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 = {
|
SERVICE_HANDLERS = {
|
||||||
services.BELKIN_WEMO: "switch",
|
SERVICE_WEMO: "switch",
|
||||||
services.GOOGLE_CAST: "media_player",
|
SERVICE_CAST: "media_player",
|
||||||
services.PHILIPS_HUE: "light",
|
SERVICE_HUE: "light",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -56,14 +59,7 @@ def setup(hass, config):
|
|||||||
""" Starts a discovery service. """
|
""" Starts a discovery service. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
from netdisco.service import DiscoveryService
|
||||||
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
|
|
||||||
|
|
||||||
# Disable zeroconf logging, it spams
|
# Disable zeroconf logging, it spams
|
||||||
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
|
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "4f94fd4404583fbf27cc899c024d26ff"
|
VERSION = "ccfe7497d635ab4df3e6943b05adbd9b"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
Subproject commit 576c04efb49a8a5f7f35734458ffc93f874dd68d
|
Subproject commit 5a3fcc970b30d640e6a370b6f20904a745f69659
|
@ -99,7 +99,7 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
|
|||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
wink.DISCOVER_LIGHTS: 'wink',
|
wink.DISCOVER_LIGHTS: 'wink',
|
||||||
isy994.DISCOVER_LIGHTS: 'isy994',
|
isy994.DISCOVER_LIGHTS: 'isy994',
|
||||||
discovery.services.PHILIPS_HUE: 'hue',
|
discovery.SERVICE_HUE: 'hue',
|
||||||
}
|
}
|
||||||
|
|
||||||
PROP_TO_ATTR = {
|
PROP_TO_ATTR = {
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
""" Support for Hue lights. """
|
""" Support for Wink lights. """
|
||||||
import logging
|
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.light import ATTR_BRIGHTNESS
|
||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
@ -11,6 +8,8 @@ from homeassistant.const import CONF_ACCESS_TOKEN
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return Wink lights. """
|
""" Find and return Wink lights. """
|
||||||
|
import pywink
|
||||||
|
|
||||||
token = config.get(CONF_ACCESS_TOKEN)
|
token = config.get(CONF_ACCESS_TOKEN)
|
||||||
|
|
||||||
if not pywink.is_token_set() and token is None:
|
if not pywink.is_token_set() and token is None:
|
||||||
|
@ -24,7 +24,7 @@ SCAN_INTERVAL = 30
|
|||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
discovery.services.GOOGLE_CAST: 'cast',
|
discovery.SERVICE_CAST: 'cast',
|
||||||
}
|
}
|
||||||
|
|
||||||
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
||||||
|
94
homeassistant/components/notify/slack.py
Normal file
94
homeassistant/components/notify/slack.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.notify.slack
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Slack platform for notify component.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Slack notifier you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
notify:
|
||||||
|
platform: slack
|
||||||
|
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
||||||
|
default_channel: '#general'
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
api_key
|
||||||
|
*Required
|
||||||
|
The slack API token to use for sending slack messages.
|
||||||
|
You can get your slack API token here https://api.slack.com/web?sudo=1
|
||||||
|
|
||||||
|
default_channel
|
||||||
|
*Required
|
||||||
|
The default channel to post to if no channel is explicitly specified when
|
||||||
|
sending the notification message.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.notify import (
|
||||||
|
DOMAIN, BaseNotificationService)
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
|
REQUIREMENTS = ['slacker>=0.6.8']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-variable
|
||||||
|
def get_service(hass, config):
|
||||||
|
""" Get the slack notification service. """
|
||||||
|
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: ['default_channel', CONF_API_KEY]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-name-in-module, unused-variable
|
||||||
|
from slacker import Error as SlackError
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import slacker. "
|
||||||
|
"Did you maybe not install the 'slacker.py' package?")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
api_token = config[DOMAIN].get(CONF_API_KEY)
|
||||||
|
|
||||||
|
return SlackNotificationService(
|
||||||
|
config[DOMAIN]['default_channel'],
|
||||||
|
api_token)
|
||||||
|
|
||||||
|
except SlackError as ex:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Slack authentication failed")
|
||||||
|
_LOGGER.exception(ex)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class SlackNotificationService(BaseNotificationService):
|
||||||
|
""" Implements notification service for Slack. """
|
||||||
|
|
||||||
|
def __init__(self, default_channel, api_token):
|
||||||
|
from slacker import Slacker
|
||||||
|
self._default_channel = default_channel
|
||||||
|
self._api_token = api_token
|
||||||
|
self.slack = Slacker(self._api_token)
|
||||||
|
self.slack.auth.test()
|
||||||
|
|
||||||
|
def send_message(self, message="", **kwargs):
|
||||||
|
""" Send a message to a user. """
|
||||||
|
|
||||||
|
from slacker import Error as SlackError
|
||||||
|
channel = kwargs.get('channel', self._default_channel)
|
||||||
|
try:
|
||||||
|
self.slack.chat.post_message(channel, message)
|
||||||
|
except SlackError as ex:
|
||||||
|
_LOGGER.exception("Could not send slack notification")
|
||||||
|
_LOGGER.exception(ex)
|
@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.forecast
|
homeassistant.components.sensor.forecast
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Forecast.io service.
|
Forecast.io service.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
@ -121,10 +120,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class ForeCastSensor(Entity):
|
class ForeCastSensor(Entity):
|
||||||
""" Implements an OpenWeatherMap sensor. """
|
""" Implements an Forecast.io sensor. """
|
||||||
|
|
||||||
def __init__(self, weather_data, sensor_type, unit):
|
def __init__(self, weather_data, sensor_type, unit):
|
||||||
self.client_name = 'Forecast'
|
self.client_name = 'Weather'
|
||||||
self._name = SENSOR_TYPES[sensor_type][0]
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
self.forecast_client = weather_data
|
self.forecast_client = weather_data
|
||||||
self._unit = unit
|
self._unit = unit
|
||||||
@ -135,7 +134,7 @@ class ForeCastSensor(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return '{} - {}'.format(self.client_name, self._name)
|
return '{} {}'.format(self.client_name, self._name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -157,10 +156,6 @@ class ForeCastSensor(Entity):
|
|||||||
try:
|
try:
|
||||||
if self.type == 'summary':
|
if self.type == 'summary':
|
||||||
self._state = data.summary
|
self._state = data.summary
|
||||||
# elif self.type == 'sunrise_time':
|
|
||||||
# self._state = data.sunriseTime
|
|
||||||
# elif self.type == 'sunset_time':
|
|
||||||
# self._state = data.sunsetTime
|
|
||||||
elif self.type == 'precip_intensity':
|
elif self.type == 'precip_intensity':
|
||||||
if data.precipIntensity == 0:
|
if data.precipIntensity == 0:
|
||||||
self._state = 'None'
|
self._state = 'None'
|
||||||
|
@ -18,6 +18,9 @@ sensor:
|
|||||||
name: My boolean sensor
|
name: My boolean sensor
|
||||||
2:
|
2:
|
||||||
name: My other boolean sensor
|
name: My other boolean sensor
|
||||||
|
coils:
|
||||||
|
0:
|
||||||
|
name: My coil switch
|
||||||
|
|
||||||
VARIABLES:
|
VARIABLES:
|
||||||
|
|
||||||
@ -25,6 +28,7 @@ VARIABLES:
|
|||||||
- "unit" = unit to attach to value (optional, ignored for boolean sensors)
|
- "unit" = unit to attach to value (optional, ignored for boolean sensors)
|
||||||
- "registers" contains a list of relevant registers to read from
|
- "registers" contains a list of relevant registers to read from
|
||||||
it can contain a "bits" section, listing relevant bits
|
it can contain a "bits" section, listing relevant bits
|
||||||
|
- "coils" contains a list of relevant coils to read from
|
||||||
|
|
||||||
- each named register will create an integer sensor
|
- each named register will create an integer sensor
|
||||||
- each named bit will create a boolean sensor
|
- each named bit will create a boolean sensor
|
||||||
@ -49,6 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
_LOGGER.error("No slave number provided for serial Modbus")
|
_LOGGER.error("No slave number provided for serial Modbus")
|
||||||
return False
|
return False
|
||||||
registers = config.get("registers")
|
registers = config.get("registers")
|
||||||
|
if registers:
|
||||||
for regnum, register in registers.items():
|
for regnum, register in registers.items():
|
||||||
if register.get("name"):
|
if register.get("name"):
|
||||||
sensors.append(ModbusSensor(register.get("name"),
|
sensors.append(ModbusSensor(register.get("name"),
|
||||||
@ -64,6 +69,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
slave,
|
slave,
|
||||||
regnum,
|
regnum,
|
||||||
bitnum))
|
bitnum))
|
||||||
|
coils = config.get("coils")
|
||||||
|
if coils:
|
||||||
|
for coilnum, coil in coils.items():
|
||||||
|
sensors.append(ModbusSensor(coil.get("name"),
|
||||||
|
slave,
|
||||||
|
coilnum,
|
||||||
|
coil=True))
|
||||||
|
|
||||||
add_devices(sensors)
|
add_devices(sensors)
|
||||||
|
|
||||||
|
|
||||||
@ -71,13 +84,14 @@ class ModbusSensor(Entity):
|
|||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
""" Represents a Modbus Sensor """
|
""" Represents a Modbus Sensor """
|
||||||
|
|
||||||
def __init__(self, name, slave, register, bit=None, unit=None):
|
def __init__(self, name, slave, register, bit=None, unit=None, coil=False):
|
||||||
self._name = name
|
self._name = name
|
||||||
self.slave = int(slave) if slave else 1
|
self.slave = int(slave) if slave else 1
|
||||||
self.register = int(register)
|
self.register = int(register)
|
||||||
self.bit = int(bit) if bit else None
|
self.bit = int(bit) if bit else None
|
||||||
self._value = None
|
self._value = None
|
||||||
self._unit = unit
|
self._unit = unit
|
||||||
|
self._coil = coil
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: %s" % (self.name, self.state)
|
return "%s: %s" % (self.name, self.state)
|
||||||
@ -118,14 +132,14 @@ class ModbusSensor(Entity):
|
|||||||
else:
|
else:
|
||||||
return self._unit
|
return self._unit
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
attr = super().state_attributes
|
|
||||||
return attr
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
result = modbus.NETWORK.read_holding_registers(unit=self.slave,
|
""" Update the state of the sensor. """
|
||||||
address=self.register,
|
if self._coil:
|
||||||
|
result = modbus.NETWORK.read_coils(self.register, 1)
|
||||||
|
self._value = result.bits[0]
|
||||||
|
else:
|
||||||
|
result = modbus.NETWORK.read_holding_registers(
|
||||||
|
unit=self.slave, address=self.register,
|
||||||
count=1)
|
count=1)
|
||||||
val = 0
|
val = 0
|
||||||
for i, res in enumerate(result.registers):
|
for i, res in enumerate(result.registers):
|
||||||
|
@ -21,9 +21,6 @@ Port of your connection to your MySensors device.
|
|||||||
"""
|
"""
|
||||||
import logging
|
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.helpers.entity import Entity
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -39,12 +36,16 @@ ATTR_NODE_ID = "node_id"
|
|||||||
ATTR_CHILD_ID = "child_id"
|
ATTR_CHILD_ID = "child_id"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Setup the mysensors platform. """
|
""" Setup the mysensors platform. """
|
||||||
|
|
||||||
|
import mysensors.mysensors as mysensors
|
||||||
|
import mysensors.const as const
|
||||||
|
|
||||||
devices = {} # keep track of devices added to HA
|
devices = {} # keep track of devices added to HA
|
||||||
# Just assume celcius means that the user wants metric for now.
|
# Just assume celcius means that the user wants metric for now.
|
||||||
# It may make more sense to make this a global config option in the future.
|
# 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)
|
name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id)
|
||||||
node[child_id][value_type] = \
|
node[child_id][value_type] = \
|
||||||
MySensorsNodeValue(
|
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])
|
new_devices.append(node[child_id][value_type])
|
||||||
else:
|
else:
|
||||||
node[child_id][value_type].update_sensor(
|
node[child_id][value_type].update_sensor(
|
||||||
@ -102,8 +103,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
class MySensorsNodeValue(Entity):
|
class MySensorsNodeValue(Entity):
|
||||||
""" Represents the value of a MySensors child node. """
|
""" Represents the value of a MySensors child node. """
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
def __init__(self, node_id, child_id, name, value_type, metric):
|
def __init__(self, node_id, child_id, name, value_type, metric, const):
|
||||||
self._name = name
|
self._name = name
|
||||||
self.node_id = node_id
|
self.node_id = node_id
|
||||||
self.child_id = child_id
|
self.child_id = child_id
|
||||||
@ -111,6 +112,7 @@ class MySensorsNodeValue(Entity):
|
|||||||
self.value_type = value_type
|
self.value_type = value_type
|
||||||
self.metric = metric
|
self.metric = metric
|
||||||
self._value = ''
|
self._value = ''
|
||||||
|
self.const = const
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -130,11 +132,11 @@ class MySensorsNodeValue(Entity):
|
|||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
""" Unit of measurement of this entity. """
|
""" 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
|
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
|
||||||
elif self.value_type == const.SetReq.V_HUM or \
|
elif self.value_type == self.const.SetReq.V_HUM or \
|
||||||
self.value_type == const.SetReq.V_DIMMER or \
|
self.value_type == self.const.SetReq.V_DIMMER or \
|
||||||
self.value_type == const.SetReq.V_LIGHT_LEVEL:
|
self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
|
||||||
return '%'
|
return '%'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -150,8 +152,8 @@ class MySensorsNodeValue(Entity):
|
|||||||
def update_sensor(self, value, battery_level):
|
def update_sensor(self, value, battery_level):
|
||||||
""" Update a sensor with the latest value from the controller. """
|
""" Update a sensor with the latest value from the controller. """
|
||||||
_LOGGER.info("%s value = %s", self._name, value)
|
_LOGGER.info("%s value = %s", self._name, value)
|
||||||
if self.value_type == const.SetReq.V_TRIPPED or \
|
if self.value_type == self.const.SetReq.V_TRIPPED or \
|
||||||
self.value_type == const.SetReq.V_ARMED:
|
self.value_type == self.const.SetReq.V_ARMED:
|
||||||
self._value = STATE_ON if int(value) == 1 else STATE_OFF
|
self._value = STATE_ON if int(value) == 1 else STATE_OFF
|
||||||
else:
|
else:
|
||||||
self._value = value
|
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.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['pywm>=2.2.1']
|
REQUIREMENTS = ['pyowm>=2.2.1']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'weather': ['Condition', ''],
|
'weather': ['Condition', ''],
|
||||||
|
98
homeassistant/components/sensor/rfxtrx.py
Normal file
98
homeassistant/components/sensor/rfxtrx.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.rfxtrx
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Shows sensor values from rfxtrx sensors.
|
||||||
|
|
||||||
|
Possible config keys:
|
||||||
|
device="path to rfxtrx device"
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
sensor 2:
|
||||||
|
platform: rfxtrx
|
||||||
|
device : /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
|
@ -77,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
try:
|
try:
|
||||||
sensor_name = config[ts_sensor.id]
|
sensor_name = config[ts_sensor.id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if 'only_named' in config:
|
if util.convert(config.get('only_named'), bool, False):
|
||||||
continue
|
continue
|
||||||
sensor_name = str(ts_sensor.id)
|
sensor_name = str(ts_sensor.id)
|
||||||
|
|
||||||
|
63
homeassistant/components/sensor/temper.py
Normal file
63
homeassistant/components/sensor/temper.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.temper
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for getting temperature from TEMPer devices
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 within Home Assistant. """
|
||||||
|
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,14 @@
|
|||||||
""" Support for Wink sensors. """
|
""" Support for Wink sensors. """
|
||||||
import logging
|
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.helpers.entity import Entity
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the Wink platform. """
|
""" Sets up the Wink platform. """
|
||||||
|
import pywink
|
||||||
|
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
token = config.get(CONF_ACCESS_TOKEN)
|
token = config.get(CONF_ACCESS_TOKEN)
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
|||||||
|
|
||||||
# Maps discovered services to their platforms
|
# Maps discovered services to their platforms
|
||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
discovery.services.BELKIN_WEMO: 'wemo',
|
discovery.SERVICE_WEMO: 'wemo',
|
||||||
wink.DISCOVER_SWITCHES: 'wink',
|
wink.DISCOVER_SWITCHES: 'wink',
|
||||||
isy994.DISCOVER_SWITCHES: 'isy994',
|
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||||
}
|
}
|
||||||
|
87
homeassistant/components/switch/edimax.py
Normal file
87
homeassistant/components/switch/edimax.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.edimax
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for Edimax switches.
|
||||||
|
"""
|
||||||
|
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 within Home Assistant. """
|
||||||
|
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'
|
@ -18,12 +18,16 @@ sensor:
|
|||||||
name: My switch
|
name: My switch
|
||||||
2:
|
2:
|
||||||
name: My other switch
|
name: My other switch
|
||||||
|
coils:
|
||||||
|
0:
|
||||||
|
name: My coil switch
|
||||||
|
|
||||||
VARIABLES:
|
VARIABLES:
|
||||||
|
|
||||||
- "slave" = slave number (ignored and can be omitted if not serial Modbus)
|
- "slave" = slave number (ignored and can be omitted if not serial Modbus)
|
||||||
- "registers" contains a list of relevant registers to read from
|
- "registers" contains a list of relevant registers to read from
|
||||||
- it must contain a "bits" section, listing relevant bits
|
- it must contain a "bits" section, listing relevant bits
|
||||||
|
- "coils" contains a list of relevant coils to read from/write to
|
||||||
|
|
||||||
- each named bit will create a switch
|
- each named bit will create a switch
|
||||||
"""
|
"""
|
||||||
@ -44,6 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
_LOGGER.error("No slave number provided for serial Modbus")
|
_LOGGER.error("No slave number provided for serial Modbus")
|
||||||
return False
|
return False
|
||||||
registers = config.get("registers")
|
registers = config.get("registers")
|
||||||
|
if registers:
|
||||||
for regnum, register in registers.items():
|
for regnum, register in registers.items():
|
||||||
bits = register.get("bits")
|
bits = register.get("bits")
|
||||||
for bitnum, bit in bits.items():
|
for bitnum, bit in bits.items():
|
||||||
@ -52,17 +57,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
slave,
|
slave,
|
||||||
regnum,
|
regnum,
|
||||||
bitnum))
|
bitnum))
|
||||||
|
coils = config.get("coils")
|
||||||
|
if coils:
|
||||||
|
for coilnum, coil in coils.items():
|
||||||
|
switches.append(ModbusSwitch(coil.get("name"),
|
||||||
|
slave,
|
||||||
|
coilnum,
|
||||||
|
0,
|
||||||
|
coil=True))
|
||||||
add_devices(switches)
|
add_devices(switches)
|
||||||
|
|
||||||
|
|
||||||
class ModbusSwitch(ToggleEntity):
|
class ModbusSwitch(ToggleEntity):
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
""" Represents a Modbus switch. """
|
""" Represents a Modbus switch. """
|
||||||
|
|
||||||
def __init__(self, name, slave, register, bit):
|
def __init__(self, name, slave, register, bit, coil=False):
|
||||||
self._name = name
|
self._name = name
|
||||||
self.slave = int(slave) if slave else 1
|
self.slave = int(slave) if slave else 1
|
||||||
self.register = int(register)
|
self.register = int(register)
|
||||||
self.bit = int(bit)
|
self.bit = int(bit)
|
||||||
|
self._coil = coil
|
||||||
self._is_on = None
|
self._is_on = None
|
||||||
self.register_value = None
|
self.register_value = None
|
||||||
|
|
||||||
@ -92,30 +107,41 @@ class ModbusSwitch(ToggleEntity):
|
|||||||
""" Get the name of the switch. """
|
""" Get the name of the switch. """
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
attr = super().state_attributes
|
|
||||||
return attr
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
|
""" Set switch on. """
|
||||||
if self.register_value is None:
|
if self.register_value is None:
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
if self._coil:
|
||||||
|
modbus.NETWORK.write_coil(self.register, True)
|
||||||
|
else:
|
||||||
val = self.register_value | (0x0001 << self.bit)
|
val = self.register_value | (0x0001 << self.bit)
|
||||||
modbus.NETWORK.write_register(unit=self.slave,
|
modbus.NETWORK.write_register(unit=self.slave,
|
||||||
address=self.register,
|
address=self.register,
|
||||||
value=val)
|
value=val)
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
|
""" Set switch off. """
|
||||||
if self.register_value is None:
|
if self.register_value is None:
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
if self._coil:
|
||||||
|
modbus.NETWORK.write_coil(self.register, False)
|
||||||
|
else:
|
||||||
val = self.register_value & ~(0x0001 << self.bit)
|
val = self.register_value & ~(0x0001 << self.bit)
|
||||||
modbus.NETWORK.write_register(unit=self.slave,
|
modbus.NETWORK.write_register(unit=self.slave,
|
||||||
address=self.register,
|
address=self.register,
|
||||||
value=val)
|
value=val)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
result = modbus.NETWORK.read_holding_registers(unit=self.slave,
|
""" Update the state of the switch. """
|
||||||
address=self.register,
|
if self._coil:
|
||||||
|
result = modbus.NETWORK.read_coils(self.register, 1)
|
||||||
|
self.register_value = result.bits[0]
|
||||||
|
self._is_on = self.register_value
|
||||||
|
else:
|
||||||
|
result = modbus.NETWORK.read_holding_registers(
|
||||||
|
unit=self.slave, address=self.register,
|
||||||
count=1)
|
count=1)
|
||||||
val = 0
|
val = 0
|
||||||
for i, res in enumerate(result.registers):
|
for i, res in enumerate(result.registers):
|
||||||
|
@ -12,17 +12,8 @@ from homeassistant.components.switch import SwitchDevice
|
|||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return WeMo switches. """
|
""" Find and return WeMo switches. """
|
||||||
try:
|
import pywemo
|
||||||
# pylint: disable=no-name-in-module, import-error
|
import pywemo.discovery as discovery
|
||||||
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
|
|
||||||
|
|
||||||
if discovery_info is not None:
|
if discovery_info is not None:
|
||||||
device = discovery.device_from_description(discovery_info)
|
device = discovery.device_from_description(discovery_info)
|
||||||
|
@ -6,15 +6,14 @@ Support for Wink switches.
|
|||||||
"""
|
"""
|
||||||
import logging
|
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.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the Wink platform. """
|
""" Sets up the Wink platform. """
|
||||||
|
import pywink
|
||||||
|
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
token = config.get(CONF_ACCESS_TOKEN)
|
token = config.get(CONF_ACCESS_TOKEN)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
|||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import (
|
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"
|
DOMAIN = "thermostat"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -24,6 +24,8 @@ SERVICE_SET_TEMPERATURE = "set_temperature"
|
|||||||
|
|
||||||
ATTR_CURRENT_TEMPERATURE = "current_temperature"
|
ATTR_CURRENT_TEMPERATURE = "current_temperature"
|
||||||
ATTR_AWAY_MODE = "away_mode"
|
ATTR_AWAY_MODE = "away_mode"
|
||||||
|
ATTR_MAX_TEMP = "max_temp"
|
||||||
|
ATTR_MIN_TEMP = "min_temp"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -131,6 +133,9 @@ class ThermostatDevice(Entity):
|
|||||||
if device_attr is not None:
|
if device_attr is not None:
|
||||||
data.update(device_attr)
|
data.update(device_attr)
|
||||||
|
|
||||||
|
data[ATTR_MIN_TEMP] = self.min_temp
|
||||||
|
data[ATTR_MAX_TEMP] = self.max_temp
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -162,3 +167,13 @@ class ThermostatDevice(Entity):
|
|||||||
def turn_away_mode_off(self):
|
def turn_away_mode_off(self):
|
||||||
""" Turns away mode off. """
|
""" Turns away mode off. """
|
||||||
pass
|
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,6 +62,7 @@ import logging
|
|||||||
import datetime
|
import datetime
|
||||||
import homeassistant.components as core
|
import homeassistant.components as core
|
||||||
|
|
||||||
|
import homeassistant.util as util
|
||||||
from homeassistant.components.thermostat import ThermostatDevice
|
from homeassistant.components.thermostat import ThermostatDevice
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF
|
from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF
|
||||||
@ -91,16 +92,18 @@ class HeatControl(ThermostatDevice):
|
|||||||
self.target_sensor_entity_id = config.get("target_sensor")
|
self.target_sensor_entity_id = config.get("target_sensor")
|
||||||
|
|
||||||
self.time_temp = []
|
self.time_temp = []
|
||||||
|
if config.get("time_temp"):
|
||||||
for time_temp in list(config.get("time_temp").split(",")):
|
for time_temp in list(config.get("time_temp").split(",")):
|
||||||
time, temp = time_temp.split(':')
|
time, temp = time_temp.split(':')
|
||||||
time_start, time_end = time.split('-')
|
time_start, time_end = time.split('-')
|
||||||
start_time = datetime.datetime.time(datetime.datetime.
|
start_time = datetime.datetime.time(
|
||||||
strptime(time_start, '%H%M'))
|
datetime.datetime.strptime(time_start, '%H%M'))
|
||||||
end_time = datetime.datetime.time(datetime.datetime.
|
end_time = datetime.datetime.time(
|
||||||
strptime(time_end, '%H%M'))
|
datetime.datetime.strptime(time_end, '%H%M'))
|
||||||
self.time_temp.append((start_time, end_time, float(temp)))
|
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._manual_sat_temp = None
|
||||||
self._away = False
|
self._away = False
|
||||||
@ -179,7 +182,7 @@ class HeatControl(ThermostatDevice):
|
|||||||
if not self._heater_manual_changed:
|
if not self._heater_manual_changed:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.set_temperature(100)
|
self.set_temperature(self.max_temp)
|
||||||
|
|
||||||
self._heater_manual_changed = True
|
self._heater_manual_changed = True
|
||||||
|
|
||||||
@ -195,3 +198,13 @@ class HeatControl(ThermostatDevice):
|
|||||||
def turn_away_mode_off(self):
|
def turn_away_mode_off(self):
|
||||||
""" Turns away mode off. """
|
""" Turns away mode off. """
|
||||||
self._away = False
|
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
|
import logging
|
||||||
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
|
||||||
import homeassistant.external.wink.pywink as pywink
|
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
@ -19,6 +16,8 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "wink"
|
DOMAIN = "wink"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
|
||||||
|
'#pywink>=0.1']
|
||||||
|
|
||||||
DISCOVER_LIGHTS = "wink.lights"
|
DISCOVER_LIGHTS = "wink.lights"
|
||||||
DISCOVER_SWITCHES = "wink.switches"
|
DISCOVER_SWITCHES = "wink.switches"
|
||||||
@ -32,6 +31,7 @@ def setup(hass, config):
|
|||||||
if not validate_config(config, {DOMAIN: [CONF_ACCESS_TOKEN]}, logger):
|
if not validate_config(config, {DOMAIN: [CONF_ACCESS_TOKEN]}, logger):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
import pywink
|
||||||
pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN])
|
pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN])
|
||||||
|
|
||||||
# Load components for the devices in the Wink that we support
|
# Load components for the devices in the Wink that we support
|
||||||
|
1
homeassistant/external/netdisco
vendored
1
homeassistant/external/netdisco
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit b2cad7c2b959efa8eee9b5ac62d87232bf0b5176
|
|
1
homeassistant/external/pymysensors
vendored
1
homeassistant/external/pymysensors
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit cd5ef892eeec0ad027727f7e8f757e7f2927da97
|
|
1
homeassistant/external/pynetgear
vendored
1
homeassistant/external/pynetgear
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit e946ecf7926b9b2adaa1e3127a9738201a1b1fc7
|
|
1
homeassistant/external/pywemo
vendored
1
homeassistant/external/pywemo
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit ca94e41faa48c783f600a2efd550c6b7dae01b0d
|
|
408
homeassistant/external/wink/pywink.py
vendored
408
homeassistant/external/wink/pywink.py
vendored
@ -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)
|
|
@ -5,9 +5,6 @@ pytz>=2015.2
|
|||||||
|
|
||||||
# Optional, needed for specific components
|
# Optional, needed for specific components
|
||||||
|
|
||||||
# Discovery platform (discovery)
|
|
||||||
zeroconf>=0.16.0
|
|
||||||
|
|
||||||
# Sun (sun)
|
# Sun (sun)
|
||||||
astral>=0.8.1
|
astral>=0.8.1
|
||||||
|
|
||||||
@ -77,5 +74,29 @@ python-forecastio>=1.3.3
|
|||||||
# Firmata Bindings (*.arduino)
|
# Firmata Bindings (*.arduino)
|
||||||
PyMata==2.07a
|
PyMata==2.07a
|
||||||
|
|
||||||
# Mysensors serial gateway
|
#Rfxtrx sensor
|
||||||
pyserial>=2.7
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user