Example #1
0
    def get_closest_network_id(cls, hass, latitude, longitude):
        """Return the id of the network closest to provided location."""
        try:
            yield from cls.NETWORKS_LIST_LOADING.acquire()
            if cls.NETWORKS_LIST is None:
                networks = yield from async_citybikes_request(
                    hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
                cls.NETWORKS_LIST = networks[ATTR_NETWORKS_LIST]
            networks_list = cls.NETWORKS_LIST
            network = networks_list[0]
            result = network[ATTR_ID]
            minimum_dist = location.distance(
                latitude, longitude,
                network[ATTR_LOCATION][ATTR_LATITUDE],
                network[ATTR_LOCATION][ATTR_LONGITUDE])
            for network in networks_list[1:]:
                network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE]
                network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE]
                dist = location.distance(latitude, longitude,
                                         network_latitude, network_longitude)
                if dist < minimum_dist:
                    minimum_dist = dist
                    result = network[ATTR_ID]

            return result
        except CityBikesRequestError:
            raise PlatformNotReady
        finally:
            cls.NETWORKS_LIST_LOADING.release()
Example #2
0
 def test_get_distance(self):
     """Test getting the distance."""
     meters = location_util.distance(COORDINATES_PARIS[0],
                                     COORDINATES_PARIS[1],
                                     COORDINATES_NEW_YORK[0],
                                     COORDINATES_NEW_YORK[1])
     self.assertAlmostEqual(meters / 1000, DISTANCE_KM, places=2)
Example #3
0
def in_zone(zone, latitude, longitude, radius=0):
    """ Test if given latitude, longitude is in given zone. """
    zone_dist = distance(
        latitude, longitude,
        zone.attributes[ATTR_LATITUDE], zone.attributes[ATTR_LONGITUDE])

    return zone_dist - radius < zone.attributes[ATTR_RADIUS]
Example #4
0
    def test_get_distance(self):
        """Test getting the distance."""
        meters = location_util.distance(
            COORDINATES_PARIS[0], COORDINATES_PARIS[1],
            COORDINATES_NEW_YORK[0], COORDINATES_NEW_YORK[1])

        assert meters/1000 - DISTANCE_KM < 0.01
Example #5
0
    def determine_interval(self, devicename, latitude, longitude, battery):
        """Calculate new interval."""
        distancefromhome = None
        zone_state = self.hass.states.get('zone.home')
        zone_state_lat = zone_state.attributes['latitude']
        zone_state_long = zone_state.attributes['longitude']
        distancefromhome = distance(
            latitude, longitude, zone_state_lat, zone_state_long)
        distancefromhome = round(distancefromhome / 1000, 1)

        currentzone = active_zone(self.hass, latitude, longitude)

        if ((currentzone is not None and
             currentzone == self._overridestates.get(devicename)) or
                (currentzone is None and
                 self._overridestates.get(devicename) == 'away')):
            return

        self._overridestates[devicename] = None

        if currentzone is not None:
            self._intervals[devicename] = 30
            return

        if distancefromhome is None:
            return
        if distancefromhome > 25:
            self._intervals[devicename] = round(distancefromhome / 2, 0)
        elif distancefromhome > 10:
            self._intervals[devicename] = 5
        else:
            self._intervals[devicename] = 1
        if battery is not None and battery <= 33 and distancefromhome > 3:
            self._intervals[devicename] = self._intervals[devicename] * 2
Example #6
0
    def test_get_distance_to_same_place(self):
        """Test getting the distance."""
        meters = location_util.distance(
            COORDINATES_PARIS[0], COORDINATES_PARIS[1],
            COORDINATES_PARIS[0], COORDINATES_PARIS[1])

        assert meters == 0
Example #7
0
def async_active_zone(hass, latitude, longitude, radius=0):
    """Find the active zone for given latitude, longitude.

    This method must be run in the event loop.
    """
    # Sort entity IDs so that we are deterministic if equal distance to 2 zones
    zones = (hass.states.get(entity_id) for entity_id
             in sorted(hass.states.async_entity_ids(DOMAIN)))

    min_dist = None
    closest = None

    for zone in zones:
        if zone.attributes.get(ATTR_PASSIVE):
            continue

        zone_dist = distance(
            latitude, longitude,
            zone.attributes[ATTR_LATITUDE], zone.attributes[ATTR_LONGITUDE])

        within_zone = zone_dist - radius < zone.attributes[ATTR_RADIUS]
        closer_zone = closest is None or zone_dist < min_dist
        smaller_zone = (zone_dist == min_dist and
                        zone.attributes[ATTR_RADIUS] <
                        closest.attributes[ATTR_RADIUS])

        if within_zone and (closer_zone or smaller_zone):
            min_dist = zone_dist
            closest = zone

    return closest
Example #8
0
    def distance(self: object, lat: float, lon: float) -> float:
        """Calculate distance from Home Assistant.

        Async friendly.
        """
        return self.units.length(
            location.distance(self.latitude, self.longitude, lat, lon), 'm')
Example #9
0
 def update(self):
     """Update device state."""
     currently_tracked = set()
     states = self._session.get(OPENSKY_API_URL).json().get(ATTR_STATES)
     for state in states:
         data = dict(zip(OPENSKY_API_FIELDS, state))
         missing_location = (
             data.get(ATTR_LONGITUDE) is None or
             data.get(ATTR_LATITUDE) is None)
         if missing_location:
             continue
         if data.get(ATTR_ON_GROUND):
             continue
         distance = util_location.distance(
             self._latitude, self._longitude,
             data.get(ATTR_LATITUDE), data.get(ATTR_LONGITUDE))
         if distance is None or distance > self._radius:
             continue
         callsign = data[ATTR_CALLSIGN].strip()
         if callsign == '':
             continue
         currently_tracked.add(callsign)
     if self._previously_tracked is not None:
         entries = currently_tracked - self._previously_tracked
         exits = self._previously_tracked - currently_tracked
         self._handle_boundary(entries, EVENT_OPENSKY_ENTRY)
         self._handle_boundary(exits, EVENT_OPENSKY_EXIT)
     self._state = len(currently_tracked)
     self._previously_tracked = currently_tracked
Example #10
0
    def distance(self, *args):
        """Calculate distance.

        Will calculate distance from home to a point or between points.
        Points can be passed in using state objects or lat/lng coordinates.
        """
        locations = []

        to_process = list(args)

        while to_process:
            value = to_process.pop(0)
            point_state = self._resolve_state(value)

            if point_state is None:
                # We expect this and next value to be lat&lng
                if not to_process:
                    _LOGGER.warning(
                        "Distance:Expected latitude and longitude, got %s",
                        value)
                    return None

                value_2 = to_process.pop(0)
                latitude = convert(value, float)
                longitude = convert(value_2, float)

                if latitude is None or longitude is None:
                    _LOGGER.warning("Distance:Unable to process latitude and "
                                    "longitude: %s, %s", value, value_2)
                    return None

            else:
                if not loc_helper.has_location(point_state):
                    _LOGGER.warning(
                        "distance:State does not contain valid location: %s",
                        point_state)
                    return None

                latitude = point_state.attributes.get(ATTR_LATITUDE)
                longitude = point_state.attributes.get(ATTR_LONGITUDE)

                if latitude is None or longitude is None:
                    _LOGGER.warning(
                        "Distance:State does not contains a location: %s",
                        value)
                    return None

            locations.append((latitude, longitude))

        if len(locations) == 1:
            return self._hass.config.distance(*locations[0])

        return self._hass.config.units.length(
            loc_util.distance(*locations[0] + locations[1]), 'm')
Example #11
0
    def determine_interval(self, devicename, latitude, longitude, battery):
        """Calculate new interval."""
        currentzone = active_zone(self.hass, latitude, longitude)

        if ((currentzone is not None and
             currentzone == self._overridestates.get(devicename)) or
                (currentzone is None and
                 self._overridestates.get(devicename) == 'away')):
            return

        zones = (self.hass.states.get(entity_id) for entity_id
                 in sorted(self.hass.states.entity_ids('zone')))

        distances = []
        for zone_state in zones:
            zone_state_lat = zone_state.attributes['latitude']
            zone_state_long = zone_state.attributes['longitude']
            zone_distance = distance(
                latitude, longitude, zone_state_lat, zone_state_long)
            distances.append(round(zone_distance / 1000, 1))

        if distances:
            mindistance = min(distances)
        else:
            mindistance = None

        self._overridestates[devicename] = None

        if currentzone is not None:
            self._intervals[devicename] = 30
            return

        if mindistance is None:
            return

        # Calculate out how long it would take for the device to drive to the
        # nearest zone at 120 km/h:
        interval = round(mindistance / 2, 0)

        # Never poll more than once per minute
        interval = max(interval, 1)

        if interval > 180:
            # Three hour drive?  This is far enough that they might be flying
            # home - check every half hour
            interval = 30

        if battery is not None and battery <= 33 and mindistance > 3:
            # Low battery - let's check half as often
            interval = interval * 2

        self._intervals[devicename] = interval
Example #12
0
def closest(latitude, longitude, states):
    """Return closest state to point."""
    with_location = [state for state in states if has_location(state)]

    if not with_location:
        return None

    return min(
        with_location,
        key=lambda state: loc_util.distance(
            latitude, longitude, state.attributes.get(ATTR_LATITUDE),
            state.attributes.get(ATTR_LONGITUDE))
    )
Example #13
0
async def async_setup_platform(hass, config, async_add_entities,
                               discovery_info=None):
    """Set up the CityBikes platform."""
    if PLATFORM not in hass.data:
        hass.data[PLATFORM] = {MONITORED_NETWORKS: {}}

    latitude = config.get(CONF_LATITUDE, hass.config.latitude)
    longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
    network_id = config.get(CONF_NETWORK)
    stations_list = set(config.get(CONF_STATIONS_LIST, []))
    radius = config.get(CONF_RADIUS, 0)
    name = config[CONF_NAME]
    if not hass.config.units.is_metric:
        radius = distance.convert(radius, LENGTH_FEET, LENGTH_METERS)

    # Create a single instance of CityBikesNetworks.
    networks = hass.data.setdefault(
        CITYBIKES_NETWORKS, CityBikesNetworks(hass))

    if not network_id:
        network_id = await networks.get_closest_network_id(latitude, longitude)

    if network_id not in hass.data[PLATFORM][MONITORED_NETWORKS]:
        network = CityBikesNetwork(hass, network_id)
        hass.data[PLATFORM][MONITORED_NETWORKS][network_id] = network
        hass.async_create_task(network.async_refresh())
        async_track_time_interval(hass, network.async_refresh, SCAN_INTERVAL)
    else:
        network = hass.data[PLATFORM][MONITORED_NETWORKS][network_id]

    await network.ready.wait()

    devices = []
    for station in network.stations:
        dist = location.distance(
            latitude, longitude, station[ATTR_LATITUDE],
            station[ATTR_LONGITUDE])
        station_id = station[ATTR_ID]
        station_uid = str(station.get(ATTR_EXTRA, {}).get(ATTR_UID, ''))

        if radius > dist or stations_list.intersection(
                (station_id, station_uid)):
            if name:
                uid = "_".join([network.network_id, name, station_id])
            else:
                uid = "_".join([network.network_id, station_id])
            entity_id = async_generate_entity_id(
                ENTITY_ID_FORMAT, uid, hass=hass)
            devices.append(CityBikesStation(network, station_id, entity_id))

    async_add_entities(devices, True)
Example #14
0
def closest(latitude: float, longitude: float,
            states: Sequence[State]) -> Optional[State]:
    """Return closest state to point.

    Async friendly.
    """
    with_location = [state for state in states if has_location(state)]

    if not with_location:
        return None

    return min(
        with_location,
        key=lambda state: loc_util.distance(
            state.attributes.get(ATTR_LATITUDE),
            state.attributes.get(ATTR_LONGITUDE),
            latitude, longitude)
    )
Example #15
0
 def devicechanged(self, entity, old_state, new_state):
     if entity is None:
         return
         
     self._distance = None
     if 'latitude' in new_state.attributes:
         device_state_lat = new_state.attributes['latitude']
         device_state_long = new_state.attributes['longitude']
         zone_state = self.hass.states.get('zone.home')
         zone_state_lat = zone_state.attributes['latitude']
         zone_state_long = zone_state.attributes['longitude']
         self._distance = distance(device_state_lat, device_state_long,
                                   zone_state_lat, zone_state_long)
         self._distance = round(self._distance / 1000, 1)
     if 'battery' in new_state.attributes:
         self._battery = new_state.attributes['battery']
         
     if new_state.state == self._overridestate:
         self.update_ha_state()
         return
         
     self._overridestate = None
     
     if new_state.state != 'not_home':
         self._interval = 30
         self.update_ha_state()
     else:
         if self._distance is None:
             self.update_ha_state()
             return
         if self._distance > 100:
             self._interval = round((self._distance / 60), 0)
         elif self._distance > 50:
             self._interval = 30
         elif self._distance > 25:
             self._interval = 15
         elif self._distance > 10:
             self._interval = 5
         else:
             self._interval = 1
         if self._battery is not None:
             if self._battery <= 33 and self._distance > 3:
                 self._interval = self._interval * 2
         self.update_ha_state()
