-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Zillow Zestimate sensor #12597
Changes from 3 commits
89eb0e1
9a07396
14b1006
28e2567
f1d8b5b
ed7d885
3efe493
aa05824
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
""" | ||
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/ | ||
""" | ||
import logging | ||
from datetime import timedelta | ||
|
||
import voluptuous as vol | ||
import requests | ||
|
||
from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
from homeassistant.const import (CONF_API_KEY, | ||
CONF_NAME, STATE_UNKNOWN, 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_LOCATION = 'location' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
ATTR_UPDATE = 'update' | ||
ATTR_AMOUNT = 'amount' | ||
ATTR_CHANGE = 'amount_change_30days' | ||
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): | ||
"""Setup the Zestimate sensor.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Set up the..." |
||
import xmltodict | ||
|
||
name = config.get(CONF_NAME) | ||
properties = config[CONF_ZPID] | ||
params = {'zws-id': config[CONF_API_KEY]} | ||
|
||
sensors = [] | ||
for zpid in properties: | ||
params['zpid'] = zpid | ||
try: | ||
response = requests.get(_RESOURCE, params=params, timeout=5) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you do this? This looks like you do the first update before you create the Please don't do this. Setting up the platform should be fast, i.e., should do no I/O if not absolutely necessary. The first update should happen only when your |
||
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 False | ||
except requests.exceptions.ConnectionError: | ||
_LOGGER.error('The URL is not accessible') | ||
return False | ||
|
||
data = ZestimateData(params) | ||
sensors.append(ZestimateDataSensor(name, data)) | ||
add_devices(sensors, True) | ||
|
||
|
||
class ZestimateDataSensor(Entity): | ||
"""Implementation of a Zestimate sensor.""" | ||
|
||
def __init__(self, name, data): | ||
"""Initialize the sensor.""" | ||
self.data = data | ||
self._name = name | ||
self.update() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't call update here. Since you called |
||
|
||
@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 STATE_UNKNOWN | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return |
||
|
||
@property | ||
def device_state_attributes(self): | ||
"""Return the state attributes.""" | ||
attributes = {} | ||
if self.data.measurings is not None: | ||
if ATTR_AMOUNT in self.data.measurings: | ||
data = self.data.measurings | ||
attributes[ATTR_AMOUNT] = data[ATTR_AMOUNT] | ||
attributes[ATTR_CURRENCY] = data[ATTR_CURRENCY] | ||
attributes[ATTR_LAST_UPDATED] = data[ATTR_LAST_UPDATED] | ||
attributes[ATTR_CHANGE] = data[ATTR_CHANGE] | ||
attributes[ATTR_VAL_HI] = data[ATTR_VAL_HI] | ||
attributes[ATTR_VAL_LOW] = data[ATTR_VAL_LOW] | ||
|
||
attributes[ATTR_LOCATION] = self.data.address | ||
attributes[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION | ||
return attributes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Event though |
||
|
||
@property | ||
def icon(self): | ||
"""Icon to use in the frontend, if any.""" | ||
return ICON | ||
|
||
def update(self): | ||
"""Get the latest data and update the states.""" | ||
self.data.update() | ||
if self.data.measurings is not None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the state also be set to unknown if there are no measurings? |
||
if ATTR_LAST_UPDATED not in self.data.measurings: | ||
self._state = STATE_UNKNOWN | ||
else: | ||
self._state = self.data.measurings[ATTR_AMOUNT] | ||
|
||
|
||
class ZestimateData(object): | ||
"""The Class for handling data retrieval.""" | ||
|
||
def __init__(self, params): | ||
"""Initialize the data object.""" | ||
self.params = params | ||
self.address = None | ||
self.measurings = None | ||
|
||
@Throttle(MIN_TIME_BETWEEN_UPDATES) | ||
def update(self): | ||
"""Get the latest data from hydrodata.ch.""" | ||
import xmltodict | ||
|
||
details = {} | ||
try: | ||
response = requests.get(_RESOURCE, params=self.params, timeout=5) | ||
except requests.exceptions.ConnectionError: | ||
_LOGGER.error('Unable to retrieve data from %s', _RESOURCE) | ||
|
||
try: | ||
decoded = response.content.decode('utf-8') | ||
to_dict = xmltodict.parse(decoded) | ||
response = to_dict.get(ZESTIMATE)['response'] | ||
data = response[NAME] | ||
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 = response['address']['street'] | ||
self.measurings = details | ||
except AttributeError: | ||
self.measurings = None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'd probably want to log an error (or at least a warning) here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please sort 🔤.