diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 0ed4b2b2fd2ba..1556547b64341 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -27,7 +27,7 @@ DOMAIN = "homeassistant" # How often time_changed event should fire -TIMER_INTERVAL = 10 # seconds +TIMER_INTERVAL = 3 # seconds # How long we wait for the result of a service call SERVICE_CALL_LIMIT = 10 # seconds diff --git a/homeassistant/components/http/www_static/polymer/components/domain-icon.html b/homeassistant/components/http/www_static/polymer/components/domain-icon.html index a35bbfaacaa96..1980be11d8cbf 100644 --- a/homeassistant/components/http/www_static/polymer/components/domain-icon.html +++ b/homeassistant/components/http/www_static/polymer/components/domain-icon.html @@ -59,6 +59,9 @@ case "thermostat": return "homeassistant:thermostat"; + case "sensor": + return "visibility"; + default: return "bookmark-outline"; } diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py new file mode 100644 index 0000000000000..2ed732289b640 --- /dev/null +++ b/homeassistant/components/sensor/__init__.py @@ -0,0 +1,89 @@ +""" +homeassistant.components.sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Component to interface with various sensors that can be monitored. +""" +import logging +from datetime import timedelta + +from homeassistant.loader import get_component +import homeassistant.util as util +from homeassistant.const import ( + STATE_OPEN) +from homeassistant.helpers import ( + platform_devices_from_config) +from homeassistant.components import group, discovery, wink + +DOMAIN = 'sensor' +DEPENDENCIES = [] + +GROUP_NAME_ALL_SENSORS = 'all_sensors' +ENTITY_ID_ALL_SENSORS = group.ENTITY_ID_FORMAT.format( + GROUP_NAME_ALL_SENSORS) + +ENTITY_ID_FORMAT = DOMAIN + '.{}' + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1) + +# Maps discovered services to their platforms +DISCOVERY_PLATFORMS = { + wink.DISCOVER_SENSORS: 'wink', +} + +_LOGGER = logging.getLogger(__name__) + + +def is_on(hass, entity_id=None): + """ Returns if the sensor is open based on the statemachine. """ + entity_id = entity_id or ENTITY_ID_ALL_SENSORS + + return hass.states.is_state(entity_id, STATE_OPEN) + + +def setup(hass, config): + """ Track states and offer events for sensors. """ + logger = logging.getLogger(__name__) + + sensors = platform_devices_from_config( + config, DOMAIN, hass, ENTITY_ID_FORMAT, logger) + + # pylint: disable=unused-argument + @util.Throttle(MIN_TIME_BETWEEN_SCANS) + def update_sensor_states(now): + """ Update states of all sensors. """ + if sensors: + logger.info("Updating sensor states") + + for sensor in sensors.values(): + sensor.update_ha_state(hass, True) + + update_sensor_states(None) + + # Track all sensors in a group + sensor_group = group.Group( + hass, GROUP_NAME_ALL_SENSORS, sensors.keys(), False) + + def sensor_discovered(service, info): + """ Called when a sensor is discovered. """ + platform = get_component("{}.{}".format( + DOMAIN, DISCOVERY_PLATFORMS[service])) + + discovered = platform.devices_discovered(hass, config, info) + + for sensor in discovered: + if sensor is not None and sensor not in sensors.values(): + sensor.entity_id = util.ensure_unique_string( + ENTITY_ID_FORMAT.format(util.slugify(sensor.name)), + sensors.keys()) + + sensors[sensor.entity_id] = sensor + + sensor.update_ha_state(hass) + + sensor_group.update_tracked_entity_ids(sensors.keys()) + + discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), sensor_discovered) + + hass.track_time_change(update_sensor_states) + + return True diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py new file mode 100644 index 0000000000000..99303d3ef5616 --- /dev/null +++ b/homeassistant/components/sensor/wink.py @@ -0,0 +1,35 @@ +""" Support for Wink sensors. """ +import logging + +# pylint: disable=no-name-in-module, import-error +import homeassistant.external.wink.pywink as pywink + +from homeassistant.components.wink import WinkSensorDevice +from homeassistant.const import CONF_ACCESS_TOKEN + + +# pylint: disable=unused-argument +def get_devices(hass, config): + """ Find and return Wink sensors. """ + token = config.get(CONF_ACCESS_TOKEN) + + if token is None: + logging.getLogger(__name__).error( + "Missing wink access_token - " + "get one at https://winkbearertoken.appspot.com/") + return [] + + pywink.set_bearer_token(token) + + return get_sensors() + + +# pylint: disable=unused-argument +def devices_discovered(hass, config, info): + """ Called when a device is discovered. """ + return get_sensors() + + +def get_sensors(): + """ Returns the Wink sensors. """ + return [WinkSensorDevice(sensor) for sensor in pywink.get_sensors()] diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index d049d1ecc26ba..20b3e0b75f9b6 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -8,9 +8,10 @@ from homeassistant import bootstrap from homeassistant.loader import get_component -from homeassistant.helpers import validate_config, ToggleDevice +from homeassistant.helpers import validate_config, ToggleDevice, Device from homeassistant.const import ( EVENT_PLATFORM_DISCOVERED, CONF_ACCESS_TOKEN, + STATE_OPEN, STATE_CLOSED, ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME) DOMAIN = "wink" @@ -18,6 +19,7 @@ DISCOVER_LIGHTS = "wink.lights" DISCOVER_SWITCHES = "wink.switches" +DISCOVER_SENSORS = "wink.sensors" def setup(hass, config): @@ -32,7 +34,8 @@ def setup(hass, config): # Load components for the devices in the Wink that we support for component_name, func_exists, discovery_type in ( ('light', pywink.get_bulbs, DISCOVER_LIGHTS), - ('switch', pywink.get_switches, DISCOVER_SWITCHES)): + ('switch', pywink.get_switches, DISCOVER_SWITCHES), + ('sensor', pywink.get_sensors, DISCOVER_SENSORS)): if func_exists(): component = get_component(component_name) @@ -50,6 +53,44 @@ def setup(hass, config): return True +class WinkSensorDevice(Device): + """ represents a wink sensor within home assistant. """ + + def __init__(self, wink): + self.wink = wink + + @property + def state(self): + """ Returns the state. """ + return STATE_OPEN if self.is_open else STATE_CLOSED + + @property + def unique_id(self): + """ Returns the id of this wink switch """ + return "{}.{}".format(self.__class__, self.wink.deviceId()) + + @property + def name(self): + """ Returns the name of the sensor if any. """ + return self.wink.name() + + @property + def state_attributes(self): + """ Returns optional state attributes. """ + return { + ATTR_FRIENDLY_NAME: self.wink.name() + } + + def update(self): + """ Update state of the sensor. """ + self.wink.updateState() + + @property + def is_open(self): + """ True if door is open. """ + return self.wink.state() + + class WinkToggleDevice(ToggleDevice): """ represents a WeMo switch within home assistant. """ diff --git a/homeassistant/const.py b/homeassistant/const.py index 39089ae84cb36..31c575b96c739 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -35,6 +35,8 @@ STATE_HOME = 'home' STATE_NOT_HOME = 'not_home' STATE_UNKNOWN = "unknown" +STATE_OPEN = 'open' +STATE_CLOSED = 'closed' # #### STATE AND EVENT ATTRIBUTES #### # Contains current time for a TIME_CHANGED event diff --git a/homeassistant/external/wink/pywink.py b/homeassistant/external/wink/pywink.py index 7d44050f55c8c..72a3353a77ca9 100644 --- a/homeassistant/external/wink/pywink.py +++ b/homeassistant/external/wink/pywink.py @@ -10,6 +10,124 @@ 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 "" % (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 @@ -248,53 +366,28 @@ def __repr__(self): self.name(), self.deviceId(), self.state()) -def get_bulbs_and_switches(): +def get_devices(filter, constructor): arequestUrl = baseUrl + "/users/me/wink_devices" j = requests.get(arequestUrl, headers=headers).json() items = j.get('data') - switches = [] + devices = [] for item in items: - id = item.get('light_bulb_id') - if id != None: - switches.append(wink_bulb(item)) - id = item.get('binary_switch_id') - if id != None: - switches.append(wink_binary_switch(item)) - - return switches - - -def get_bulbs(): - arequestUrl = baseUrl + "/users/me/wink_devices" - j = requests.get(arequestUrl, headers=headers).json() - - items = j.get('data') - - switches = [] - for item in items: - id = item.get('light_bulb_id') + id = item.get(filter) if id is not None: - switches.append(wink_bulb(item)) + devices.append(constructor(item)) - return switches + return devices +def get_bulbs(): + return get_devices('light_bulb_id', wink_bulb) def get_switches(): - arequestUrl = baseUrl + "/users/me/wink_devices" - j = requests.get(arequestUrl, headers=headers).json() - - items = j.get('data') - - switches = [] - for item in items: - id = item.get('binary_switch_id') - if id is not None: - switches.append(wink_binary_switch(item)) - - return switches + return get_devices('binary_switch_id', wink_binary_switch) +def get_sensors(): + return get_devices('sensor_pod_id', wink_sensor_pod) def set_bearer_token(token): global headers