Example #16
0
def async_setup_platform(hass, config, async_add_devices,
                         discovery_info=None):
    """Set up the CityBikes platform."""
    if DOMAIN not in hass.data:
        hass.data[DOMAIN] = {MONITORED_NETWORKS: {}}

    latitude = config.get(CONF_LATITUDE, hass.config.latitude)
    longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
    network_id = config.get(CONF_NETWORK)
    stations_list = set(config.get(CONF_STATIONS_LIST, []))
    radius = config.get(CONF_RADIUS, 0)
    name = config.get(CONF_NAME)
    if not hass.config.units.is_metric:
        radius = distance.convert(radius, LENGTH_FEET, LENGTH_METERS)

    if not network_id:
        network_id = yield from CityBikesNetwork.get_closest_network_id(
            hass, latitude, longitude)

    if network_id not in hass.data[DOMAIN][MONITORED_NETWORKS]:
        network = CityBikesNetwork(hass, network_id)
        hass.data[DOMAIN][MONITORED_NETWORKS][network_id] = network
        hass.async_add_job(network.async_refresh)
        async_track_time_interval(hass, network.async_refresh,
                                  SCAN_INTERVAL)
    else:
        network = hass.data[DOMAIN][MONITORED_NETWORKS][network_id]

    yield from network.ready.wait()

    devices = []
    for station in network.stations:
        dist = location.distance(latitude, longitude,
                                 station[ATTR_LATITUDE],
                                 station[ATTR_LONGITUDE])
        station_id = station[ATTR_ID]
        station_uid = str(station.get(ATTR_EXTRA, {}).get(ATTR_UID, ''))

        if radius > dist or stations_list.intersection((station_id,
                                                        station_uid)):
            devices.append(CityBikesStation(network, station_id, name))

    async_add_devices(devices, True)
Example #17
0
def closest(
    latitude: float, longitude: float, states: Sequence[State]
) -> Optional[State]:
    """Return closest state to point.

    Async friendly.
    """
    with_location = [state for state in states if has_location(state)]

    if not with_location:
        return None

    return min(
        with_location,
        key=lambda state: loc_util.distance(
            state.attributes.get(ATTR_LATITUDE),
            state.attributes.get(ATTR_LONGITUDE),
            latitude,
            longitude,
        ),
    )
