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()
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)
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]
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
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
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
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
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')
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
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')
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
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)) )
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)
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) )
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()
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)
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, ), )
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)
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
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
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()
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
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()
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...")
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)
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')
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)
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()
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] + "®ion=" + 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_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)
def distance(self, lat, lon): """Calculate distance from Home Assistant in meters.""" return location.distance(self.latitude, self.longitude, lat, lon)
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, )
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
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
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]
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)
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
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