def _prev_seen(self, dev_id, last_seen): prev_seen, reported = self._dev_data.get(dev_id, (None, False)) if self._max_update_wait: now = dt_util.utcnow() most_recent_update = last_seen or prev_seen or self._started overdue = now - most_recent_update > self._max_update_wait if overdue and not reported and now - self._started > EVENT_DELAY: self._hass.bus.fire( EVENT_UPDATE_OVERDUE, {ATTR_ENTITY_ID: DT_ENTITY_ID_FORMAT.format(dev_id)}, ) reported = True elif not overdue and reported: self._hass.bus.fire( EVENT_UPDATE_RESTORED, { ATTR_ENTITY_ID: DT_ENTITY_ID_FORMAT.format(dev_id), ATTR_WAIT: str(last_seen - (prev_seen or self._started)).split( "." )[0], }, ) reported = False self._dev_data[dev_id] = last_seen or prev_seen, reported return prev_seen
async def test_matching_source_type(hass, mock_device_tracker_conf): """Test setting source type.""" dev_id = "paulus" entity_id = ENTITY_ID_FORMAT.format(dev_id) topic = "/location/paulus" source_type = SOURCE_TYPE_BLUETOOTH location = "work" hass.config.components = set(["mqtt", "zone"]) assert await async_setup_component( hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: { CONF_PLATFORM: "mqtt", "devices": { dev_id: topic }, "source_type": source_type, } }, ) async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() assert hass.states.get( entity_id).attributes["source_type"] == SOURCE_TYPE_BLUETOOTH
async def test_multi_level_wildcard_topic_not_matching( hass, mock_device_tracker_conf): """Test not matching multi level wildcard topic.""" dev_id = "paulus" entity_id = ENTITY_ID_FORMAT.format(dev_id) subscription = "/location/#" topic = "/somewhere/room/paulus" location = "work" hass.config.components = set(["mqtt", "zone"]) assert await async_setup_component( hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: { CONF_PLATFORM: "mqtt", "devices": { dev_id: subscription }, } }, ) async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() assert hass.states.get(entity_id) is None
async def test_not_matching_custom_payload_for_home_and_not_home( hass, mock_device_tracker_conf): """Test not matching payload does not set state to home or not_home.""" dev_id = "paulus" entity_id = ENTITY_ID_FORMAT.format(dev_id) topic = "/location/paulus" payload_home = "present" payload_not_home = "not present" payload_not_matching = "test" hass.config.components = set(["mqtt", "zone"]) assert await async_setup_component( hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: { CONF_PLATFORM: "mqtt", "devices": { dev_id: topic }, "payload_home": payload_home, "payload_not_home": payload_not_home, } }, ) async_fire_mqtt_message(hass, topic, payload_not_matching) await hass.async_block_till_done() assert hass.states.get(entity_id).state != STATE_HOME assert hass.states.get(entity_id).state != STATE_NOT_HOME
def __init__(self, hass, config, see): self._hass = hass self._see = see entities = config[CONF_ENTITY_ID] self._entities = {} for entity_id in entities: self._entities[entity_id] = { WARNED: False, SEEN: None, SOURCE_TYPE: None, DATA: None} self._dev_id = config[CONF_NAME] self._entity_id = ENTITY_ID_FORMAT.format(self._dev_id) self._time_as = config[CONF_TIME_AS] if self._time_as in [TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]: from timezonefinderL import TimezoneFinder self._tf = TimezoneFinder() self._req_movement = config[CONF_REQ_MOVEMENT] self._lock = threading.Lock() self._prev_seen = None self._init_complete = False self._remove = track_state_change( hass, entities, self._update_info) for entity_id in entities: self._update_info(entity_id, None, hass.states.get(entity_id)) def init_complete(event): self._init_complete = True hass.bus.listen_once(EVENT_HOMEASSISTANT_START, init_complete)
def __init__(self, hass, config, see): self._hass = hass self._see = see entities = config[CONF_ENTITY_ID] self._entities = {} for entity_id in entities: self._entities[entity_id] = { WARNED: False, SOURCE_TYPE: None, STATE: None } self._dev_id = config[CONF_NAME] self._entity_id = ENTITY_ID_FORMAT.format(self._dev_id) self._time_as = config[CONF_TIME_AS] if self._time_as in [TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]: from timezonefinderL import TimezoneFinder self._tf = TimezoneFinder() self._lock = threading.Lock() self._prev_seen = None self._remove = track_state_change(hass, entities, self._update_info) for entity_id in entities: self._update_info(entity_id, None, hass.states.get(entity_id), init=True)
def __init__(self, tracker_id, get_token_callback, get_tracker_callback, tracker): """Set up Georide entity.""" self._tracker_id = tracker_id self._get_token_callback = get_token_callback self._get_tracker_callback = get_tracker_callback self._name = tracker.tracker_name self._data = tracker or {} self.entity_id = ENTITY_ID_FORMAT.format(tracker_id)
def update_device(self, devicename): """Update the device_tracker entity.""" from pyicloud.exceptions import PyiCloudNoDevicesException # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = self.hass.states.get(ENTITY_ID_FORMAT.format(devicename)) if entity is None and devicename in self.seen_devices: return attrs = {} kwargs = {} if self.api is None: return try: for device in self.api.devices: if str(device) != str(self.devices[devicename]): continue status = device.status(DEVICESTATUSSET) _LOGGER.debug("Device Status is %s", status) dev_id = status["name"].replace(" ", "", 99) dev_id = slugify(dev_id) attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get( status["deviceStatus"], "error") attrs[ATTR_LOWPOWERMODE] = status["lowPowerMode"] attrs[ATTR_BATTERYSTATUS] = status["batteryStatus"] attrs[ATTR_ACCOUNTNAME] = self.accountname status = device.status(DEVICESTATUSSET) battery = status.get("batteryLevel", 0) * 100 location = status["location"] if location and location["horizontalAccuracy"]: horizontal_accuracy = int(location["horizontalAccuracy"]) if horizontal_accuracy < self._gps_accuracy_threshold: self.determine_interval( devicename, location["latitude"], location["longitude"], battery, ) interval = self._intervals.get(devicename, 1) attrs[ATTR_INTERVAL] = interval accuracy = location["horizontalAccuracy"] kwargs["dev_id"] = dev_id kwargs["host_name"] = status["name"] kwargs["gps"] = (location["latitude"], location["longitude"]) kwargs["battery"] = battery kwargs["gps_accuracy"] = accuracy kwargs[ATTR_ATTRIBUTES] = attrs self.see(**kwargs) self.seen_devices[devicename] = True except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found")
async def test_lights_turn_on_when_coming_home_after_sun_set(hass, scanner): """Test lights turn on when coming home after sun set.""" test_time = datetime(2017, 4, 5, 3, 2, 3, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=test_time): await common_light.async_turn_off(hass) assert await async_setup_component( hass, device_sun_light_trigger.DOMAIN, {device_sun_light_trigger.DOMAIN: {}} ) hass.states.async_set(DT_ENTITY_ID_FORMAT.format("device_2"), STATE_HOME) await hass.async_block_till_done() assert light.is_on(hass)
async def test_new_message(hass, mock_device_tracker_conf): """Test new message.""" dev_id = "paulus" entity_id = ENTITY_ID_FORMAT.format(dev_id) topic = "/location/paulus" location = "work" hass.config.components = set(["mqtt", "zone"]) assert await async_setup_component( hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: { CONF_PLATFORM: "mqtt", "devices": { dev_id: topic } } }, ) async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() assert hass.states.get(entity_id).state == location
async def test_lights_turn_on_when_coming_home_after_sun_set_person( hass, scanner): """Test lights turn on when coming home after sun set.""" device_1 = DT_ENTITY_ID_FORMAT.format("device_1") device_2 = DT_ENTITY_ID_FORMAT.format("device_2") test_time = datetime(2017, 4, 5, 3, 2, 3, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=test_time): await common_light.async_turn_off(hass) hass.states.async_set(device_1, STATE_NOT_HOME) hass.states.async_set(device_2, STATE_NOT_HOME) await hass.async_block_till_done() assert not light.is_on(hass) assert hass.states.get( device_tracker.ENTITY_ID_ALL_DEVICES).state == "not_home" assert hass.states.get(device_1).state == "not_home" assert hass.states.get(device_2).state == "not_home" assert await async_setup_component( hass, "person", { "person": [{ "id": "me", "name": "Me", "device_trackers": [device_1] }] }, ) await group.Group.async_create_group(hass, "person_me", ["person.me"]) assert await async_setup_component( hass, device_sun_light_trigger.DOMAIN, { device_sun_light_trigger.DOMAIN: { "device_group": "group.person_me" } }, ) assert not light.is_on(hass) assert hass.states.get(device_1).state == "not_home" assert hass.states.get(device_2).state == "not_home" assert hass.states.get("person.me").state == "not_home" # Unrelated device has no impact hass.states.async_set(device_2, STATE_HOME) await hass.async_block_till_done() assert not light.is_on(hass) assert hass.states.get(device_1).state == "not_home" assert hass.states.get(device_2).state == "home" assert hass.states.get("person.me").state == "not_home" # person home switches on hass.states.async_set(device_1, STATE_HOME) await hass.async_block_till_done() assert light.is_on(hass) assert hass.states.get(device_1).state == "home" assert hass.states.get(device_2).state == "home" assert hass.states.get("person.me").state == "home"
def __init__(self, dev_id, data=None): """Set up OwnTracks entity.""" self._dev_id = dev_id self._data = data or {} self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
def _update_member(self, member, dev_id): prev_seen, reported = self._dev_data.get(dev_id, (None, False)) loc = member.get('location') try: last_seen = _utc_from_ts(loc.get('timestamp')) except AttributeError: last_seen = None if self._max_update_wait: now = dt_util.utcnow() update = last_seen or prev_seen or self._started overdue = now - update > self._max_update_wait if overdue and not reported and now - self._started > EVENT_DELAY: self._hass.bus.fire( 'life360_update_overdue', {'entity_id': DT_ENTITY_ID_FORMAT.format(dev_id)}) reported = True elif not overdue and reported: self._hass.bus.fire( 'life360_update_restored', { 'entity_id': DT_ENTITY_ID_FORMAT.format(dev_id), 'wait': str(last_seen - (prev_seen or self._started)).split('.')[0] }) reported = False self._dev_data[dev_id] = last_seen or prev_seen, reported if not loc: err_msg = member['issues']['title'] if err_msg: if member['issues']['dialog']: err_msg += ': ' + member['issues']['dialog'] else: err_msg = 'Location information missing' self._err(dev_id, err_msg) return if last_seen and (not prev_seen or last_seen > prev_seen): lat = loc.get('latitude') lon = loc.get('longitude') gps_accuracy = loc.get('accuracy') try: lat = float(lat) lon = float(lon) # Life360 reports accuracy in feet, but Device Tracker expects # gps_accuracy in meters. gps_accuracy = round( convert(float(gps_accuracy), LENGTH_FEET, LENGTH_METERS)) except (TypeError, ValueError): self._err( dev_id, 'GPS data invalid: {}, {}, {}'.format( lat, lon, gps_accuracy)) return self._ok(dev_id) msg = 'Updating {}'.format(dev_id) if prev_seen: msg += '; Time since last update: {}'.format(last_seen - prev_seen) _LOGGER.debug(msg) if (self._max_gps_accuracy is not None and gps_accuracy > self._max_gps_accuracy): _LOGGER.warning( '%s: Ignoring update because expected GPS ' 'accuracy (%.0f) is not met: %.0f', dev_id, self._max_gps_accuracy, gps_accuracy) return place_name = loc.get('name') or None # Does user want location name to be shown as state? if SHOW_PLACES in self._show_as_state: loc_name = place_name # Make sure Home Place is always seen exactly as home, # which is the special device_tracker state for home. if loc_name and loc_name.lower() == self._home_place_name: loc_name = STATE_HOME else: loc_name = None # If a place name is given, then address will just be a copy of # it, so don't bother with address. Otherwise, piece address # lines together, depending on which are present. if place_name: address = None else: address1 = loc.get('address1') or None address2 = loc.get('address2') or None if address1 and address2: address = ', '.join([address1, address2]) else: address = address1 or address2 raw_speed = loc.get('speed') try: speed = float(raw_speed) * SPEED_FACTOR_MPH if self._hass.config.units.is_metric: speed = convert(speed, LENGTH_MILES, LENGTH_KILOMETERS) speed = max(0, round(speed)) except (TypeError, ValueError): speed = STATE_UNKNOWN driving = _bool_attr_from_int(loc.get('isDriving')) if (driving in (STATE_UNKNOWN, False) and self._driving_speed is not None and speed != STATE_UNKNOWN): driving = speed >= self._driving_speed moving = _bool_attr_from_int(loc.get('inTransit')) if self._time_as in [TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]: # timezone_at will return a string or None. tzname = self._tf.timezone_at(lng=lon, lat=lat) # get_time_zone will return a tzinfo or None. time_zone = dt_util.get_time_zone(tzname) attrs = {ATTR_TIME_ZONE: tzname or STATE_UNKNOWN} else: time_zone = None attrs = {} attrs.update({ ATTR_ADDRESS: address, ATTR_AT_LOC_SINCE: self._dt_attr_from_ts(loc.get('since'), time_zone), ATTR_BATTERY_CHARGING: _bool_attr_from_int(loc.get('charge')), ATTR_DRIVING: driving, ATTR_LAST_SEEN: self._dt_attr_from_utc(last_seen, time_zone), ATTR_MOVING: moving, ATTR_RAW_SPEED: raw_speed, ATTR_SPEED: speed, ATTR_WIFI_ON: _bool_attr_from_int(loc.get('wifiState')), }) # If we don't have a location name yet and user wants driving or # moving to be shown as state, and current location is not in a HA # zone, then update location name accordingly. if not loc_name: active_zone = run_callback_threadsafe(self._hass.loop, async_active_zone, self._hass, lat, lon, gps_accuracy).result() if not active_zone: if SHOW_DRIVING in self._show_as_state and driving is True: loc_name = SHOW_DRIVING.capitalize() elif SHOW_MOVING in self._show_as_state and moving is True: loc_name = SHOW_MOVING.capitalize() try: battery = int(float(loc.get('battery'))) except (TypeError, ValueError): battery = None self._see(dev_id=dev_id, location_name=loc_name, gps=(lat, lon), gps_accuracy=gps_accuracy, battery=battery, attributes=attrs, picture=member.get('avatar'))