Example #18
0
def in_zone(zone: State,
            latitude: float,
            longitude: float,
            radius: float = 0) -> bool:
    """Test if given latitude, longitude is in given zone.

    Async friendly.
    """
    if zone.state == STATE_UNAVAILABLE:
        return False

    zone_dist = distance(
        latitude,
        longitude,
        zone.attributes[ATTR_LATITUDE],
        zone.attributes[ATTR_LONGITUDE],
    )

    if zone_dist is None or zone.attributes[ATTR_RADIUS] is None:
        return False
    return zone_dist - radius < cast(float, zone.attributes[ATTR_RADIUS])
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Set up the CityBikes platform."""
    if PLATFORM not in hass.data:
        hass.data[PLATFORM] = {MONITORED_NETWORKS: {}}

    latitude = config.get(CONF_LATITUDE, hass.config.latitude)
    longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
    network_id = config.get(CONF_NETWORK)
    stations_list = set(config.get(CONF_STATIONS_LIST, []))
    radius = config.get(CONF_RADIUS, 0)
    name = config.get(CONF_NAME)
    if not hass.config.units.is_metric:
        radius = distance.convert(radius, LENGTH_FEET, LENGTH_METERS)

    if not network_id:
        network_id = yield from CityBikesNetwork.get_closest_network_id(
            hass, latitude, longitude)

    if network_id not in hass.data[PLATFORM][MONITORED_NETWORKS]:
        network = CityBikesNetwork(hass, network_id)
        hass.data[PLATFORM][MONITORED_NETWORKS][network_id] = network
        hass.async_add_job(network.async_refresh)
        async_track_time_interval(hass, network.async_refresh, SCAN_INTERVAL)
    else:
        network = hass.data[PLATFORM][MONITORED_NETWORKS][network_id]

    yield from network.ready.wait()

    devices = []
    for station in network.stations:
        dist = location.distance(latitude, longitude, station[ATTR_LATITUDE],
                                 station[ATTR_LONGITUDE])
        station_id = station[ATTR_ID]
        station_uid = str(station.get(ATTR_EXTRA, {}).get(ATTR_UID, ''))

        if radius > dist or stations_list.intersection(
            (station_id, station_uid)):
            devices.append(CityBikesStation(hass, network, station_id, name))

    async_add_devices(devices, True)
Example #20
0
def async_active_zone(hass: HomeAssistant,
                      latitude: float,
                      longitude: float,
                      radius: int = 0) -> Optional[State]:
    """Find the active zone for given latitude, longitude.

    This method must be run in the event loop.
    """
    # Sort entity IDs so that we are deterministic if equal distance to 2 zones
    zones = (cast(State, hass.states.get(entity_id))
             for entity_id in sorted(hass.states.async_entity_ids(DOMAIN)))

    min_dist = None
    closest = None

    for zone in zones:
        if zone.attributes.get(ATTR_PASSIVE):
            continue

        zone_dist = distance(
            latitude,
            longitude,
            zone.attributes[ATTR_LATITUDE],
            zone.attributes[ATTR_LONGITUDE],
        )

        if zone_dist is None:
            continue

        within_zone = zone_dist - radius < zone.attributes[ATTR_RADIUS]
        closer_zone = closest is None or zone_dist < min_dist  # type: ignore
        smaller_zone = (zone_dist == min_dist
                        and zone.attributes[ATTR_RADIUS] < cast(
                            State, closest).attributes[ATTR_RADIUS])

        if within_zone and (closer_zone or smaller_zone):
            min_dist = zone_dist
            closest = zone

    return closest
Example #21
0
 def update(self):
     """Update device state."""
     currently_tracked = set()
     flight_metadata = {}
     states = self._session.get(OPENSKY_API_URL).json().get(ATTR_STATES)
     for state in states:
         flight = dict(zip(OPENSKY_API_FIELDS, state))
         callsign = flight[ATTR_CALLSIGN].strip()
         if callsign != "":
             flight_metadata[callsign] = flight
         else:
             continue
         missing_location = (flight.get(ATTR_LONGITUDE) is None
                             or flight.get(ATTR_LATITUDE) is None)
         if missing_location:
             continue
         if flight.get(ATTR_ON_GROUND):
             continue
         distance = util_location.distance(
             self._latitude,
             self._longitude,
             flight.get(ATTR_LATITUDE),
             flight.get(ATTR_LONGITUDE),
         )
         if distance is None or distance > self._radius:
             continue
         altitude = flight.get(ATTR_ALTITUDE)
         if altitude > self._altitude and self._altitude != 0:
             continue
         currently_tracked.add(callsign)
     if self._previously_tracked is not None:
         entries = currently_tracked - self._previously_tracked
         exits = self._previously_tracked - currently_tracked
         self._handle_boundary(entries, EVENT_OPENSKY_ENTRY,
                               flight_metadata)
         self._handle_boundary(exits, EVENT_OPENSKY_EXIT, flight_metadata)
     self._state = len(currently_tracked)
     self._previously_tracked = currently_tracked
Example #22
0
    async def get_closest_network_id(self, latitude, longitude):
        """Return the id of the network closest to provided location."""
        try:
            await self.networks_loading.acquire()
            if self.networks is None:
                networks = await async_citybikes_request(
                    self.hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
                self.networks = networks[ATTR_NETWORKS_LIST]
            result = None
            minimum_dist = None
            for network in self.networks:
                network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE]
                network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE]
                dist = location.distance(
                    latitude, longitude, network_latitude, network_longitude)
                if minimum_dist is None or dist < minimum_dist:
                    minimum_dist = dist
                    result = network[ATTR_ID]

            return result
        except CityBikesRequestError:
            raise PlatformNotReady
        finally:
            self.networks_loading.release()
Example #23
0
 def update(self):
     """Update device state."""
     currently_tracked = set()
     flight_metadata = {}
     states = self._session.get(OPENSKY_API_URL).json().get(ATTR_STATES)
     for state in states:
         flight = dict(zip(OPENSKY_API_FIELDS, state))
         callsign = flight[ATTR_CALLSIGN].strip()
         if callsign != '':
             flight_metadata[callsign] = flight
         else:
             continue
         missing_location = (
             flight.get(ATTR_LONGITUDE) is None or
             flight.get(ATTR_LATITUDE) is None)
         if missing_location:
             continue
         if flight.get(ATTR_ON_GROUND):
             continue
         distance = util_location.distance(
             self._latitude, self._longitude,
             flight.get(ATTR_LATITUDE), flight.get(ATTR_LONGITUDE))
         if distance is None or distance > self._radius:
             continue
         altitude = flight.get(ATTR_ALTITUDE)
         if altitude > self._altitude and self._altitude != 0:
             continue
         currently_tracked.add(callsign)
     if self._previously_tracked is not None:
         entries = currently_tracked - self._previously_tracked
         exits = self._previously_tracked - currently_tracked
         self._handle_boundary(entries, EVENT_OPENSKY_ENTRY,
                               flight_metadata)
         self._handle_boundary(exits, EVENT_OPENSKY_EXIT, flight_metadata)
     self._state = len(currently_tracked)
     self._previously_tracked = currently_tracked
Example #24
0
    def get_closest_network_id(cls, hass, latitude, longitude):
        """Return the id of the network closest to provided location."""
        try:
            yield from cls.NETWORKS_LIST_LOADING.acquire()
            if cls.NETWORKS_LIST is None:
                networks = yield from async_citybikes_request(
                    hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
                cls.NETWORKS_LIST = networks[ATTR_NETWORKS_LIST]
            result = None
            minimum_dist = None
            for network in cls.NETWORKS_LIST:
                network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE]
                network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE]
                dist = location.distance(latitude, longitude, network_latitude,
                                         network_longitude)
                if minimum_dist is None or dist < minimum_dist:
                    minimum_dist = dist
                    result = network[ATTR_ID]

            return result
        except CityBikesRequestError:
            raise PlatformNotReady
        finally:
            cls.NETWORKS_LIST_LOADING.release()
Example #25
0
    async def get_closest_network_id(self, latitude, longitude):
        """Return the id of the network closest to provided location."""
        try:
            await self.networks_loading.acquire()
            if self.networks is None:
                networks = await async_citybikes_request(
                    self.hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
                self.networks = networks[ATTR_NETWORKS_LIST]
            result = None
            minimum_dist = None
            for network in self.networks:
                network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE]
                network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE]
                dist = location.distance(
                    latitude, longitude, network_latitude, network_longitude)
                if minimum_dist is None or dist < minimum_dist:
                    minimum_dist = dist
                    result = network[ATTR_ID]

            return result
        except CityBikesRequestError:
            raise PlatformNotReady
        finally:
            self.networks_loading.release()
Example #26
0
    def do_update(self, reason):
        """Get the latest data and updates the states."""

        previous_state = self.state[:-14]
        distance_traveled = 0
        devicetracker_zone = None

        _LOGGER.info("(" + self._name + ") Calling update due to " + reason)
        _LOGGER.info("(" + self._name + ") Check if update req'd : " +
                     self._devicetracker_id)
        _LOGGER.debug("(" + self._name + ") Previous State        : " +
                      previous_state)

        if hasattr(self, '_devicetracker_id'):
            now = datetime.now()
            old_latitude = str(self._latitude)
            old_longitude = str(self._longitude)
            new_latitude = str(
                self._hass.states.get(
                    self._devicetracker_id).attributes.get('latitude'))
            new_longitude = str(
                self._hass.states.get(
                    self._devicetracker_id).attributes.get('longitude'))
            home_latitude = str(self._home_latitude)
            home_longitude = str(self._home_longitude)
            last_distance_m = self._distance_m
            last_updated = self._mtime
            current_location = new_latitude + "," + new_longitude
            previous_location = old_latitude + "," + old_longitude
            home_location = home_latitude + "," + home_longitude

            #maplink_google ='https://www.google.com/maps/@' + current_location+',' + self._map_zoom + 'z'
            maplink_apple = 'https://maps.apple.com/maps/?ll=' + current_location + '&z=' + self._map_zoom
            #maplink_google = 'https://www.google.com/maps/dir/?api=1&origin=' + current_location + '&destination=' + home_location + '&travelmode=driving&layer=traffic'
            maplink_google = 'https://www.google.com/maps/search/?api=1&basemap=roadmap&layer=traffic&query=' + current_location
            if (new_latitude != 'None' and new_longitude != 'None'
                    and home_latitude != 'None' and home_longitude != 'None'):
                distance_m = distance(float(new_latitude),
                                      float(new_longitude),
                                      float(home_latitude),
                                      float(home_longitude))
                distance_km = round(distance_m / 1000, 2)
                distance_from_home = str(distance_km) + ' km'

                deviation = self.haversine(float(old_latitude),
                                           float(old_longitude),
                                           float(new_latitude),
                                           float(new_longitude))
                if deviation <= 0.2:  # in kilometers
                    direction = "stationary"
                elif last_distance_m > distance_m:
                    direction = "towards home"
                elif last_distance_m < distance_m:
                    direction = "away from home"
                else:
                    direction = "stationary"

                _LOGGER.debug("(" + self._name + ") Previous Location: " +
                              previous_location)
                _LOGGER.debug("(" + self._name + ") Current Location : " +
                              current_location)
                _LOGGER.debug("(" + self._name + ") Home Location    : " +
                              home_location)
                _LOGGER.info("(" + self._name + ") Distance from home : (" +
                             (self._home_zone).split(".")[1] + "): " +
                             distance_from_home)
                _LOGGER.info("(" + self._name + ") Travel Direction   :(" +
                             direction + ")")
                """Update if location has changed."""

                devicetracker_zone = self.hass.states.get(
                    self._devicetracker_id).state
                distance_traveled = distance(float(new_latitude),
                                             float(new_longitude),
                                             float(old_latitude),
                                             float(old_longitude))

                _LOGGER.info("(" + self._name +
                             ") DeviceTracker Zone (before update): " +
                             devicetracker_zone)
                _LOGGER.info("(" + self._name +
                             ") Meters traveled since last update: " +
                             str(round(distance_traveled)))

        proceed_with_update = True

        if current_location == previous_location:
            _LOGGER.debug(
                "(" + self._name +
                ") Skipping update because co-ordinates are identical")
            proceed_with_update = False
        elif int(distance_traveled) > 0 and self._updateskipped > 3:
            proceed_with_update = True
            _LOGGER.debug(
                "(" + self._name +
                ") Allowing update after 3 skips even with distance traveled < 10m"
            )
        elif int(distance_traveled) < 10:
            self._updateskipped = self._updateskipped + 1
            _LOGGER.debug("(" + self._name +
                          ") Skipping update because location changed " +
                          str(distance_traveled) + " < 10m  (" +
                          str(self._updateskipped) + ")")
            proceed_with_update = False

        if previous_state == 'Initializing...':
            _LOGGER.debug("(" + self._name +
                          ") Peforming Initial Update for user at home...")
            proceed_with_update = True

        if proceed_with_update and devicetracker_zone:
            _LOGGER.debug("(" + self._name + ") Proceeding with update for " +
                          self._devicetracker_id)
            self._devicetracker_zone = devicetracker_zone
            _LOGGER.info("(" + self._name + ") DeviceTracker Zone (current) " +
                         self._devicetracker_zone + " Skipped Updates: " +
                         str(self._updateskipped))

            self._reset_attributes()

            self._latitude = new_latitude
            self._longitude = new_longitude
            self._latitude_old = old_latitude
            self._longitude_old = old_longitude
            self._location_current = current_location
            self._location_previous = previous_location
            self._devicetracker_zone = devicetracker_zone
            self._distance_km = distance_from_home
            self._distance_m = distance_m
            self._direction = direction

            if self._map_provider == 'google':
                _LOGGER.debug("(" + self._name +
                              ") Google Map Link requested for: [" +
                              self._map_provider + "]")
                self._map_link = maplink_google
            else:
                _LOGGER.debug("(" + self._name +
                              ") Apple Map Link requested for: [" +
                              self._map_provider + "]")
                self._map_link = maplink_apple

            _LOGGER.debug("(" + self._name + ") Map Link generated: " +
                          self._map_link)

            if self._api_key == 'no key':
                osm_url = "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=" + self._latitude + "&lon=" + self._longitude + "&addressdetails=1&namedetails=1&zoom=18&limit=1"
            else:
                osm_url = "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=" + self._latitude + "&lon=" + self._longitude + "&addressdetails=1&namedetails=1&zoom=18&limit=1&email=" + self._api_key

            osm_decoded = {}
            _LOGGER.info("(" + self._name +
                         ") OpenStreetMap request sent with lat=" +
                         self._latitude + " and lon=" + self._longitude)
            _LOGGER.debug("(" + self._name + ") url - " + osm_url)
            osm_response = get(osm_url)
            osm_json_input = osm_response.text
            _LOGGER.debug("(" + self._name + ") response - " + osm_json_input)
            osm_decoded = json.loads(osm_json_input)
            decoded = osm_decoded

            place_options = self._options.lower()
            place_type = '-'
            place_name = '-'
            place_category = '-'
            place_neighbourhood = '-'
            street_number = ''
            street = 'Unnamed Road'
            city = '-'
            postal_town = '-'
            region = '-'
            county = '-'
            country = '-'
            postal_code = ''
            formatted_address = ''
            target_option = ''

            if "place" in self._options:
                place_type = osm_decoded["type"]
                if place_type == "yes":
                    place_type = osm_decoded["addresstype"]
                if place_type in osm_decoded["address"]:
                    place_name = osm_decoded["address"][place_type]
                if "category" in osm_decoded:
                    place_category = osm_decoded["category"]
                    if place_category in osm_decoded["address"]:
                        place_name = osm_decoded["address"][place_category]
                if "name" in osm_decoded["namedetails"]:
                    place_name = osm_decoded["namedetails"]["name"]
                if "neighbourhood" in osm_decoded["address"]:
                    place_neighbourhood = osm_decoded["address"][
                        "neighbourhood"]
                if self._devicetracker_zone == 'not_home' and place_name != 'house':
                    new_state = place_name

            if "house_number" in osm_decoded["address"]:
                street_number = osm_decoded["address"]["house_number"]
            if "road" in osm_decoded["address"]:
                street = osm_decoded["address"]["road"]
            if "city" in osm_decoded["address"]:
                city = osm_decoded["address"]["city"]
            if "town" in osm_decoded["address"]:
                city = osm_decoded["address"]["town"]
            if "village" in osm_decoded["address"]:
                city = osm_decoded["address"]["village"]
            if "city_district" in osm_decoded["address"]:
                postal_town = osm_decoded["address"]["city_district"]
            if "suburb" in osm_decoded["address"]:
                postal_town = osm_decoded["address"]["suburb"]
            if "state" in osm_decoded["address"]:
                region = osm_decoded["address"]["state"]
            if "county" in osm_decoded["address"]:
                county = osm_decoded["address"]["county"]
            if "country" in osm_decoded["address"]:
                country = osm_decoded["address"]["country"]
            if "postcode" in osm_decoded["address"]:
                postal_code = osm_decoded["address"]["postcode"]
            if "display_name" in osm_decoded:
                formatted_address = osm_decoded["display_name"]

            self._place_type = place_type
            self._place_category = place_category
            self._place_neighbourhood = place_neighbourhood
            self._place_name = place_name

            self._street_number = street_number
            self._street = street
            self._city = city
            self._postal_town = postal_town
            self._region = region
            self._county = county
            self._country = country
            self._postal_code = postal_code
            self._formatted_address = formatted_address
            self._mtime = str(datetime.now())

            if 'error_message' in osm_decoded:
                new_state = osm_decoded['error_message']
                _LOGGER.info("(" + self._name +
                             ") An error occurred contacting the web service")
            elif self._devicetracker_zone == "not_home":
                if city == '-':
                    city = postal_town
                    if city == '-':
                        city = county

                # Options:  "zone, place, street_number, street, city, county, state, postal_code, country, formatted_address"

                _LOGGER.debug("(" + self._name +
                              ") Building State from Display Options: " +
                              self._options)

                display_options = []
                options_array = self._options.split(',')
                for option in options_array:
                    display_options.append(option.strip())

                user_display = []

                if "zone" in display_options and (
                        "do_not_show_not_home" not in display_options
                        and self._devicetracker_zone != "not_home"):
                    zone = self._devicetracker_zone
                    user_display.append(zone)
                if "place" in display_options:
                    if place_name != "-":
                        user_display.append(place_name)
                    if place_category.lower() != "place":
                        user_display.append(place_category)
                    if place_type.lower() != "yes":
                        user_display.append(place_type)
                    user_display.append(place_neighbourhood)
                    user_display.append(street_number)
                    user_display.append(street)
                else:
                    if "street_number" in display_options:
                        user_display.append(street_number)
                    if "street" in display_options:
                        user_display.append(street)
                if "city" in display_options:
                    user_display.append(city)
                if "county" in display_options:
                    user_display.append(county)
                if "state" in display_options:
                    user_display.append(region)
                elif "region" in display_options:
                    user_display.append(region)
                if "postal_code" in display_options:
                    user_display.append(postal_code)
                if "country" in display_options:
                    user_display.append(country)
                if "formatted_address" in display_options:
                    user_display.append(formatted_address)

                if "do_not_reorder" in display_options:
                    user_display = []
                    display_options.remove("do_not_reorder")
                    for option in display_options:
                        if option == "state":
                            target_option = "region"
                        if option == "place_neighborhood":
                            target_option = "place_neighbourhood"
                        if option in locals():
                            user_display.append(target_option)

                if not user_display:
                    user_display = self._devicetracker_zone
                    user_display.append(street)
                    user_display.append(city)

                new_state = ', '.join(item for item in user_display)
                _LOGGER.debug(
                    "(" + self._name +
                    ") New State built from Display Options will be: " +
                    new_state)
            else:
                new_state = devicetracker_zone
                _LOGGER.debug("(" + self._name +
                              ") New State from DeviceTracker set to: " +
                              new_state)

            current_time = "%02d:%02d" % (now.hour, now.minute)

            if previous_state != new_state:
                _LOGGER.info("(" + self._name +
                             ") New state built using options: " +
                             self._options)
                _LOGGER.debug("(" + self._name + ") Building EventData for (" +
                              new_state + ")")
                self._state = new_state + " (since " + current_time + ")"
                event_data = {}
                event_data['entity'] = self._name
                event_data['place_name'] = place_name
                event_data['from_state'] = previous_state
                event_data['to_state'] = new_state
                event_data['distance_from_home'] = distance_from_home
                event_data['direction'] = direction
                event_data['devicetracker_zone'] = devicetracker_zone
                event_data['latitude'] = self._latitude
                event_data['longitude'] = self._longitude
                event_data['previous_latitude'] = self._latitude_old
                event_data['previous_longitude'] = self._longitude_old
                event_data['map'] = self._map_link
                event_data['mtime'] = current_time
                #_LOGGER.debug( "(" + self._name + ") Event Data: " + event_data )
                #self._hass.bus.fire(DEFAULT_NAME+'_state_update', { 'entity': self._name, 'place_name': place_name, 'from_state': previous_state, 'to_state': new_state, 'distance_from_home': distance_from_home, 'direction': direction, 'devicetracker_zone': devicetracker_zone, 'mtime': current_time, 'latitude': self._latitude, 'longitude': self._longitude, 'map': self._map_link })
                self._hass.bus.fire(DEFAULT_NAME + '_state_update', event_data)
                _LOGGER.debug("(" + self._name + ") Update complete...")
Example #27
0
    def check_proximity_state_change(self, entity, old_state, new_state):
        """Perform the proximity checking."""
        entity_name = new_state.name
        devices_to_calculate = False
        devices_in_zone = ''

        zone_state = self.hass.states.get(self.proximity_zone)
        proximity_latitude = zone_state.attributes.get('latitude')
        proximity_longitude = zone_state.attributes.get('longitude')

        # Check for devices in the monitored zone.
        for device in self.proximity_devices:
            device_state = self.hass.states.get(device)

            if device_state is None:
                devices_to_calculate = True
                continue

            if device_state.state not in self.ignored_zones:
                devices_to_calculate = True

            # Check the location of all devices.
            if (device_state.state).lower() == (self.friendly_name).lower():
                device_friendly = device_state.name
                if devices_in_zone != '':
                    devices_in_zone = devices_in_zone + ', '
                devices_in_zone = devices_in_zone + device_friendly

        # No-one to track so reset the entity.
        if not devices_to_calculate:
            self.dist_to = 'not set'
            self.dir_of_travel = 'not set'
            self.nearest = 'not set'
            self.schedule_update_ha_state()
            return

        # At least one device is in the monitored zone so update the entity.
        if devices_in_zone != '':
            self.dist_to = 0
            self.dir_of_travel = 'arrived'
            self.nearest = devices_in_zone
            self.schedule_update_ha_state()
            return

        # We can't check proximity because latitude and longitude don't exist.
        if 'latitude' not in new_state.attributes:
            return

        # Collect distances to the zone for all devices.
        distances_to_zone = {}
        for device in self.proximity_devices:
            # Ignore devices in an ignored zone.
            device_state = self.hass.states.get(device)
            if device_state.state in self.ignored_zones:
                continue

            # Ignore devices if proximity cannot be calculated.
            if 'latitude' not in device_state.attributes:
                continue

            # Calculate the distance to the proximity zone.
            dist_to_zone = distance(proximity_latitude, proximity_longitude,
                                    device_state.attributes['latitude'],
                                    device_state.attributes['longitude'])

            # Add the device and distance to a dictionary.
            distances_to_zone[device] = round(
                convert(dist_to_zone, 'm', self.unit_of_measurement), 1)

        # Loop through each of the distances collected and work out the
        # closest.
        closest_device = None  # type: str
        dist_to_zone = None  # type: float

        for device in distances_to_zone:
            if not dist_to_zone or distances_to_zone[device] < dist_to_zone:
                closest_device = device
                dist_to_zone = distances_to_zone[device]

        # If the closest device is one of the other devices.
        if closest_device != entity:
            self.dist_to = round(distances_to_zone[closest_device])
            self.dir_of_travel = 'unknown'
            device_state = self.hass.states.get(closest_device)
            self.nearest = device_state.name
            self.schedule_update_ha_state()
            return

        # Stop if we cannot calculate the direction of travel (i.e. we don't
        # have a previous state and a current LAT and LONG).
        if old_state is None or 'latitude' not in old_state.attributes:
            self.dist_to = round(distances_to_zone[entity])
            self.dir_of_travel = 'unknown'
            self.nearest = entity_name
            self.schedule_update_ha_state()
            return

        # Reset the variables
        distance_travelled = 0

        # Calculate the distance travelled.
        old_distance = distance(proximity_latitude, proximity_longitude,
                                old_state.attributes['latitude'],
                                old_state.attributes['longitude'])
        new_distance = distance(proximity_latitude, proximity_longitude,
                                new_state.attributes['latitude'],
                                new_state.attributes['longitude'])
        distance_travelled = round(new_distance - old_distance, 1)

        # Check for tolerance
        if distance_travelled < self.tolerance * -1:
            direction_of_travel = 'towards'
        elif distance_travelled > self.tolerance:
            direction_of_travel = 'away_from'
        else:
            direction_of_travel = 'stationary'

        # Update the proximity entity
        self.dist_to = round(dist_to_zone)
        self.dir_of_travel = direction_of_travel
        self.nearest = entity_name
        self.schedule_update_ha_state()
        _LOGGER.debug(
            'proximity.%s update entity: distance=%s: direction=%s: '
            'device=%s', self.friendly_name, round(dist_to_zone),
            direction_of_travel, entity_name)

        _LOGGER.info('%s: proximity calculation complete', entity_name)
Example #28
0
 def distance(self: object, lat: float, lon: float) -> float:
     """Calculate distance from Home Assistant."""
     return self.units.length(
         location.distance(self.latitude, self.longitude, lat, lon), 'm')
Example #29
0
    async def async_update(self):
        """Update data."""
        _LOGGER.debug("Feed URL: %s", self._url)

        if self._feed:
            self._modified = self._feed.get("modified")
            self._etag = self._feed.get("etag")
        else:
            self._modified = None
            self._etag = None

        try:
            self._feed = await self._hass.async_add_executor_job(
                feedparser.parse, self._url, self._etag, self._modfied)
            _LOGGER.debug("Feed contents: %s", self._feed)

            if not self._feed:
                _LOGGER.debug("Failed to get feed")
            else:
                if self._feed.bozo != 0:
                    _LOGGER.debug("Error parsing feed %s", self._url)
                elif len(self._feed.entries) > 0:
                    _LOGGER.debug("Got %s entries", len(self._feed.entries))

                    pubdate = self._feed.entries[0]["published"]

                    if self._restart:
                        self._lastmsg_time = self._convert_time(pubdate)
                        self._restart = False
                        _LOGGER.debug("Last datestamp read %s",
                                      self._lastmsg_time)
                        return

                    for item in reversed(self._feed.entries):
                        eventmsg = ""

                        lastmsg_time = self._convert_time(item.published)
                        if lastmsg_time < self._lastmsg_time:
                            continue
                        self._lastmsg_time = lastmsg_time

                        eventmsg = item.title.replace(
                            "~", "") + "\n" + item.published + "\n"
                        _LOGGER.debug("New emergency event found: %s",
                                      eventmsg)

                        if "geo_lat" in item:
                            lat_event = float(item.geo_lat)
                            lon_event = float(item.geo_long)
                            dist = distance(self._lat, self._lon, lat_event,
                                            lon_event)
                            if self._radius:
                                _LOGGER.debug("Filtering on Radius %s",
                                              self._radius)
                                _LOGGER.debug("Calculated distance %d m", dist)
                                if dist > self._radius:
                                    eventmsg = ""
                                    _LOGGER.debug("Radius filter mismatch")
                                    continue
                                else:
                                    _LOGGER.debug("Radius filter matched")
                        else:
                            _LOGGER.debug("No location info in item")
                            lat_event = 0.0
                            lon_event = 0.0
                            dist = 0
                            if not self._nolocation:
                                _LOGGER.debug("No location, discarding.")
                                eventmsg = ""
                                continue

                        if "summary" in item:
                            capcodetext = item.summary.replace("<br />", "\n")
                        else:
                            capcodetext = ""

                        if self._capcodelist:
                            _LOGGER.debug("Filtering on Capcode(s) %s",
                                          self._capcodelist)
                            capfound = False
                            for capcode in self._capcodelist:
                                _LOGGER.debug(
                                    "Searching for capcode %s in %s",
                                    capcode.strip(),
                                    capcodetext,
                                )
                                if capcodetext.find(capcode) != -1:
                                    _LOGGER.debug("Capcode filter matched")
                                    capfound = True
                                    break
                                else:
                                    _LOGGER.debug("Capcode filter mismatch")
                                    continue
                            if not capfound:
                                eventmsg = ""

                        if eventmsg:
                            event = {}
                            event["msgtext"] = eventmsg
                            event["latitude"] = lat_event
                            event["longitude"] = lon_event
                            event["distance"] = int(round(dist))
                            event["msgtime"] = lastmsg_time
                            event["capcodetext"] = capcodetext
                            _LOGGER.debug(
                                "Text: %s, Time: %s, Lat: %s, Long: %s, Distance: %s, Capcodetest: %s",
                                event["msgtext"],
                                event["msgtime"],
                                event["latitude"],
                                event["longitude"],
                                event["distance"],
                                event["capcodetext"],
                            )
                            self._data = event

        except ValueError as err:
            _LOGGER.error("Error feedparser %s", err.args)
            self._data = None
    def check_proximity_state_change(self, entity, old_state, new_state):
        # pylint: disable=too-many-branches,too-many-statements,too-many-locals
        """ Function to perform the proximity checking """
        entity_name = new_state.name
        devices_to_calculate = self.ignored_zones == []
        devices_have_coordinates = False
        devices_in_zone = ''

        zone_state = self.hass.states.get(self.proximity_zone)
        proximity_latitude = zone_state.attributes.get('latitude')
        proximity_longitude = zone_state.attributes.get('longitude')

        # check for devices in the monitored zone
        for device in self.proximity_devices:
            device_state = self.hass.states.get(device)

            if 'latitude' not in device_state.attributes:
                continue

            devices_have_coordinates = True

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if not zone.in_zone(ignored_zone_state,
                                    device_state_lat,
                                    device_state_lon):
                    devices_to_calculate = True

            # check the location of all devices
            if zone.in_zone(zone_state,
                            device_state_lat,
                            device_state_lon):
                device_friendly = device_state.name
                if devices_in_zone != '':
                    devices_in_zone = devices_in_zone + ', '
                devices_in_zone = devices_in_zone + device_friendly

        # at least one device is in the monitored zone so update the entity
        if devices_in_zone != '':
            self.dist_to = 0
            self.dir_of_travel = 'arrived'
            self.nearest = devices_in_zone
            self.update_ha_state()
            return

        # no-one to track so reset the entity
        if not devices_to_calculate or not devices_have_coordinates:
            self.dist_to = 'not set'
            self.dir_of_travel = 'not set'
            self.nearest = 'not set'
            self.update_ha_state()
            return

        # we can't check proximity because latitude and longitude don't exist
        if 'latitude' not in new_state.attributes:
            return

        # collect distances to the zone for all devices
        distances_to_zone = {}
        for device in self.proximity_devices:
            # ignore devices in an ignored zone
            device_state = self.hass.states.get(device)

            # ignore devices if proximity cannot be calculated
            if 'latitude' not in device_state.attributes:
                continue

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            device_in_ignored_zone = False
            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if zone.in_zone(ignored_zone_state,
                                device_state_lat,
                                device_state_lon):
                    device_in_ignored_zone = True
                    continue
            if device_in_ignored_zone:
                continue

            # calculate the distance to the proximity zone
            dist_to_zone = distance(proximity_latitude,
                                    proximity_longitude,
                                    device_state_lat,
                                    device_state_lon)

            # add the device and distance to a dictionary
            distances_to_zone[device] = round(dist_to_zone / 1000, 1)

        # loop through each of the distances collected and work out the closest
        closest_device = ''
        dist_to_zone = 1000000

        for device in distances_to_zone:
            if distances_to_zone[device] < dist_to_zone:
                closest_device = device
                dist_to_zone = distances_to_zone[device]

        # if the closest device is one of the other devices
        if closest_device != entity:
            self.dist_to = round(distances_to_zone[closest_device])
            self.dir_of_travel = 'unknown'
            device_state = self.hass.states.get(closest_device)
            self.nearest = device_state.name
            self.update_ha_state()
            return

        # stop if we cannot calculate the direction of travel (i.e. we don't
        # have a previous state and a current LAT and LONG)
        if old_state is None or 'latitude' not in old_state.attributes:
            self.dist_to = round(distances_to_zone[entity])
            self.dir_of_travel = 'unknown'
            self.nearest = entity_name
            self.update_ha_state()
            return

        # reset the variables
        distance_travelled = 0

        # calculate the distance travelled
        old_distance = distance(proximity_latitude, proximity_longitude,
                                old_state.attributes['latitude'],
                                old_state.attributes['longitude'])
        new_distance = distance(proximity_latitude, proximity_longitude,
                                new_state.attributes['latitude'],
                                new_state.attributes['longitude'])
        distance_travelled = round(new_distance - old_distance, 1)

        # check for tolerance
        if distance_travelled < self.tolerance * -1:
            direction_of_travel = 'towards'
        elif distance_travelled > self.tolerance:
            direction_of_travel = 'away_from'
        else:
            direction_of_travel = 'stationary'

        # update the proximity entity
        self.dist_to = round(dist_to_zone)
        self.dir_of_travel = direction_of_travel
        self.nearest = entity_name
        self.update_ha_state()
        _LOGGER.debug('proximity.%s update entity: distance=%s: direction=%s: '
                      'device=%s', self.friendly_name, round(dist_to_zone),
                      direction_of_travel, entity_name)

        _LOGGER.info('%s: proximity calculation complete', entity_name)
Example #31
0
    def check_proximity_state_change(self, entity, old_state, new_state):
        """Function to perform the proximity checking."""
        entity_name = new_state.name
        devices_to_calculate = False
        devices_in_zone = ''

        zone_state = self.hass.states.get(self.proximity_zone)
        proximity_latitude = zone_state.attributes.get('latitude')
        proximity_longitude = zone_state.attributes.get('longitude')

        # Check for devices in the monitored zone.
        for device in self.proximity_devices:
            device_state = self.hass.states.get(device)

            if device_state.state not in self.ignored_zones:
                devices_to_calculate = True

            # Check the location of all devices.
            if (device_state.state).lower() == (self.friendly_name).lower():
                device_friendly = device_state.name
                if devices_in_zone != '':
                    devices_in_zone = devices_in_zone + ', '
                devices_in_zone = devices_in_zone + device_friendly

        # No-one to track so reset the entity.
        if not devices_to_calculate:
            self.dist_to = 'not set'
            self.dir_of_travel = 'not set'
            self.nearest = 'not set'
            self.update_ha_state()
            return

        # At least one device is in the monitored zone so update the entity.
        if devices_in_zone != '':
            self.dist_to = 0
            self.dir_of_travel = 'arrived'
            self.nearest = devices_in_zone
            self.update_ha_state()
            return

        # We can't check proximity because latitude and longitude don't exist.
        if 'latitude' not in new_state.attributes:
            return

        # Collect distances to the zone for all devices.
        distances_to_zone = {}
        for device in self.proximity_devices:
            # Ignore devices in an ignored zone.
            device_state = self.hass.states.get(device)
            if device_state.state in self.ignored_zones:
                continue

            # Ignore devices if proximity cannot be calculated.
            if 'latitude' not in device_state.attributes:
                continue

            # Calculate the distance to the proximity zone.
            dist_to_zone = distance(proximity_latitude,
                                    proximity_longitude,
                                    device_state.attributes['latitude'],
                                    device_state.attributes['longitude'])

            # Add the device and distance to a dictionary.
            distances_to_zone[device] = round(
                convert(dist_to_zone, 'm', self.unit_of_measurement), 1)

        # Loop through each of the distances collected and work out the
        # closest.
        closest_device = None  # type: str
        dist_to_zone = None  # type: float

        for device in distances_to_zone:
            if not dist_to_zone or distances_to_zone[device] < dist_to_zone:
                closest_device = device
                dist_to_zone = distances_to_zone[device]

        # If the closest device is one of the other devices.
        if closest_device != entity:
            self.dist_to = round(distances_to_zone[closest_device])
            self.dir_of_travel = 'unknown'
            device_state = self.hass.states.get(closest_device)
            self.nearest = device_state.name
            self.update_ha_state()
            return

        # Stop if we cannot calculate the direction of travel (i.e. we don't
        # have a previous state and a current LAT and LONG).
        if old_state is None or 'latitude' not in old_state.attributes:
            self.dist_to = round(distances_to_zone[entity])
            self.dir_of_travel = 'unknown'
            self.nearest = entity_name
            self.update_ha_state()
            return

        # Reset the variables
        distance_travelled = 0

        # Calculate the distance travelled.
        old_distance = distance(proximity_latitude, proximity_longitude,
                                old_state.attributes['latitude'],
                                old_state.attributes['longitude'])
        new_distance = distance(proximity_latitude, proximity_longitude,
                                new_state.attributes['latitude'],
                                new_state.attributes['longitude'])
        distance_travelled = round(new_distance - old_distance, 1)

        # Check for tolerance
        if distance_travelled < self.tolerance * -1:
            direction_of_travel = 'towards'
        elif distance_travelled > self.tolerance:
            direction_of_travel = 'away_from'
        else:
            direction_of_travel = 'stationary'

        # Update the proximity entity
        self.dist_to = round(dist_to_zone)
        self.dir_of_travel = direction_of_travel
        self.nearest = entity_name
        self.update_ha_state()
        _LOGGER.debug('proximity.%s update entity: distance=%s: direction=%s: '
                      'device=%s', self.friendly_name, round(dist_to_zone),
                      direction_of_travel, entity_name)

        _LOGGER.info('%s: proximity calculation complete', entity_name)
    def check_proximity_initial_state(self):
        # pylint: disable=too-many-branches,too-many-statements,too-many-locals
        """ Function to perform the proximity checking in the initial state """
        devices_to_calculate = self.ignored_zones == []
        devices_have_coordinates = False
        devices_in_zone = ''

        zone_state = self.hass.states.get(self.proximity_zone)
        proximity_latitude = zone_state.attributes.get('latitude')
        proximity_longitude = zone_state.attributes.get('longitude')

        # check for devices in the monitored zone
        for device in self.proximity_devices:
            device_state = self.hass.states.get(device)

            if 'latitude' not in device_state.attributes:
                continue

            devices_have_coordinates = True

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if not zone.in_zone(ignored_zone_state,
                                    device_state_lat,
                                    device_state_lon):
                    devices_to_calculate = True

            # check the location of all devices
            if zone.in_zone(zone_state,
                            device_state_lat,
                            device_state_lon):
                device_friendly = device_state.name
                if devices_in_zone != '':
                    devices_in_zone = devices_in_zone + ', '
                devices_in_zone = devices_in_zone + device_friendly

        # at least one device is in the monitored zone so update the entity
        if devices_in_zone != '':
            self.dist_to = 0
            self.dir_of_travel = 'arrived'
            self.nearest = devices_in_zone
            self.update_ha_state()
            return

        # no-one to track so reset the entity
        if not devices_to_calculate or not devices_have_coordinates:
            self.dist_to = 'not set'
            self.dir_of_travel = 'not set'
            self.nearest = 'not set'
            self.update_ha_state()
            return

        # collect distances to the zone for all devices
        distances_to_zone = {}
        for device in self.proximity_devices:
            # ignore devices in an ignored zone
            device_state = self.hass.states.get(device)

            # ignore devices if proximity cannot be calculated
            if 'latitude' not in device_state.attributes:
                continue

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            device_in_ignored_zone = False
            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if zone.in_zone(ignored_zone_state,
                                device_state_lat,
                                device_state_lon):
                    device_in_ignored_zone = True
                    continue
            if device_in_ignored_zone:
                continue

            # calculate the distance to the proximity zone
            dist_to_zone = distance(proximity_latitude,
                                    proximity_longitude,
                                    device_state_lat,
                                    device_state_lon)

            # add the device and distance to a dictionary
            distances_to_zone[device] = round(dist_to_zone / 1000, 1)

        # loop through each of the distances collected and work out the closest
        closest_device = ''
        dist_to_zone = 1000000

        for device in distances_to_zone:
            if distances_to_zone[device] < dist_to_zone:
                closest_device = device
                dist_to_zone = distances_to_zone[device]

        self.dist_to = round(distances_to_zone[closest_device])
        self.dir_of_travel = 'unknown'
        device_state = self.hass.states.get(closest_device)
        self.nearest = device_state.name
        self.update_ha_state()
Example #33
0
    async def async_update(self, dummy):
        """Update data."""

        if self._feed:
            self._modified = self._feed.get("modified")
            self._etag = self._feed.get("etag")
        else:
            self._modified = None
            self._etag = None

        self._feed = await self._hass.async_add_executor_job(
            feedparser.parse, self._url, self._etag, self._modified)

        if not self._feed:
            _LOGGER.debug("Failed to get feed data from %s", self._url)
            return

        if self._feed.bozo:
            _LOGGER.debug("Error parsing feed data from %s", self._url)
            return

        _LOGGER.debug("Feed url: %s data: %s", self._url, self._feed)

        if self._restart:
            self._restart = False
            self._event_time = self._convert_time(
                self._feed.entries[0]["published"])
            _LOGGER.debug("Start fresh after a restart")
            return

        try:
            for entry in reversed(self._feed.entries):

                event_msg = ""
                event_caps = ""
                event_time = self._convert_time(entry.published)
                if event_time < self._event_time:
                    continue
                self._event_time = event_time
                event_msg = entry.title.replace(
                    "~", "") + "\n" + entry.published + "\n"
                _LOGGER.debug("New P2000 event found: %s, at %s", event_msg,
                              entry.published)

                if "geo_lat" in entry:
                    event_lat = float(entry.geo_lat)
                    event_lon = float(entry.geo_long)
                    event_dist = distance(self._lat, self._lon, event_lat,
                                          event_lon)
                    event_dist = int(round(event_dist))
                    if self._radius:
                        _LOGGER.debug(
                            "Filtering on Radius %s, calculated distance %d m ",
                            self._radius,
                            event_dist,
                        )
                        if event_dist > self._radius:
                            event_msg = ""
                            _LOGGER.debug("Radius filter mismatch, discarding")
                            continue
                        _LOGGER.debug("Radius filter matched")
                else:
                    event_lat = 0.0
                    event_lon = 0.0
                    event_dist = 0
                    if not self._nolocation:
                        _LOGGER.debug("No location found, discarding")
                        continue

                if "summary" in entry:
                    event_caps = entry.summary.replace("<br />", "\n")

                if self._capcodelist:
                    _LOGGER.debug("Filtering on Capcode(s) %s",
                                  self._capcodelist)
                    capfound = False
                    for capcode in self._capcodelist:
                        _LOGGER.debug(
                            "Searching for capcode %s in %s",
                            capcode.strip(),
                            event_caps,
                        )
                        if event_caps.find(capcode) != -1:
                            _LOGGER.debug("Capcode filter matched")
                            capfound = True
                            break
                        _LOGGER.debug("Capcode filter mismatch, discarding")
                        continue
                    if not capfound:
                        continue

                if self._contains:
                    _LOGGER.debug("Filtering on Contains string %s",
                                  self._contains)
                    if event_msg.find(self._contains) != -1:
                        _LOGGER.debug("Contains string filter matched")
                    else:
                        _LOGGER.debug(
                            "Contains string filter mismatch, discarding")
                        continue

                if event_msg:
                    event = {}
                    event["msgtext"] = event_msg
                    event["latitude"] = event_lat
                    event["longitude"] = event_lon
                    event["distance"] = event_dist
                    event["msgtime"] = event_time
                    event["capcodetext"] = event_caps
                    _LOGGER.debug("Event: %s", event)
                    self._data = event

            dispatcher_send(self._hass, DATA_UPDATED)

        except ValueError as err:
            _LOGGER.error("Error parsing feed data %s", err)
            self._data = None
    def handle_reverse_geocode(call):
        """
        Handle the reverse_geocode service.

        Input:
            - Parameters for the call:
                entity_id
                friendly_name_template (optional)
                force_update (optional)
            - Attributes of entity_id:
                - latitude
                - longitude
                - location_time (optional)
        Output:
            - determine <locality> for friendly_name
            - record full location from Google_Maps, MapQuest, and/or Open_Street_Map
            - calculate other location-based statistics, such as distance_from_home
            - create/update additional sensors if requested
        """

        entity_id = call.data.get(CONF_ENTITY_ID, "NONE")
        template = call.data.get(CONF_FRIENDLY_NAME_TEMPLATE, "NONE")
        force_update = call.data.get("force_update", False)

        if entity_id == "NONE":
            {
                _LOGGER.warning(
                    "%s is required in call of %s.reverse_geocode service." %
                    (CONF_ENTITY_ID, DOMAIN))
            }
            return False

        _LOGGER.debug("(%s) === Start === %s = %s; %s = %s" % (
            entity_id,
            CONF_FRIENDLY_NAME_TEMPLATE,
            template,
            "force_update",
            force_update,
        ))

        with INTEGRATION_LOCK:
            """Lock while updating the pli(API_STATE_OBJECT)."""
            _LOGGER.debug("INTEGRATION_LOCK obtained")

            try:
                currentApiTime = datetime.now()

                if pli.state.lower() != STATE_ON:
                    """Allow API calls to be paused."""
                    pli.attributes["api_calls_skipped"] += 1
                    _LOGGER.debug(
                        "(%s) api_calls_skipped = %d" %
                        (entity_id, pli.attributes["api_calls_skipped"]))
                else:
                    """Throttle the API calls so that we don't exceed policy."""
                    wait_time = (pli.attributes["api_last_updated"] -
                                 currentApiTime +
                                 THROTTLE_INTERVAL).total_seconds()
                    if wait_time > 0:
                        pli.attributes["api_calls_throttled"] += 1
                        _LOGGER.debug(
                            "(%s) wait_time = %05.3f; api_calls_throttled = %d"
                            % (
                                entity_id,
                                wait_time,
                                pli.attributes["api_calls_throttled"],
                            ))
                        time.sleep(wait_time)
                        currentApiTime = datetime.now()

                    # Record the integration attributes in the API_STATE_OBJECT:

                    pli.attributes["api_last_updated"] = currentApiTime

                    pli.attributes["api_calls_requested"] += 1

                    counter_attribute = f"{entity_id} calls"
                    if counter_attribute in pli.attributes:
                        new_count = pli.attributes[counter_attribute] + 1
                    else:
                        new_count = 1
                    pli.attributes[counter_attribute] = new_count
                    _LOGGER.debug("(" + entity_id + ") " + counter_attribute +
                                  " = " + str(new_count))

                    # Handle the service call, updating the target(entity_id):

                    with TARGET_LOCK:
                        """Lock while updating the target(entity_id)."""
                        _LOGGER.debug("TARGET_LOCK obtained")

                        target = PERSON_LOCATION_ENTITY(entity_id, pli)
                        attribution = ""

                        if ATTR_LATITUDE in target.attributes:
                            new_latitude = target.attributes[ATTR_LATITUDE]
                        else:
                            new_latitude = "None"
                        if ATTR_LONGITUDE in target.attributes:
                            new_longitude = target.attributes[ATTR_LONGITUDE]
                        else:
                            new_longitude = "None"

                        if "location_latitude" in target.this_entity_info:
                            old_latitude = target.this_entity_info[
                                "location_latitude"]
                        else:
                            old_latitude = "None"
                        if "location_longitude" in target.this_entity_info:
                            old_longitude = target.this_entity_info[
                                "location_longitude"]
                        else:
                            old_longitude = "None"

                        if (new_latitude != "None" and new_longitude != "None"
                                and old_latitude != "None"
                                and old_longitude != "None"):
                            distance_traveled = round(
                                distance(
                                    float(new_latitude),
                                    float(new_longitude),
                                    float(old_latitude),
                                    float(old_longitude),
                                ),
                                3,
                            )

                            if (pli.attributes["home_latitude"] != "None"
                                    and pli.attributes["home_longitude"] !=
                                    "None"):
                                old_distance_from_home = round(
                                    distance(
                                        float(old_latitude),
                                        float(old_longitude),
                                        float(pli.attributes["home_latitude"]),
                                        float(
                                            pli.attributes["home_longitude"]),
                                    ),
                                    3,
                                )
                            else:
                                old_distance_from_home = 0

                            compass_bearing = round(
                                calculate_initial_compass_bearing(
                                    (float(old_latitude),
                                     float(old_longitude)),
                                    (float(new_latitude),
                                     float(new_longitude)),
                                ),
                                1,
                            )

                            _LOGGER.debug("(" + entity_id +
                                          ") distance_traveled = " +
                                          str(distance_traveled) +
                                          "; compass_bearing = " +
                                          str(compass_bearing))
                        else:
                            distance_traveled = 0
                            old_distance_from_home = 0
                            compass_bearing = 0

                        target.attributes[
                            ATTR_COMPASS_BEARING] = compass_bearing

                        if new_latitude == "None" or new_longitude == "None":
                            _LOGGER.debug(
                                "(" + entity_id +
                                ") Skipping geocoding because coordinates are missing"
                            )
                        elif (distance_traveled <
                              MIN_DISTANCE_TRAVELLED_TO_GEOCODE
                              and old_latitude != "None"
                              and old_longitude != "None"
                              and not force_update):
                            _LOGGER.debug(
                                "(" + entity_id +
                                ") Skipping geocoding because distance_traveled < "
                                + str(MIN_DISTANCE_TRAVELLED_TO_GEOCODE))
                        else:
                            locality = "?"

                            if "location_time" in target.attributes:
                                new_location_time = datetime.strptime(
                                    str(target.attributes["location_time"]),
                                    "%Y-%m-%d %H:%M:%S.%f",
                                )
                                _LOGGER.debug("(" + entity_id +
                                              ") new_location_time = " +
                                              str(new_location_time))
                            else:
                                new_location_time = currentApiTime

                            if ("reverse_geocode_location_time"
                                    in target.this_entity_info):
                                old_location_time = target.this_entity_info[
                                    "reverse_geocode_location_time"]
                                _LOGGER.debug("(" + entity_id +
                                              ") old_location_time = " +
                                              str(old_location_time))
                            else:
                                old_location_time = new_location_time

                            elapsed_seconds = (
                                new_location_time -
                                old_location_time).total_seconds()
                            _LOGGER.debug("(" + entity_id +
                                          ") elapsed_seconds = " +
                                          str(elapsed_seconds))

                            if elapsed_seconds > 0:
                                speed_during_interval = (distance_traveled /
                                                         elapsed_seconds)
                                _LOGGER.debug("(" + entity_id +
                                              ") speed_during_interval = " +
                                              str(speed_during_interval) +
                                              " meters/sec")
                            else:
                                speed_during_interval = 0

                            if ("reported_state" in target.attributes
                                    and target.attributes["reported_state"].
                                    lower() == "home"):
                                distance_from_home = 0  # clamp it down since "Home" is not a single point
                            elif (new_latitude != "None"
                                  and new_longitude != "None"
                                  and pli.attributes["home_latitude"] != "None"
                                  and
                                  pli.attributes["home_longitude"] != "None"):
                                distance_from_home = round(
                                    distance(
                                        float(new_latitude),
                                        float(new_longitude),
                                        float(pli.attributes["home_latitude"]),
                                        float(
                                            pli.attributes["home_longitude"]),
                                    ),
                                    3,
                                )
                            else:
                                distance_from_home = (
                                    0  # could only happen if we don't have coordinates
                                )
                            _LOGGER.debug("(" + entity_id +
                                          ") meters_from_home = " +
                                          str(distance_from_home))
                            target.attributes[ATTR_METERS_FROM_HOME] = round(
                                distance_from_home, 1)
                            target.attributes[ATTR_MILES_FROM_HOME] = round(
                                distance_from_home / METERS_PER_MILE, 1)

                            if speed_during_interval <= 0.5:
                                direction = "stationary"
                            elif old_distance_from_home > distance_from_home:
                                direction = "toward home"
                            elif old_distance_from_home < distance_from_home:
                                direction = "away from home"
                            else:
                                direction = "stationary"
                            _LOGGER.debug("(" + entity_id + ") direction = " +
                                          direction)
                            target.attributes["direction"] = direction

                            if (pli.configuration[CONF_OSM_API_KEY] !=
                                    DEFAULT_API_KEY_NOT_SET):
                                """Call the Open Street Map (Nominatim) API if CONF_OSM_API_KEY is configured"""
                                if (pli.configuration[CONF_OSM_API_KEY] ==
                                        DEFAULT_API_KEY_NOT_SET):
                                    osm_url = (
                                        "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat="
                                        + str(new_latitude) + "&lon=" +
                                        str(new_longitude) +
                                        "&addressdetails=1&namedetails=1&zoom=18&limit=1"
                                    )
                                else:
                                    osm_url = (
                                        "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat="
                                        + str(new_latitude) + "&lon=" +
                                        str(new_longitude) +
                                        "&addressdetails=1&namedetails=1&zoom=18&limit=1&email="
                                        + pli.configuration[CONF_OSM_API_KEY])

                                osm_decoded = {}
                                osm_response = get(osm_url)
                                osm_json_input = osm_response.text
                                osm_decoded = json.loads(osm_json_input)

                                if "city" in osm_decoded["address"]:
                                    locality = osm_decoded["address"]["city"]
                                elif "town" in osm_decoded["address"]:
                                    locality = osm_decoded["address"]["town"]
                                elif "villiage" in osm_decoded["address"]:
                                    locality = osm_decoded["address"][
                                        "village"]
                                elif "municipality" in osm_decoded["address"]:
                                    locality = osm_decoded["address"][
                                        "municipality"]
                                elif "county" in osm_decoded["address"]:
                                    locality = osm_decoded["address"]["county"]
                                elif "state" in osm_decoded["address"]:
                                    locality = osm_decoded["address"]["state"]
                                elif "country" in osm_decoded["address"]:
                                    locality = osm_decoded["address"][
                                        "country"]
                                _LOGGER.debug("(" + entity_id +
                                              ") OSM locality = " + locality)

                                if "display_name" in osm_decoded:
                                    display_name = osm_decoded["display_name"]
                                else:
                                    display_name = locality
                                _LOGGER.debug("(" + entity_id +
                                              ") OSM display_name = " +
                                              display_name)

                                target.attributes[
                                    "Open_Street_Map"] = display_name.replace(
                                        ", ", " ")

                                if "licence" in osm_decoded:
                                    osm_attribution = '"' + osm_decoded[
                                        "licence"] + '"'
                                    attribution += osm_attribution + "; "
                                else:
                                    osm_attribution = ""

                                if (ATTR_GEOCODED in pli.
                                        configuration[CONF_CREATE_SENSORS]):
                                    target.make_template_sensor(
                                        "Open_Street_Map",
                                        [
                                            {
                                                ATTR_COMPASS_BEARING:
                                                compass_bearing
                                            },
                                            ATTR_LATITUDE,
                                            ATTR_LONGITUDE,
                                            ATTR_SOURCE_TYPE,
                                            ATTR_GPS_ACCURACY,
                                            "icon",
                                            {
                                                "locality": locality
                                            },
                                            {
                                                "location_time":
                                                new_location_time.strftime(
                                                    "%Y-%m-%d %H:%M:%S")
                                            },
                                            {
                                                ATTR_ATTRIBUTION:
                                                osm_attribution
                                            },
                                        ],
                                    )

                            if (pli.configuration[CONF_GOOGLE_API_KEY] !=
                                    DEFAULT_API_KEY_NOT_SET):
                                """Call the Google Maps Reverse Geocoding API if CONF_GOOGLE_API_KEY is configured"""
                                """https://developers.google.com/maps/documentation/geocoding/overview?hl=en_US#ReverseGeocoding"""
                                google_url = (
                                    "https://maps.googleapis.com/maps/api/geocode/json?language="
                                    + pli.configuration[CONF_LANGUAGE] +
                                    "&region=" +
                                    pli.configuration[CONF_REGION] +
                                    "&latlng=" + str(new_latitude) + "," +
                                    str(new_longitude) + "&key=" +
                                    pli.configuration[CONF_GOOGLE_API_KEY])
                                google_decoded = {}
                                google_response = get(google_url)
                                google_json_input = google_response.text
                                google_decoded = json.loads(google_json_input)

                                google_status = google_decoded["status"]
                                if google_status != "OK":
                                    _LOGGER.error("(" + entity_id +
                                                  ") google_status = " +
                                                  google_status)
                                else:
                                    if "results" in google_decoded:
                                        if ("formatted_address" in
                                                google_decoded["results"][0]):
                                            formatted_address = google_decoded[
                                                "results"][0][
                                                    "formatted_address"]
                                            _LOGGER.debug(
                                                "(" + entity_id +
                                                ") Google formatted_address = "
                                                + formatted_address)
                                            target.attributes[
                                                "Google_Maps"] = formatted_address
                                        for component in google_decoded[
                                                "results"][0][
                                                    "address_components"]:
                                            if "locality" in component[
                                                    "types"]:
                                                locality = component[
                                                    "long_name"]
                                                _LOGGER.debug(
                                                    "(" + entity_id +
                                                    ") Google locality = " +
                                                    locality)
                                            elif (
                                                    locality == "?"
                                            ) and ("administrative_area_level_2"
                                                   in component["types"]
                                                   ):  # fall back to county
                                                locality = component[
                                                    "long_name"]
                                            elif (
                                                    locality == "?"
                                            ) and ("administrative_area_level_1"
                                                   in component["types"]
                                                   ):  # fall back to state
                                                locality = component[
                                                    "long_name"]

                                        google_attribution = '"powered by Google"'
                                        attribution += google_attribution + "; "

                                        if (ATTR_GEOCODED in pli.configuration[
                                                CONF_CREATE_SENSORS]):
                                            target.make_template_sensor(
                                                "Google_Maps",
                                                [
                                                    {
                                                        ATTR_COMPASS_BEARING:
                                                        compass_bearing
                                                    },
                                                    ATTR_LATITUDE,
                                                    ATTR_LONGITUDE,
                                                    ATTR_SOURCE_TYPE,
                                                    ATTR_GPS_ACCURACY,
                                                    "icon",
                                                    {
                                                        "locality": locality
                                                    },
                                                    {
                                                        "location_time":
                                                        new_location_time.
                                                        strftime(
                                                            "%Y-%m-%d %H:%M:%S"
                                                        )
                                                    },
                                                    {
                                                        ATTR_ATTRIBUTION:
                                                        google_attribution
                                                    },
                                                ],
                                            )

                            if (pli.configuration[CONF_MAPQUEST_API_KEY] !=
                                    DEFAULT_API_KEY_NOT_SET):
                                """Call the Mapquest Reverse Geocoding API if CONF_MAPQUEST_API_KEY is configured"""
                                """https://developer.mapquest.com/documentation/geocoding-api/reverse/get/"""
                                mapquest_url = (
                                    "https://www.mapquestapi.com/geocoding/v1/reverse"
                                    + "?location=" + str(new_latitude) + "," +
                                    str(new_longitude) + "&thumbMaps=false" +
                                    "&key=" +
                                    pli.configuration[CONF_MAPQUEST_API_KEY])
                                mapquest_decoded = {}
                                mapquest_response = get(mapquest_url)
                                mapquest_json_input = mapquest_response.text
                                _LOGGER.debug("(" + entity_id +
                                              ") response - " +
                                              mapquest_json_input)
                                mapquest_decoded = json.loads(
                                    mapquest_json_input)

                                mapquest_statuscode = mapquest_decoded["info"][
                                    "statuscode"]
                                if mapquest_statuscode != 0:
                                    _LOGGER.error(
                                        "(" + entity_id +
                                        ") mapquest_statuscode = " +
                                        str(mapquest_statuscode) +
                                        " messages = " +
                                        mapquest_decoded["info"]["messages"])
                                else:
                                    if ("results" in mapquest_decoded
                                            and "locations"
                                            in mapquest_decoded["results"][0]):
                                        mapquest_location = mapquest_decoded[
                                            "results"][0]["locations"][0]

                                        formatted_address = ""
                                        if "street" in mapquest_location:
                                            formatted_address += (
                                                mapquest_location["street"] +
                                                ", ")
                                        if "adminArea5" in mapquest_location:  # city
                                            locality = mapquest_location[
                                                "adminArea5"]
                                            formatted_address += locality + ", "
                                        elif ("adminArea4" in mapquest_location
                                              and "adminArea4Type"
                                              in mapquest_location):  # county
                                            locality = (
                                                mapquest_location["adminArea4"]
                                                + " " + mapquest_location[
                                                    "adminArea4Type"])
                                            formatted_address += locality + ", "
                                        if "adminArea3" in mapquest_location:  # state
                                            formatted_address += (
                                                mapquest_location["adminArea3"]
                                                + " ")
                                        if "postalCode" in mapquest_location:  # zip
                                            formatted_address += (
                                                mapquest_location["postalCode"]
                                                + " ")
                                        if ("adminArea1" in mapquest_location
                                                and
                                                mapquest_location["adminArea1"]
                                                != "US"):  # country
                                            formatted_address += mapquest_location[
                                                "adminArea1"]

                                        _LOGGER.debug(
                                            "(" + entity_id +
                                            ") mapquest formatted_address = " +
                                            formatted_address)
                                        target.attributes[
                                            "MapQuest"] = formatted_address

                                        _LOGGER.debug(
                                            "(" + entity_id +
                                            ") mapquest locality = " +
                                            locality)

                                        mapquest_attribution = (
                                            '"' + mapquest_decoded["info"]
                                            ["copyright"]["text"] + '"')
                                        attribution += mapquest_attribution + "; "

                                        if (ATTR_GEOCODED in pli.configuration[
                                                CONF_CREATE_SENSORS]):
                                            target.make_template_sensor(
                                                "MapQuest",
                                                [
                                                    {
                                                        ATTR_COMPASS_BEARING:
                                                        compass_bearing
                                                    },
                                                    ATTR_LATITUDE,
                                                    ATTR_LONGITUDE,
                                                    ATTR_SOURCE_TYPE,
                                                    ATTR_GPS_ACCURACY,
                                                    "icon",
                                                    {
                                                        "locality": locality
                                                    },
                                                    {
                                                        "location_time":
                                                        new_location_time.
                                                        strftime(
                                                            "%Y-%m-%d %H:%M:%S"
                                                        )
                                                    },
                                                    {
                                                        ATTR_ATTRIBUTION:
                                                        mapquest_attribution
                                                    },
                                                ],
                                            )

                            if template != "NONE":
                                target.attributes[
                                    "friendly_name"] = template.replace(
                                        "<locality>", locality)
                            target.this_entity_info["locality"] = locality
                            target.this_entity_info["geocode_count"] += 1
                            target.this_entity_info[
                                "location_latitude"] = new_latitude
                            target.this_entity_info[
                                "location_longitude"] = new_longitude
                            target.this_entity_info[
                                "reverse_geocode_location_time"] = new_location_time

                            if "reported_state" in target.attributes:
                                if target.attributes[
                                        "reported_state"] != "Away":
                                    newBreadCrumb = target.attributes[
                                        "reported_state"]
                                else:
                                    newBreadCrumb = locality
                            else:
                                newBreadCrumb = locality
                            if ATTR_BREAD_CRUMBS in target.attributes:
                                oldBreadCrumbs = target.attributes[
                                    ATTR_BREAD_CRUMBS]
                                if not oldBreadCrumbs.endswith(newBreadCrumb):
                                    target.attributes[ATTR_BREAD_CRUMBS] = (
                                        oldBreadCrumbs + "> " +
                                        newBreadCrumb)[-255:]
                            else:
                                target.attributes[
                                    ATTR_BREAD_CRUMBS] = newBreadCrumb

                            # Call WazeRouteCalculator if not at Home:
                            if not pli.configuration["use_waze"]:
                                pass
                            elif (target.attributes[ATTR_METERS_FROM_HOME] <
                                  WAZE_MIN_METERS_FROM_HOME):
                                target.attributes[
                                    ATTR_DRIVING_MILES] = target.attributes[
                                        ATTR_MILES_FROM_HOME]
                                target.attributes[ATTR_DRIVING_MINUTES] = "0"
                            else:
                                try:
                                    """
                                    Figure it out from:
                                    https://github.com/home-assistant/core/blob/dev/homeassistant/components/waze_travel_time/sensor.py
                                    https://github.com/kovacsbalu/WazeRouteCalculator
                                    """
                                    _LOGGER.debug("(" + entity_id +
                                                  ") Waze calculation")

                                    from_location = (str(new_latitude) + "," +
                                                     str(new_longitude))
                                    to_location = (
                                        str(pli.attributes["home_latitude"]) +
                                        "," +
                                        str(pli.attributes["home_longitude"]))
                                    route = WazeRouteCalculator(
                                        from_location,
                                        to_location,
                                        pli.configuration["waze_region"],
                                        avoid_toll_roads=True,
                                    )
                                    routeTime, routeDistance = route.calc_route_info(
                                    )
                                    _LOGGER.debug("(" + entity_id +
                                                  ") Waze routeDistance " +
                                                  str(routeDistance))  # km
                                    routeDistance = (routeDistance *
                                                     METERS_PER_KM /
                                                     METERS_PER_MILE)  # miles
                                    if routeDistance >= 100:
                                        target.attributes[
                                            ATTR_DRIVING_MILES] = str(
                                                round(routeDistance, 0))
                                    elif routeDistance >= 10:
                                        target.attributes[
                                            ATTR_DRIVING_MILES] = str(
                                                round(routeDistance, 1))
                                    else:
                                        target.attributes[
                                            ATTR_DRIVING_MILES] = str(
                                                round(routeDistance, 2))
                                    _LOGGER.debug("(" + entity_id +
                                                  ") Waze routeTime " +
                                                  str(routeTime))  # minutes
                                    target.attributes[
                                        ATTR_DRIVING_MINUTES] = str(
                                            round(routeTime, 1))
                                    attribution += (
                                        '"Data by Waze App. https://waze.com"; '
                                    )
                                except Exception as e:
                                    _LOGGER.error("(" + entity_id +
                                                  ") Waze Exception " +
                                                  type(e).__name__ + ": " +
                                                  str(e))
                                    _LOGGER.debug(traceback.format_exc())
                                    pli.attributes["waze_error_count"] += 1

                                    target.attributes[
                                        ATTR_DRIVING_MILES] = target.attributes[
                                            ATTR_MILES_FROM_HOME]

                        target.attributes[ATTR_ATTRIBUTION] = attribution

                        target.set_state()

                        target.make_template_sensors()

                        _LOGGER.debug("TARGET_LOCK release...")
            except Exception as e:
                _LOGGER.error("(%s) Exception %s: %s" %
                              (entity_id, type(e).__name__, str(e)))
                _LOGGER.debug(traceback.format_exc())
                pli.attributes["api_error_count"] += 1

            pli.set_state()
            _LOGGER.debug("INTEGRATION_LOCK release...")
        _LOGGER.debug("(%s) === Return ===", entity_id)
    def check_proximity_initial_state(self):
        # pylint: disable=too-many-branches,too-many-statements,too-many-locals
        """ Function to perform the proximity checking in the initial state """
        devices_to_calculate = self.ignored_zones == []
        devices_have_coordinates = False
        devices_in_zone = ''

        zone_state = self.hass.states.get(self.proximity_zone)
        proximity_latitude = zone_state.attributes.get('latitude')
        proximity_longitude = zone_state.attributes.get('longitude')

        # check for devices in the monitored zone
        for device in self.proximity_devices:
            device_state = self.hass.states.get(device)

            if 'latitude' not in device_state.attributes:
                continue

            devices_have_coordinates = True

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if not zone.in_zone(ignored_zone_state, device_state_lat,
                                    device_state_lon):
                    devices_to_calculate = True

            # check the location of all devices
            if zone.in_zone(zone_state, device_state_lat, device_state_lon):
                device_friendly = device_state.name
                if devices_in_zone != '':
                    devices_in_zone = devices_in_zone + ', '
                devices_in_zone = devices_in_zone + device_friendly

        # at least one device is in the monitored zone so update the entity
        if devices_in_zone != '':
            self.dist_to = 0
            self.dir_of_travel = 'arrived'
            self.nearest = devices_in_zone
            self.update_ha_state()
            return

        # no-one to track so reset the entity
        if not devices_to_calculate or not devices_have_coordinates:
            self.dist_to = 'not set'
            self.dir_of_travel = 'not set'
            self.nearest = 'not set'
            self.update_ha_state()
            return

        # collect distances to the zone for all devices
        distances_to_zone = {}
        for device in self.proximity_devices:
            # ignore devices in an ignored zone
            device_state = self.hass.states.get(device)

            # ignore devices if proximity cannot be calculated
            if 'latitude' not in device_state.attributes:
                continue

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            device_in_ignored_zone = False
            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if zone.in_zone(ignored_zone_state, device_state_lat,
                                device_state_lon):
                    device_in_ignored_zone = True
                    continue
            if device_in_ignored_zone:
                continue

            # calculate the distance to the proximity zone
            dist_to_zone = distance(proximity_latitude, proximity_longitude,
                                    device_state_lat, device_state_lon)

            # add the device and distance to a dictionary
            distances_to_zone[device] = round(dist_to_zone / 1000, 1)

        # loop through each of the distances collected and work out the closest
        closest_device = ''
        dist_to_zone = 1000000

        for device in distances_to_zone:
            if distances_to_zone[device] < dist_to_zone:
                closest_device = device
                dist_to_zone = distances_to_zone[device]

        self.dist_to = round(distances_to_zone[closest_device])
        self.dir_of_travel = 'unknown'
        device_state = self.hass.states.get(closest_device)
        self.nearest = device_state.name
        self.update_ha_state()
