Skip to content

Commit e3cef96

Browse files
authored
Add option to use all states of an input entity (pnbruckner#28)
1 parent ebaff62 commit e3cef96

File tree

4 files changed

+55
-12
lines changed

4 files changed

+55
-12
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ __pycache__/
33
*.py[cod]
44
*$py.class
55

6+
.vscode/

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ device_tracker:
1515
entity_id:
1616
- device_tracker.platform1_me
1717
- device_tracker.platform2_me
18+
- binary_sensor.i_am_home
1819
```
1920
2021
## Installation
@@ -58,7 +59,7 @@ sudo apt install libatlas3-base
5859

5960
### `device_tracker` platform
6061

61-
- **entity_id**: Entity IDs of watched device tracker devices. Can be a single entity ID, a list of entity IDs, or a string containing multiple entity IDs separated by commas.
62+
- **entity_id**: Entity IDs of watched device tracker devices. Can be a single entity ID, a list of entity IDs, or a string containing multiple entity IDs separated by commas. Another option is to specify a dictionary with `entity` specifying the entity ID, and `all_states` specifying a boolean value that controls whether or not to use all states of the entity (rather than just the "Home" state, which is the default.)
6263
- **name**: Object ID (i.e., part of entity ID after the dot) of composite device. For example, `NAME` would result in an entity ID of `device_tracker.NAME`.
6364
- **require_movement** (*Optional*): `true` or `false`. Default is `false`. If `true`, will skip update from a GPS-based tracker if it has not moved. Specifically, if circle defined by new GPS coordinates and accuracy overlaps circle defined by previous GPS coordinates and accuracy then update will be ignored.
6465
- **time_as** (*Optional*): One of `utc`, `local`, `device_or_utc` or `device_or_local`. Default is `utc` which shows time attributes in UTC. `local` shows time attributes per HA's `time_zone` configuration. `device_or_utc` and `device_or_local` attempt to determine the time zone in which the device is located based on its GPS coordinates. The name of the time zone (or `unknown`) will be shown in a new attribute named `time_zone`. If the time zone can be determined, then time attributes will be shown in that time zone. If the time zone cannot be determined, then time attributes will be shown in UTC if `device_or_utc` is selected, or in HA's local time zone if `device_or_local` is selected.
@@ -67,7 +68,7 @@ sudo apt install libatlas3-base
6768

6869
Watched GPS-based devices must have, at a minimum, the following attributes: `latitude`, `longitude` and `gps_accuracy`. If they don't they will not be used.
6970

70-
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. 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'.
71+
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'.
7172

7273
If a watched device has a `last_seen` attribute, that will be used in the composite device. If not, then `last_updated` from the entity's state will be used instead.
7374

@@ -108,6 +109,9 @@ device_tracker:
108109
entity_id:
109110
- device_tracker.platform1_me
110111
- device_tracker.platform2_me
112+
- device_tracker.router_my_device
113+
- entity: binary_sensor.i_am_home
114+
all_states: true
111115
```
112116

113117
### Time zone examples

custom_components/composite/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Constants for Composite Integration."""
22
DOMAIN = "composite"
33

4+
CONF_ALL_STATES = "all_states"
5+
CONF_ENTITY = "entity"
46
CONF_REQ_MOVEMENT = "require_movement"
57
CONF_TIME_AS = "time_as"
68

custom_components/composite/device_tracker.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from homeassistant.components.device_tracker import (
1010
ATTR_BATTERY,
1111
ATTR_SOURCE_TYPE,
12+
DOMAIN as DT_DOMAIN,
1213
PLATFORM_SCHEMA,
1314
SOURCE_TYPE_BLUETOOTH,
1415
SOURCE_TYPE_BLUETOOTH_LE,
@@ -26,7 +27,6 @@
2627
ATTR_LONGITUDE,
2728
CONF_ENTITY_ID,
2829
CONF_NAME,
29-
EVENT_HOMEASSISTANT_START,
3030
STATE_HOME,
3131
STATE_NOT_HOME,
3232
STATE_ON,
@@ -40,14 +40,15 @@
4040
from homeassistant.util.location import distance
4141

4242
from .const import (
43+
CONF_ALL_STATES,
44+
CONF_ENTITY,
4345
CONF_REQ_MOVEMENT,
4446
CONF_TIME_AS,
4547
DOMAIN,
4648
TIME_AS_OPTS,
4749
TZ_DEVICE_LOCAL,
4850
TZ_DEVICE_UTC,
4951
TZ_LOCAL,
50-
TZ_UTC,
5152
)
5253

5354
_LOGGER = logging.getLogger(__name__)
@@ -60,6 +61,7 @@
6061
INACTIVE = "inactive"
6162
ACTIVE = "active"
6263
WARNED = "warned"
64+
USE_ALL_STATES = "use_all_states"
6365
STATUS = "status"
6466
SEEN = "seen"
6567
SOURCE_TYPE = ATTR_SOURCE_TYPE
@@ -75,10 +77,40 @@
7577
SOURCE_TYPE_ROUTER,
7678
)
7779

80+
81+
def _entities(entities):
82+
result = []
83+
for entity in entities:
84+
if isinstance(entity, dict):
85+
result.append(entity)
86+
else:
87+
result.append(
88+
{
89+
CONF_ENTITY: entity,
90+
CONF_ALL_STATES: False,
91+
}
92+
)
93+
return result
94+
95+
96+
ENTITIES = vol.All(
97+
cv.ensure_list,
98+
[
99+
vol.Any(
100+
{
101+
vol.Required(CONF_ENTITY): cv.entity_id,
102+
vol.Required(CONF_ALL_STATES): cv.boolean,
103+
},
104+
cv.entity_id,
105+
msg="Expected an entity ID",
106+
)
107+
],
108+
_entities,
109+
)
78110
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
79111
{
80112
vol.Required(CONF_NAME): cv.slugify,
81-
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
113+
vol.Required(CONF_ENTITY_ID): ENTITIES,
82114
vol.Optional(CONF_TIME_AS, default=TIME_AS_OPTS[0]): vol.In(TIME_AS_OPTS),
83115
vol.Optional(CONF_REQ_MOVEMENT, default=False): cv.boolean,
84116
}
@@ -107,25 +139,29 @@ def __init__(self, hass, config, see):
107139
self._see = see
108140
entities = config[CONF_ENTITY_ID]
109141
self._entities = {}
110-
for entity_id in entities:
142+
entity_ids = []
143+
for entity in entities:
144+
entity_id = entity[CONF_ENTITY]
111145
self._entities[entity_id] = {
146+
USE_ALL_STATES: entity[CONF_ALL_STATES],
112147
STATUS: INACTIVE,
113148
SEEN: None,
114149
SOURCE_TYPE: None,
115150
DATA: None,
116151
}
152+
entity_ids.append(entity_id)
117153
self._dev_id = config[CONF_NAME]
118-
self._entity_id = f"device_tracker.{self._dev_id}"
154+
self._entity_id = f"{DT_DOMAIN}.{self._dev_id}"
119155
self._time_as = config[CONF_TIME_AS]
120156
if self._time_as in [TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]:
121157
self._tf = hass.data[DOMAIN]
122158
self._req_movement = config[CONF_REQ_MOVEMENT]
123159
self._lock = threading.Lock()
124160
self._prev_seen = None
125161

126-
self._remove = track_state_change(hass, entities, self._update_info)
162+
self._remove = track_state_change(hass, entity_ids, self._update_info)
127163

128-
for entity_id in entities:
164+
for entity_id in entity_ids:
129165
self._update_info(entity_id, None, hass.states.get(entity_id))
130166

131167
def _bad_entity(self, entity_id, message):
@@ -153,8 +189,8 @@ def _good_entity(self, entity_id, seen, source_type, data):
153189
{STATUS: ACTIVE, SEEN: seen, SOURCE_TYPE: source_type, DATA: data}
154190
)
155191

156-
def _use_non_gps_data(self, state):
157-
if state == STATE_HOME:
192+
def _use_non_gps_data(self, entity_id, state):
193+
if state == STATE_HOME or self._entities[entity_id][USE_ALL_STATES]:
158194
return True
159195
entities = self._entities.values()
160196
if any(entity[SOURCE_TYPE] == SOURCE_TYPE_GPS for entity in entities):
@@ -262,7 +298,7 @@ def _update_info(self, entity_id, old_state, new_state):
262298

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

265-
if not self._use_non_gps_data(state):
301+
if not self._use_non_gps_data(entity_id, state):
266302
return
267303

268304
# Don't use new GPS data if it's not complete.

0 commit comments

Comments
 (0)