From a0c61529303900414ebf383c0afd18304b4f4b68 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Tue, 14 Feb 2017 22:43:57 -0500 Subject: [PATCH 1/3] add crimereports --- .../components/sensor/crimereports.py | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 homeassistant/components/sensor/crimereports.py diff --git a/homeassistant/components/sensor/crimereports.py b/homeassistant/components/sensor/crimereports.py new file mode 100644 index 00000000000000..643212ed7d925a --- /dev/null +++ b/homeassistant/components/sensor/crimereports.py @@ -0,0 +1,134 @@ +""" +Sensor for Crime Reports. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.crimereports/ +""" +from collections import defaultdict +import logging +from datetime import timedelta + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.zone import ( + ATTR_RADIUS, ENTITY_ID_FORMAT as ZONE_ENTITY_ID_FORMAT) +from homeassistant.const import ( + CONF_INCLUDE, CONF_EXCLUDE, CONF_ZONE, + ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_FRIENDLY_NAME, + LENGTH_KILOMETERS, LENGTH_METERS) +from homeassistant.helpers.entity import Entity +from homeassistant.util import slugify, Throttle +from homeassistant.util.distance import convert +from homeassistant.util.dt import now +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['crimereports==1.0.0'] + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['zone'] +DOMAIN = 'crimereports' +CONF_UPDATE_INTERVAL = 'update_interval' +EVENT_INCIDENT = '{}_incident'.format(DOMAIN) +NAME_FORMAT = '{} Incidents' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + CONF_ZONE: cv.string, + vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=1800)): ( + vol.All(cv.time_period, cv.positive_timedelta)), +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Crime Reports platform.""" + zone_id = ZONE_ENTITY_ID_FORMAT.format(config.get(CONF_ZONE)) + zone_state = hass.states.get(zone_id) + if not zone_state: + _LOGGER.error("could not find specified zone: %s", zone_id) + return + latitude = zone_state.attributes.get(ATTR_LATITUDE) + longitude = zone_state.attributes.get(ATTR_LONGITUDE) + radius = zone_state.attributes.get(ATTR_RADIUS) + zone_friendly_name = zone_state.attributes.get(ATTR_FRIENDLY_NAME) + name = NAME_FORMAT.format(zone_friendly_name) + add_devices([CrimeReportsSensor(hass, name, latitude, longitude, radius, + config.get(CONF_INCLUDE), + config.get(CONF_EXCLUDE), + config.get(CONF_UPDATE_INTERVAL))]) + + +class CrimeReportsSensor(Entity): + """Crime Reports Sensor.""" + + def __init__(self, hass, name, latitude, longitude, radius, + include, exclude, interval): + """Initialize the sensor.""" + import crimereports + self._hass = hass + self._name = name + self._include = include + self._exclude = exclude + radius_kilometers = convert(radius, LENGTH_METERS, LENGTH_KILOMETERS) + self._crimereports = crimereports.CrimeReports((latitude, longitude), + radius_kilometers) + self._attributes = None + self._state = None + self._previous_incidents = set() + self.update = Throttle(interval)(self._update) + self.update() + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes + + def _incident_event(self, incident): + data = { + 'type': incident.get('type'), + 'description': incident.get('friendly_description'), + 'timestamp': incident.get('timestamp'), + 'location': incident.get('location') + } + if incident.get('coordinates'): + data.update({ + ATTR_LATITUDE: incident.get('coordinates')[0], + ATTR_LONGITUDE: incident.get('coordinates')[1] + }) + self._hass.bus.fire(EVENT_INCIDENT, data) + + def _update(self): + """Update device state.""" + import crimereports + incident_counts = defaultdict(int) + incidents = self._crimereports.get_incidents(now().date(), + include=self._include, + exclude=self._exclude) + fire_events = len(self._previous_incidents) > 0 + if len(incidents) < len(self._previous_incidents): + self._previous_incidents = set() + for incident in incidents: + incident_type = slugify(incident.get('type')) + incident_counts[incident_type] += 1 + if (fire_events and incident.get('id') + not in self._previous_incidents): + self._incident_event(incident) + self._previous_incidents.add(incident.get('id')) + self._attributes = { + ATTR_ATTRIBUTION: crimereports.ATTRIBUTION + } + self._attributes.update(incident_counts) + self._state = len(incidents) From c1dac1bb28565713d89be4139ac590c85c4315f8 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Thu, 16 Feb 2017 10:14:21 -0500 Subject: [PATCH 2/3] add crimereports metadata --- .coveragerc | 1 + requirements_all.txt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.coveragerc b/.coveragerc index 9a1a31257404dd..178851d27e68ac 100644 --- a/.coveragerc +++ b/.coveragerc @@ -297,6 +297,7 @@ omit = homeassistant/components/sensor/dublin_bus_transport.py homeassistant/components/sensor/coinmarketcap.py homeassistant/components/sensor/cpuspeed.py + homeassistant/components/sensor/crimereports.py homeassistant/components/sensor/cups.py homeassistant/components/sensor/currencylayer.py homeassistant/components/sensor/darksky.py diff --git a/requirements_all.txt b/requirements_all.txt index cf59bcc75c1bfa..67e9b619f6d2be 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -90,6 +90,9 @@ colorlog>2.1,<3 # homeassistant.components.binary_sensor.concord232 concord232==0.14 +# homeassistant.components.sensor.crimereports +crimereports==1.0.0 + # homeassistant.components.light.decora # decora==0.3 From 242bc3b2dd9cc94d042de9b04a5e33758f1dad23 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Fri, 17 Feb 2017 13:32:40 -0500 Subject: [PATCH 3/3] implicit interval --- .../components/sensor/crimereports.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/sensor/crimereports.py b/homeassistant/components/sensor/crimereports.py index 643212ed7d925a..947869ee85337f 100644 --- a/homeassistant/components/sensor/crimereports.py +++ b/homeassistant/components/sensor/crimereports.py @@ -5,8 +5,8 @@ https://home-assistant.io/components/sensor.crimereports/ """ from collections import defaultdict -import logging from datetime import timedelta +import logging import voluptuous as vol @@ -18,7 +18,7 @@ ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_FRIENDLY_NAME, LENGTH_KILOMETERS, LENGTH_METERS) from homeassistant.helpers.entity import Entity -from homeassistant.util import slugify, Throttle +from homeassistant.util import slugify from homeassistant.util.distance import convert from homeassistant.util.dt import now import homeassistant.helpers.config_validation as cv @@ -27,18 +27,16 @@ _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(minutes=30) DEPENDENCIES = ['zone'] DOMAIN = 'crimereports' -CONF_UPDATE_INTERVAL = 'update_interval' EVENT_INCIDENT = '{}_incident'.format(DOMAIN) NAME_FORMAT = '{} Incidents' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ CONF_ZONE: cv.string, vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=1800)): ( - vol.All(cv.time_period, cv.positive_timedelta)), + vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]) }) @@ -57,15 +55,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): name = NAME_FORMAT.format(zone_friendly_name) add_devices([CrimeReportsSensor(hass, name, latitude, longitude, radius, config.get(CONF_INCLUDE), - config.get(CONF_EXCLUDE), - config.get(CONF_UPDATE_INTERVAL))]) + config.get(CONF_EXCLUDE))], True) class CrimeReportsSensor(Entity): """Crime Reports Sensor.""" def __init__(self, hass, name, latitude, longitude, radius, - include, exclude, interval): + include, exclude): """Initialize the sensor.""" import crimereports self._hass = hass @@ -78,8 +75,6 @@ def __init__(self, hass, name, latitude, longitude, radius, self._attributes = None self._state = None self._previous_incidents = set() - self.update = Throttle(interval)(self._update) - self.update() @property def name(self): @@ -110,7 +105,7 @@ def _incident_event(self, incident): }) self._hass.bus.fire(EVENT_INCIDENT, data) - def _update(self): + def update(self): """Update device state.""" import crimereports incident_counts = defaultdict(int)