Skip to content

Commit 915aeb6

Browse files
authored
Accept various formats for last_seen/last_timestamp attribute (pnbruckner#67)
- naive Python datetime - aware Python datetime - dt_util.utc_from_timestamp(float(x)) - dt_util.parse_datetiem(x)
1 parent a78fece commit 915aeb6

File tree

4 files changed

+43
-16
lines changed

4 files changed

+43
-16
lines changed

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# <img src="https://brands.home-assistant.io/composite/icon.png" alt="Composite Device Tracker Platform" width="50" height="50"/> Composite Device Tracker
22

3-
This integration creates a composite `device_tracker` entity from one or more other entities. It will update whenever one of the watched entities updates, taking the `last_seen`, `last_timestamp` or`last_updated` (and possibly GPS and other) data from the changing entity. The result can be a more accurate and up-to-date device tracker if the "input" entities update irregularly.
3+
This integration creates a composite `device_tracker` entity from one or more other entities. It will update whenever one of the watched entities updates, taking the "last seen" (and possibly GPS and other) data from the changing entity. The result can be a more accurate and up-to-date device tracker if the "input" entities update irregularly.
44

55
It will also create a `sensor` entity that indicates the speed of the device.
66

@@ -94,10 +94,32 @@ composite:
9494
- **use_picture** (*Optional*): `true` or `false`. Default is `false`. If `true`, use the entity's picture for the composite. Can only be `true` for at most one of the entities. If `entity_picture` is used, then this option cannot be used.
9595

9696
## Watched device notes
97+
### Used states
9798

9899
For watched non-GPS-based devices, which states are used and whether any GPS data (if present) is used depends on several factors. E.g., if GPS-based devices are in use then the 'not_home'/'off' state of non-GPS-based devices will be ignored (unless `all_states` was specified as `true` for that entity.) If only non-GPS-based devices are in use, then the composite device will be 'home' if any of the watched devices are 'home'/'on', and will be 'not_home' only when _all_ the watched devices are 'not_home'/'off'.
99100

100-
If a watched device has a `last_seen` or `last_timestamp` attribute, that will be used in the composite device. If not, then `last_updated` from the entity's state will be used instead.
101+
### Last seen
102+
103+
If a watched device has a "last seen" attribute (i.e. `last_seen` or `last_timestamp`), that will be used in the composite device. If not, then `last_updated` from the entity's [state object](https://www.home-assistant.io/docs/configuration/state_object/) will be used instead.
104+
105+
The "last seen" attribute can be in any one of these formats:
106+
107+
Python type | description
108+
-|-
109+
aware `datetime` | In any time zone
110+
naive `datetime` | Assumed to be in the system's time zone (Settings -> System -> General)
111+
`float`, `int`, `str` | A POSIX timestamp (anything accepted by `homeassistant.util.dt.utc_from_timestamp(float(x))`
112+
`str` | A date & time, aware or naive (anything accepted by `homeassistant.util.dt.parse_datetime`)
113+
114+
* See [Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
115+
116+
Integrations known to provide a supported "last seen" attribute:
117+
118+
- Google Maps (`last_seen`, [built-in](https://www.home-assistant.io/integrations/google_maps/) or [enhanced custom](https://github.com/pnbruckner/ha-google-maps))
119+
- [Enhanced GPSLogger](https://github.com/pnbruckner/ha-gpslogger) (`last_seen`)
120+
- [iCould3](https://github.com/gcobb321/icloud3) (`last_timestamp`)
121+
122+
### Miscellaneous
101123

102124
If a watched device has a `battery_level` or `battery` attribute, that will be used to update the composite device's `battery_level` attribute. If it has a `battery_charging` or `charging` attribute, that will be used to udpate the composite device's `battery_charging` attribute.
103125

custom_components/composite/device_tracker.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -417,19 +417,24 @@ async def _entity_updated( # noqa: C901
417417
new_attrs = Attributes(new_state.attributes)
418418

419419
# Get time device was last seen, which is specified by one of the entity's
420-
# attributes defined by _LAST_SEEN_ATTRS, or if that doesn't exist, then
421-
# last_updated from the new state object.
420+
# attributes defined by _LAST_SEEN_ATTRS, as a datetime.
421+
422+
def get_last_seen() -> datetime | None:
423+
"""Get last_seen from one of the possible attributes."""
424+
if (raw_last_seen := new_attrs.get(_LAST_SEEN_ATTRS)) is None:
425+
return None
426+
if isinstance(raw_last_seen, datetime):
427+
return raw_last_seen
428+
with suppress(TypeError, ValueError):
429+
return dt_util.utc_from_timestamp(float(raw_last_seen))
430+
with suppress(TypeError):
431+
return dt_util.parse_datetime(raw_last_seen)
432+
return None
433+
422434
# Make sure last_seen is timezone aware in local timezone.
423435
# Note that dt_util.as_local assumes naive datetime is in local timezone.
424-
last_seen: datetime | str | None = new_attrs.get(_LAST_SEEN_ATTRS)
425-
if not isinstance(last_seen, datetime):
426-
try:
427-
last_seen = dt_util.utc_from_timestamp(
428-
float(last_seen) # type: ignore[arg-type]
429-
)
430-
except (TypeError, ValueError):
431-
last_seen = new_state.last_updated
432-
last_seen = dt_util.as_local(last_seen)
436+
# Use last_updated from the new state object if no valid "last seen" was found.
437+
last_seen = dt_util.as_local(get_last_seen() or new_state.last_updated)
433438

434439
old_last_seen = entity.seen
435440
if old_last_seen and last_seen < old_last_seen:

custom_components/composite/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"codeowners": ["@pnbruckner"],
55
"config_flow": true,
66
"dependencies": ["file_upload"],
7-
"documentation": "https://github.com/pnbruckner/ha-composite-tracker/blob/3.3.0/README.md",
7+
"documentation": "https://github.com/pnbruckner/ha-composite-tracker/blob/3.4.0b1/README.md",
88
"iot_class": "local_polling",
99
"issue_tracker": "https://github.com/pnbruckner/ha-composite-tracker/issues",
1010
"requirements": ["filetype==1.2.0"],
11-
"version": "3.3.0"
11+
"version": "3.4.0b1"
1212
}

info.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# <img src="https://brands.home-assistant.io/composite/icon.png" alt="Composite Device Tracker Platform" width="50" height="50"/> Composite Device Tracker
22

3-
This integration creates a composite `device_tracker` entity from one or more other entities. It will update whenever one of the watched entities updates, taking the `last_seen`, `last_timestamp` or`last_updated` (and possibly GPS and other) data from the changing entity. The result can be a more accurate and up-to-date device tracker if the "input" entities update irregularly.
3+
This integration creates a composite `device_tracker` entity from one or more other entities. It will update whenever one of the watched entities updates, taking the "last seen" (and possibly GPS and other) data from the changing entity. The result can be a more accurate and up-to-date device tracker if the "input" entities update irregularly.
44

55
It will also create a `sensor` entity that indicates the speed of the device.
66

0 commit comments

Comments
 (0)