Example #36
0
    def check_proximity_state_change(self, entity, old_state, new_state):
        """Perform the proximity checking."""
        entity_name = new_state.name
        devices_to_calculate = False
        devices_in_zone = ""

        zone_state = self.hass.states.get(self.proximity_zone)
        proximity_latitude = zone_state.attributes.get(ATTR_LATITUDE)
        proximity_longitude = zone_state.attributes.get(ATTR_LONGITUDE)

        # Check for devices in the monitored zone.
        for device in self.proximity_devices:
            device_state = self.hass.states.get(device)

            if device_state is None:
                devices_to_calculate = True
                continue

            if device_state.state not in self.ignored_zones:
                devices_to_calculate = True

            # Check the location of all devices.
            if (device_state.state).lower() == (self.friendly_name).lower():
                device_friendly = device_state.name
                if devices_in_zone != "":
                    devices_in_zone = f"{devices_in_zone}, "
                devices_in_zone = devices_in_zone + device_friendly

        # No-one to track so reset the entity.
        if not devices_to_calculate:
            self.dist_to = "not set"
            self.dir_of_travel = "not set"
            self.nearest = "not set"
            self.schedule_update_ha_state()
            return

        # At least one device is in the monitored zone so update the entity.
        if devices_in_zone != "":
            self.dist_to = 0
            self.dir_of_travel = "arrived"
            self.nearest = devices_in_zone
            self.schedule_update_ha_state()
            return

        # We can't check proximity because latitude and longitude don't exist.
        if "latitude" not in new_state.attributes:
            return

        # Collect distances to the zone for all devices.
        distances_to_zone = {}
        for device in self.proximity_devices:
            # Ignore devices in an ignored zone.
            device_state = self.hass.states.get(device)
            if device_state.state in self.ignored_zones:
                continue

            # Ignore devices if proximity cannot be calculated.
            if "latitude" not in device_state.attributes:
                continue

            # Calculate the distance to the proximity zone.
            dist_to_zone = distance(
                proximity_latitude,
                proximity_longitude,
                device_state.attributes[ATTR_LATITUDE],
                device_state.attributes[ATTR_LONGITUDE],
            )

            # Add the device and distance to a dictionary.
            distances_to_zone[device] = round(
                convert(dist_to_zone, LENGTH_METERS, self.unit_of_measurement), 1
            )

        # Loop through each of the distances collected and work out the
        # closest.
        closest_device: str = None
        dist_to_zone: float = None

        for device, zone in distances_to_zone.items():
            if not dist_to_zone or zone < dist_to_zone:
                closest_device = device
                dist_to_zone = zone

        # If the closest device is one of the other devices.
        if closest_device != entity:
            self.dist_to = round(distances_to_zone[closest_device])
            self.dir_of_travel = "unknown"
            device_state = self.hass.states.get(closest_device)
            self.nearest = device_state.name
            self.schedule_update_ha_state()
            return

        # Stop if we cannot calculate the direction of travel (i.e. we don't
        # have a previous state and a current LAT and LONG).
        if old_state is None or "latitude" not in old_state.attributes:
            self.dist_to = round(distances_to_zone[entity])
            self.dir_of_travel = "unknown"
            self.nearest = entity_name
            self.schedule_update_ha_state()
            return

        # Reset the variables
        distance_travelled = 0

        # Calculate the distance travelled.
        old_distance = distance(
            proximity_latitude,
            proximity_longitude,
            old_state.attributes[ATTR_LATITUDE],
            old_state.attributes[ATTR_LONGITUDE],
        )
        new_distance = distance(
            proximity_latitude,
            proximity_longitude,
            new_state.attributes[ATTR_LATITUDE],
            new_state.attributes[ATTR_LONGITUDE],
        )
        distance_travelled = round(new_distance - old_distance, 1)

        # Check for tolerance
        if distance_travelled < self.tolerance * -1:
            direction_of_travel = "towards"
        elif distance_travelled > self.tolerance:
            direction_of_travel = "away_from"
        else:
            direction_of_travel = "stationary"

        # Update the proximity entity
        self.dist_to = round(dist_to_zone)
        self.dir_of_travel = direction_of_travel
        self.nearest = entity_name
        self.schedule_update_ha_state()
        _LOGGER.debug(
            "proximity.%s update entity: distance=%s: direction=%s: device=%s",
            self.friendly_name,
            round(dist_to_zone),
            direction_of_travel,
            entity_name,
        )

        _LOGGER.info("%s: proximity calculation complete", entity_name)
