diff --git a/.coveragerc b/.coveragerc index edf66c6252b..3d32256e9fb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -326,6 +326,7 @@ omit = homeassistant/components/sensor/coinmarketcap.py homeassistant/components/sensor/comed_hourly_pricing.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/homeassistant/components/sensor/crimereports.py b/homeassistant/components/sensor/crimereports.py new file mode 100644 index 00000000000..8f118fc3c32 --- /dev/null +++ b/homeassistant/components/sensor/crimereports.py @@ -0,0 +1,123 @@ +""" +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 +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_INCLUDE, CONF_EXCLUDE, CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, + ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, + LENGTH_KILOMETERS, LENGTH_METERS) +from homeassistant.helpers.entity import Entity +from homeassistant.util import slugify +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__) + +SCAN_INTERVAL = timedelta(minutes=30) +DOMAIN = 'crimereports' +EVENT_INCIDENT = '{}_incident'.format(DOMAIN) +CONF_RADIUS = 'radius' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_RADIUS): vol.Coerce(float), + vol.Inclusive(CONF_LATITUDE, 'coordinates'): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, 'coordinates'): cv.longitude, + vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]) +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Crime Reports platform.""" + latitude = config.get(CONF_LATITUDE, hass.config.latitude) + longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + add_devices([CrimeReportsSensor(hass, config.get(CONF_NAME), + latitude, longitude, + config.get(CONF_RADIUS), + config.get(CONF_INCLUDE), + config.get(CONF_EXCLUDE))], True) + + +class CrimeReportsSensor(Entity): + """Crime Reports Sensor.""" + + def __init__(self, hass, name, latitude, longitude, radius, + include, exclude): + """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() + + @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) diff --git a/requirements_all.txt b/requirements_all.txt index 86305ed8722..a8f8da0ee9e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -108,6 +108,9 @@ colorlog>2.1,<3 # homeassistant.components.binary_sensor.concord232 concord232==0.14 +# homeassistant.components.sensor.crimereports +crimereports==1.0.0 + # homeassistant.components.sensor.metoffice # homeassistant.components.weather.metoffice datapoint==0.4.3