From a9d242a213adce24380fb773b6d0fb7701f623f0 Mon Sep 17 00:00:00 2001 From: JC Connell Date: Sat, 3 Mar 2018 11:41:33 -0500 Subject: [PATCH] Add support for Zillow Zestimate sensor (#12597) * Added Zillow Zestimate sensor. * Add zestimate.py to .coveragerc * Fix line 167 81>80 * Incorporate tinloaf changes. * Saving work * Incorporate changes requested by MartinHjelmare * Remove unnecessary import * Add a blank line between standard library and 3rd party imports --- .coveragerc | 1 + homeassistant/components/sensor/zestimate.py | 134 +++++++++++++++++++ requirements_all.txt | 1 + 3 files changed, 136 insertions(+) create mode 100644 homeassistant/components/sensor/zestimate.py diff --git a/.coveragerc b/.coveragerc index ab9d40a1896..156f546fefb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -670,6 +670,7 @@ omit = homeassistant/components/sensor/worxlandroid.py homeassistant/components/sensor/xbox_live.py homeassistant/components/sensor/zamg.py + homeassistant/components/sensor/zestimate.py homeassistant/components/shiftr.py homeassistant/components/spc.py homeassistant/components/switch/acer_projector.py diff --git a/homeassistant/components/sensor/zestimate.py b/homeassistant/components/sensor/zestimate.py new file mode 100644 index 00000000000..d8c759f1727 --- /dev/null +++ b/homeassistant/components/sensor/zestimate.py @@ -0,0 +1,134 @@ +""" +Support for zestimate data from zillow.com. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.zestimate/ +""" +from datetime import timedelta +import logging + +import requests +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (CONF_API_KEY, + CONF_NAME, ATTR_ATTRIBUTION) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = ['xmltodict==0.11.0'] + +_LOGGER = logging.getLogger(__name__) +_RESOURCE = 'http://www.zillow.com/webservice/GetZestimate.htm' + +CONF_ZPID = 'zpid' +CONF_ATTRIBUTION = "Data provided by Zillow.com" + +DEFAULT_NAME = 'Zestimate' +NAME = 'zestimate' +ZESTIMATE = '{}:{}'.format(DEFAULT_NAME, NAME) + +ICON = 'mdi:home-variant' + +ATTR_AMOUNT = 'amount' +ATTR_CHANGE = 'amount_change_30_days' +ATTR_CURRENCY = 'amount_currency' +ATTR_LAST_UPDATED = 'amount_last_updated' +ATTR_VAL_HI = 'valuation_range_high' +ATTR_VAL_LOW = 'valuation_range_low' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_ZPID): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Zestimate sensor.""" + name = config.get(CONF_NAME) + properties = config[CONF_ZPID] + params = {'zws-id': config[CONF_API_KEY]} + + sensors = [] + for zpid in properties: + params['zpid'] = zpid + sensors.append(ZestimateDataSensor(name, params)) + add_devices(sensors, True) + + +class ZestimateDataSensor(Entity): + """Implementation of a Zestimate sensor.""" + + def __init__(self, name, params): + """Initialize the sensor.""" + self._name = name + self.params = params + self.data = None + self.address = None + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + try: + return round(float(self._state), 1) + except ValueError: + return None + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attributes = {} + if self.data is not None: + attributes = self.data + attributes['address'] = self.address + attributes[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + return attributes + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ICON + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data and update the states.""" + import xmltodict + try: + response = requests.get(_RESOURCE, params=self.params, timeout=5) + data = response.content.decode('utf-8') + data_dict = xmltodict.parse(data).get(ZESTIMATE) + error_code = int(data_dict['message']['code']) + if error_code != 0: + _LOGGER.error('The API returned: %s', + data_dict['message']['text']) + return + except requests.exceptions.ConnectionError: + _LOGGER.error('Unable to retrieve data from %s', _RESOURCE) + return + data = data_dict['response'][NAME] + details = {} + details[ATTR_AMOUNT] = data['amount']['#text'] + details[ATTR_CURRENCY] = data['amount']['@currency'] + details[ATTR_LAST_UPDATED] = data['last-updated'] + details[ATTR_CHANGE] = int(data['valueChange']['#text']) + details[ATTR_VAL_HI] = int(data['valuationRange']['high']['#text']) + details[ATTR_VAL_LOW] = int(data['valuationRange']['low']['#text']) + self.address = data_dict['response']['address']['street'] + self.data = details + if self.data is not None: + self._state = self.data[ATTR_AMOUNT] + else: + self._state = None + _LOGGER.error('Unable to parase Zestimate data from response') diff --git a/requirements_all.txt b/requirements_all.txt index a5f5b8ef473..5db632bd102 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1274,6 +1274,7 @@ xknx==0.8.3 # homeassistant.components.sensor.swiss_hydrological_data # homeassistant.components.sensor.ted5000 # homeassistant.components.sensor.yr +# homeassistant.components.sensor.zestimate xmltodict==0.11.0 # homeassistant.components.sensor.yahoo_finance