Example #37
0
 def distance(self, lat, lon):
     """Calculate distance from Home Assistant in meters."""
     return location.distance(self.latitude, self.longitude, lat, lon)
Example #38
0
    def _determine_interval(self) -> int:
        """Calculate new interval between two API fetch (in minutes)."""
        intervals = {"default": self._max_interval}
        for device in self._devices.values():
            # Max interval if no location
            if device.location is None:
                continue

            current_zone = run_callback_threadsafe(
                self.hass.loop,
                async_active_zone,
                self.hass,
                device.location[DEVICE_LOCATION_LATITUDE],
                device.location[DEVICE_LOCATION_LONGITUDE],
                device.location[DEVICE_LOCATION_HORIZONTAL_ACCURACY],
            ).result()

            # Max interval if in zone
            if current_zone is not None:
                continue

            zones = (
                self.hass.states.get(entity_id)
                for entity_id in sorted(self.hass.states.entity_ids("zone"))
            )

            distances = []
            for zone_state in zones:
                zone_state_lat = zone_state.attributes[DEVICE_LOCATION_LATITUDE]
                zone_state_long = zone_state.attributes[DEVICE_LOCATION_LONGITUDE]
                zone_distance = distance(
                    device.location[DEVICE_LOCATION_LATITUDE],
                    device.location[DEVICE_LOCATION_LONGITUDE],
                    zone_state_lat,
                    zone_state_long,
                )
                distances.append(round(zone_distance / 1000, 1))

            # Max interval if no zone
            if not distances:
                continue
            mindistance = min(distances)

            # Calculate out how long it would take for the device to drive
            # to the nearest zone at 120 km/h:
            interval = round(mindistance / 2, 0)

            # Never poll more than once per minute
            interval = max(interval, 1)

            if interval > 180:
                # Three hour drive?
                # This is far enough that they might be flying
                interval = self._max_interval

            if (
                device.battery_level is not None
                and device.battery_level <= 33
                and mindistance > 3
            ):
                # Low battery - let's check half as often
                interval = interval * 2

            intervals[device.name] = interval

        return max(
            int(min(intervals.items(), key=operator.itemgetter(1))[1]),
            self._max_interval,
        )
Example #39
0
    def _update_info(self, entity_id, old_state, new_state):
        if new_state is None:
            return

        with self._lock:
            # Get time device was last seen, which is the entity's last_seen
            # attribute, or if that doesn't exist, then last_updated from the
            # new state object. Make sure last_seen is timezone aware in UTC.
            # Note that dt_util.as_utc assumes naive datetime is in local
            # timezone.
            last_seen = new_state.attributes.get(ATTR_LAST_SEEN)
            if isinstance(last_seen, datetime):
                last_seen = dt_util.as_utc(last_seen)
            else:
                try:
                    last_seen = dt_util.utc_from_timestamp(float(last_seen))
                except (TypeError, ValueError):
                    last_seen = new_state.last_updated

            old_last_seen = self._entities[entity_id][SEEN]
            if old_last_seen and last_seen < old_last_seen:
                self._bad_entity(entity_id, 'last_seen went backwards')
                return

            # Try to get GPS and battery data.
            try:
                gps = (new_state.attributes[ATTR_LATITUDE],
                       new_state.attributes[ATTR_LONGITUDE])
            except KeyError:
                gps = None
            gps_accuracy = new_state.attributes.get(ATTR_GPS_ACCURACY)
            battery = new_state.attributes.get(
                ATTR_BATTERY, new_state.attributes.get(ATTR_BATTERY_LEVEL))
            charging = new_state.attributes.get(
                ATTR_BATTERY_CHARGING, new_state.attributes.get(ATTR_CHARGING))
            # Don't use location_name unless we have to.
            location_name = None

            # What type of tracker is this?
            if new_state.domain == BS_DOMAIN:
                source_type = SOURCE_TYPE_BINARY_SENSOR
            else:
                source_type = new_state.attributes.get(ATTR_SOURCE_TYPE)

            state = new_state.state

            if source_type == SOURCE_TYPE_GPS:
                # GPS coordinates and accuracy are required.
                if gps is None:
                    self._bad_entity(entity_id, 'missing gps attributes')
                    return
                if gps_accuracy is None:
                    self._bad_entity(entity_id,
                                     'missing gps_accuracy attribute')
                    return

                new_data = gps, gps_accuracy
                old_data = self._entities[entity_id][DATA]
                if old_data:
                    if last_seen == old_last_seen and new_data == old_data:
                        return
                    old_gps, old_acc = old_data
                self._good_entity(entity_id, last_seen, source_type, new_data)

                if (self._req_movement and old_data
                        and distance(gps[0], gps[1], old_gps[0],
                                     old_gps[1]) <= gps_accuracy + old_acc):
                    _LOGGER.debug('For {} skipping update from {}: '
                                  'not enough movement'.format(
                                      self._entity_id, entity_id))
                    return

            elif source_type in SOURCE_TYPE_NON_GPS:
                # Convert 'on'/'off' state of binary_sensor
                # to 'home'/'not_home'.
                if source_type == SOURCE_TYPE_BINARY_SENSOR:
                    if state == STATE_BINARY_SENSOR_HOME:
                        state = STATE_HOME
                    else:
                        state = STATE_NOT_HOME

                self._good_entity(entity_id, last_seen, source_type, state)

                if not self._use_non_gps_data(state):
                    return

                # Don't use new GPS data if it's not complete.
                if gps is None or gps_accuracy is None:
                    gps = gps_accuracy = None
                # Get current GPS data, if any, and determine if it is in
                # 'zone.home'.
                cur_state = self._hass.states.get(self._entity_id)
                try:
                    cur_lat = cur_state.attributes[ATTR_LATITUDE]
                    cur_lon = cur_state.attributes[ATTR_LONGITUDE]
                    cur_acc = cur_state.attributes[ATTR_GPS_ACCURACY]
                    cur_gps_is_home = (run_callback_threadsafe(
                        self._hass.loop, async_active_zone, self._hass,
                        cur_lat, cur_lon,
                        cur_acc).result().entity_id == ENTITY_ID_HOME)
                except (AttributeError, KeyError):
                    cur_gps_is_home = False

                # It's important, for this composite tracker, to avoid the
                # component level code's "stale processing." This can be done
                # one of two ways: 1) provide GPS data w/ source_type of gps,
                # or 2) provide a location_name (that will be used as the new
                # state.)

                # If router entity's state is 'home' and current GPS data from
                # composite entity is available and is in 'zone.home',
                # use it and make source_type gps.
                if state == STATE_HOME and cur_gps_is_home:
                    gps = cur_lat, cur_lon
                    gps_accuracy = cur_acc
                    source_type = SOURCE_TYPE_GPS
                # Otherwise, if new GPS data is valid (which is unlikely if
                # new state is not 'home'),
                # use it and make source_type gps.
                elif gps:
                    source_type = SOURCE_TYPE_GPS
                # Otherwise, if new state is 'home' and old state is not 'home'
                # and no GPS data, then use HA's configured Home location and
                # make source_type gps.
                elif (state == STATE_HOME and
                      (cur_state is None or cur_state.state != STATE_HOME)):
                    gps = (self._hass.config.latitude,
                           self._hass.config.longitude)
                    gps_accuracy = 0
                    source_type = SOURCE_TYPE_GPS
                # Otherwise, if new state is a valid zone AND is not 'not_home', look up the configured zone coordinates
                # and make source_type gps.
                elif (state != STATE_NOT_HOME
                      and (cur_state is None or cur_state.state != STATE_HOME)
                      and self._get_zone(state) != None):
                    gps = self._get_zone_coords(state)
                    gps_accuracy = 0
                    source_type = SOURCE_TYPE_GPS
                # Otherwise, don't use any GPS data, but set location_name to
                # new state.
                else:
                    location_name = state

            else:
                self._bad_entity(
                    entity_id,
                    'unsupported source_type: {}'.format(source_type))
                return

            # Is this newer info than last update?
            if self._prev_seen and last_seen <= self._prev_seen:
                _LOGGER.debug(
                    'For {} skipping update from {}: '
                    'last_seen not newer than previous update ({} <= {})'.
                    format(self._entity_id, entity_id, last_seen,
                           self._prev_seen))
                return

            _LOGGER.debug('Updating %s from %s', self._entity_id, entity_id)

            tzone = None
            if self._time_as in [TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]:
                tzname = None
                if gps:
                    # timezone_at will return a string or None.
                    tzname = self._tf.timezone_at(lng=gps[1], lat=gps[0])
                    # get_time_zone will return a tzinfo or None.
                    tzone = dt_util.get_time_zone(tzname)
                attrs = {ATTR_TIME_ZONE: tzname or STATE_UNKNOWN}
            else:
                attrs = {}

            attrs.update({
                ATTR_ENTITY_ID:
                tuple(entity_id
                      for entity_id, entity in self._entities.items()
                      if entity[ATTR_SOURCE_TYPE] is not None),
                ATTR_LAST_ENTITY_ID:
                entity_id,
                ATTR_LAST_SEEN:
                self._dt_attr_from_utc(nearest_second(last_seen), tzone)
            })
            if charging is not None:
                attrs[ATTR_BATTERY_CHARGING] = charging
            self._see(dev_id=self._dev_id,
                      location_name=location_name,
                      gps=gps,
                      gps_accuracy=gps_accuracy,
                      battery=battery,
                      attributes=attrs,
                      source_type=source_type)

            self._prev_seen = last_seen
Example #40
0
    async def async_update(self, dummy):
        """Update data."""

        self._feed = await self._hass.async_add_executor_job(
            feedparser.parse, self._url
        )
        if not self._feed:
            _LOGGER.debug("Failed to get feed data from %s", self._url)
            return

        if self._feed.bozo:
            _LOGGER.debug("Error parsing feed data from %s", self._url)
            return

        _LOGGER.debug("Feed url: %s data: %s", self._url, self._feed)

        if self._restart:
            self._restart = False
            self._event_time = self._convert_time(self._feed.entries[0]["published"])
            _LOGGER.debug("Start fresh after a restart")
            return

        try:
            for entry in reversed(self._feed.entries):

                event_msg = None
                event_capcode = None
                event_time = self._convert_time(entry.published)
                if event_time < self._event_time:
                    continue
                self._event_time = event_time

                # Fill data from feed
                event_msg = entry.message
                event_regioname = entry.regname
                event_regio = entry.regcode.lstrip("0")
                event_discipline = entry.dienst
                event_capcode = entry.code

                _LOGGER.debug(
                    "New P2000 event found: %s, at %s", event_msg, entry.published
                )

                # Check regio
                if "regcode" in entry:
                    if self._regiolist:
                        _LOGGER.debug("Filtering on Regio(s) %s", self._regiolist)
                        regiofound = False
                        for regio in self._regiolist:
                            _LOGGER.debug(
                                "Searching for regio %s in %s",
                                regio,
                                event_regio,
                            )
                            if event_regio == regio:
                                _LOGGER.debug("Regio matched")
                                regiofound = True
                                break
                            _LOGGER.debug("Regio mismatch, discarding")
                            continue
                        if not regiofound:
                            continue

                # Check discipline
                if "dienst" in entry:
                    if self._disciplines:
                        if self._disciplinelist:
                            _LOGGER.debug(
                                "Filtering on Disciplines(s) %s", self._disciplinelist
                            )
                            disciplinefound = False
                            for discipline in self._disciplinelist:
                                _LOGGER.debug(
                                    "Searching for discipline %s in %s",
                                    discipline,
                                    event_discipline,
                                )
                                if event_discipline == discipline:
                                    _LOGGER.debug("Discipline matched")
                                    disciplinefound = True
                                    break
                                _LOGGER.debug("Discipline mismatch, discarding")
                                continue
                            if not disciplinefound:
                                continue

                # Check radius or nolocation
                if "lat" in entry and entry.lat:
                    event_lat = float(entry.lat)
                    event_lon = float(entry.lon)
                    event_dist = distance(self._lat, self._lon, event_lat, event_lon)
                    event_dist = int(round(event_dist))
                    if self._radius:
                        _LOGGER.debug(
                            "Filtering on Radius %s, calculated distance %d m ",
                            self._radius,
                            event_dist,
                        )
                        if event_dist > self._radius:
                            event_msg = ""
                            _LOGGER.debug("Radius filter mismatch, discarding")
                            continue
                        _LOGGER.debug("Radius filter matched")
                else:
                    event_lat = 0.0
                    event_lon = 0.0
                    event_dist = 0
                    if not self._nolocation:
                        _LOGGER.debug("No location found, discarding")
                        continue

                # Check capcodes if defined
                if "code" in entry:
                    if self._capcodelist:
                        _LOGGER.debug("Filtering on Capcode(s) %s", self._capcodelist)
                        capfound = False
                        for capcode in self._capcodelist:
                            _LOGGER.debug(
                                "Searching for capcode %s in %s",
                                capcode.strip(),
                                event_capcode,
                            )
                            if event_capcode == capcode.strip():
                                _LOGGER.debug("Capcode filter matched")
                                capfound = True
                                break
                            _LOGGER.debug("Capcode filter mismatch, discarding")
                            continue
                        if not capfound:
                            continue

                if self._contains:
                    _LOGGER.debug("Filtering on Contains string %s", self._contains)
                    if event_msg.find(self._contains) != -1:
                        _LOGGER.debug("Contains string filter matched")
                    else:
                        _LOGGER.debug("Contains string filter mismatch, discarding")
                        continue

                if event_msg:
                    event = {}
                    event["msgtext"] = event_msg
                    event["latitude"] = event_lat
                    event["longitude"] = event_lon
                    event["distance"] = event_dist
                    event["msgtime"] = event_time
                    event["capcode"] = event_capcode
                    event["regio"] = event_regio
                    event["regioname"] = event_regioname
                    event["discipline"] = event_discipline
                    _LOGGER.debug("Event: %s", event)
                    self._data = event

            dispatcher_send(self._hass, DATA_UPDATED + CONF_NAME)

        except ValueError as err:
            _LOGGER.error("Error parsing feed data %s", err)
            self._data = None
Example #41
0
def in_zone(zone, latitude, longitude, radius=0):
    """ Test if given latitude, longitude is in given zone. """
    zone_dist = distance(latitude, longitude, zone.attributes[ATTR_LATITUDE],
                         zone.attributes[ATTR_LONGITUDE])

    return zone_dist - radius < zone.attributes[ATTR_RADIUS]
Example #42
0
 def distance(self, lat, lon):
     """Calculate distance from Home Assistant in meters."""
     return location.distance(self.latitude, self.longitude, lat, lon)
    def check_proximity_state_change(self, entity, old_state, new_state):
        # pylint: disable=too-many-branches,too-many-statements,too-many-locals
        """ Function to perform the proximity checking """
        entity_name = new_state.name
        devices_to_calculate = self.ignored_zones == []
        devices_have_coordinates = False
        devices_in_zone = ''

        zone_state = self.hass.states.get(self.proximity_zone)
        proximity_latitude = zone_state.attributes.get('latitude')
        proximity_longitude = zone_state.attributes.get('longitude')

        # check for devices in the monitored zone
        for device in self.proximity_devices:
            device_state = self.hass.states.get(device)

            if 'latitude' not in device_state.attributes:
                continue

            devices_have_coordinates = True

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if not zone.in_zone(ignored_zone_state, device_state_lat,
                                    device_state_lon):
                    devices_to_calculate = True

            # check the location of all devices
            if zone.in_zone(zone_state, device_state_lat, device_state_lon):
                device_friendly = device_state.name
                if devices_in_zone != '':
                    devices_in_zone = devices_in_zone + ', '
                devices_in_zone = devices_in_zone + device_friendly

        # at least one device is in the monitored zone so update the entity
        if devices_in_zone != '':
            self.dist_to = 0
            self.dir_of_travel = 'arrived'
            self.nearest = devices_in_zone
            self.update_ha_state()
            return

        # no-one to track so reset the entity
        if not devices_to_calculate or not devices_have_coordinates:
            self.dist_to = 'not set'
            self.dir_of_travel = 'not set'
            self.nearest = 'not set'
            self.update_ha_state()
            return

        # we can't check proximity because latitude and longitude don't exist
        if 'latitude' not in new_state.attributes:
            return

        # collect distances to the zone for all devices
        distances_to_zone = {}
        for device in self.proximity_devices:
            # ignore devices in an ignored zone
            device_state = self.hass.states.get(device)

            # ignore devices if proximity cannot be calculated
            if 'latitude' not in device_state.attributes:
                continue

            device_state_lat = device_state.attributes['latitude']
            device_state_lon = device_state.attributes['longitude']

            device_in_ignored_zone = False
            for ignored_zone in self.ignored_zones:
                ignored_zone_state = self.hass.states.get('zone.' +
                                                          ignored_zone)
                if zone.in_zone(ignored_zone_state, device_state_lat,
                                device_state_lon):
                    device_in_ignored_zone = True
                    continue
            if device_in_ignored_zone:
                continue

            # calculate the distance to the proximity zone
            dist_to_zone = distance(proximity_latitude, proximity_longitude,
                                    device_state_lat, device_state_lon)

            # add the device and distance to a dictionary
            distances_to_zone[device] = round(dist_to_zone / 1000, 1)

        # loop through each of the distances collected and work out the closest
        closest_device = ''
        dist_to_zone = 1000000

        for device in distances_to_zone:
            if distances_to_zone[device] < dist_to_zone:
                closest_device = device
                dist_to_zone = distances_to_zone[device]

        # if the closest device is one of the other devices
        if closest_device != entity:
            self.dist_to = round(distances_to_zone[closest_device])
            self.dir_of_travel = 'unknown'
            device_state = self.hass.states.get(closest_device)
            self.nearest = device_state.name
            self.update_ha_state()
            return

        # stop if we cannot calculate the direction of travel (i.e. we don't
        # have a previous state and a current LAT and LONG)
        if old_state is None or 'latitude' not in old_state.attributes:
            self.dist_to = round(distances_to_zone[entity])
            self.dir_of_travel = 'unknown'
            self.nearest = entity_name
            self.update_ha_state()
            return

        # reset the variables
        distance_travelled = 0

        # calculate the distance travelled
        old_distance = distance(proximity_latitude, proximity_longitude,
                                old_state.attributes['latitude'],
                                old_state.attributes['longitude'])
        new_distance = distance(proximity_latitude, proximity_longitude,
                                new_state.attributes['latitude'],
                                new_state.attributes['longitude'])
        distance_travelled = round(new_distance - old_distance, 1)

        # check for tolerance
        if distance_travelled < self.tolerance * -1:
            direction_of_travel = 'towards'
        elif distance_travelled > self.tolerance:
            direction_of_travel = 'away_from'
        else:
            direction_of_travel = 'stationary'

        # update the proximity entity
        self.dist_to = round(dist_to_zone)
        self.dir_of_travel = direction_of_travel
        self.nearest = entity_name
        self.update_ha_state()
        _LOGGER.debug(
            'proximity.%s update entity: distance=%s: direction=%s: '
            'device=%s', self.friendly_name, round(dist_to_zone),
            direction_of_travel, entity_name)

        _LOGGER.info('%s: proximity calculation complete', entity_name)
Example #44
0
def test_get_distance_to_same_place():
    """Test getting the distance."""
    meters = location_util.distance(COORDINATES_PARIS[0], COORDINATES_PARIS[1],
                                    COORDINATES_PARIS[0], COORDINATES_PARIS[1])

    assert meters == 0
Example #45
0
        if new_state.state in ignored_zones:
            _LOGGER.info('%s Device is in an ignored zone: %s', ENTITY_ID, 
				device)
            return

        """========================================================"""
        # check for latitude and longitude (on startup these values may not
		# exist)
        if not 'latitude' in new_state.attributes:
            _LOGGER.info('%s: not LAT or LONG current position cannot be '
				'calculated', entity_name)
            return

        # calculate the distance of the device from the monitored zone
        distance_from_zone = round(distance(proximity_latitude,
			proximity_longitude, new_state.attributes['latitude'],
			new_state.attributes['longitude']) / 1000, 1)
        _LOGGER.info('%s: distance from zone is: %s km', entity_name, 
			distance_from_zone)

        """========================================================"""
        # compare distance with other devices
        # default behaviour
        device_is_closest_to_zone = True

        for device in proximity_devices:
            # ignore the device we're working on
            if device == entity:
                continue

            # get the device state