Associating phone sensors back to original device

Hey all,

I’m still in the process of trying to figure out my strategy for working with entities and developing scripts. I’m trying to develop a capability to get my hands dirty in many different areas.

At night, I want to say goodnight to Alexa and run a nighttime routine. Part of this routine is to check whether our phones are plugged in, and have Alexa remind us to plug them in.

Some notes:

  • Both phones are using the Mobile App integration, so is_charging manifests as a separate binary_sensor entity
  • Ideally, I’d use the name of the phone device_tracker in the Alexa announcement
  • Also, no need to check and warn about phones that aren’t home

I am not necessarily looking for quick and dirty way to achieve this. I know I can hardcode everything, and then change every single script I have whenever one of us upgrades their phone. I’m trying to learn and develop more complex patterns that allow for extensibility, reusability and minimal system-wide impacts when something changes, with the intent of applying similar strategies for more complex cases.

My instinct is to do the following (pseudo-code):

  • For each phone device
    – Filter on ones that are home
    – Filter on the ones that are not charging
    – Get the names of the ones that are left
    – Build a string to announce

The only unifying concept of a phone is at the device level, which I’m not sure is accessible from scripts (at least, I haven’t figured out a way to get this information). Assuming there is no way, how can I associate all the discrete sensors associated with a device with each other, so that I can cross-reference their attributes?

Thoughts on different approaches are appreciated.

If you use get_enteties node in node red then you can get all enteties that has GPS_accuracy.

In my case I have person, and device trackers in the list.
These can easily be filtered out.

But that is if node red is a solution for you…

Thanks for the quick reply.

I’ve read a bit about Node Red and I am open to it, but I’m not sure I quite understand how it solves this problem. How can it help me logically tie the device_tracker to the binary_sensor for is_charging (which doesn’t have gps_accuracy)?

If your device tracker is named with the same prefix then it’s just a matter of string operations to get device_tracker.paul to be sensor.paul_battery_level.

But perhaps your sensors are not named that way?

Sorry… or binary_sensor.paul…

OK now I understand, so you’re advocating the string manipulation approach. I can find all device_trackers, and then append "_is_charging" to get the binary_sensor.

Any thoughts about how I could define all sensors as attributes of a single entity, that way I don’t have to do the same string manipulation in every script? Reading in the forums, it may have been this way previously, but then it was changed to be all separate entities. I could definitely understand the desire to have separate entities, but retaining the legacy functionality would have been nice too.

No…
But you could make this a subflow. That way you add one node to any flow and the output will be the devices and/or battery charging states.
Kind of like how you make a function in programming.

Or… perhaps node red could expose a URL that you use a rest sensor to read.
Could probably work but not sure it will make anything better.

goodnight:
  alias: Goodnight
  sequence:
  - service: notify.alexa_media
    data_template:
      data:
        type: tts
      target: media_player.bedroom_echo
      message: >
        {% set warn_phones = namespace(entities=[]) %}
        {% for phone in expand('group.phones') %}
          {% set name = phone.entity_id.split('.')[1] %}
          {% if is_state('binary_sensor.' + name + '_is_charging', 'off') and
                is_state(phone.entity_id, 'home') %}
            {% set warn_phones.entities = warn_phones.entities + [phone] %}    
          {% endif %}
        {% endfor %}
        {% if warn_phones.entities | length > 0 %}
          By the way, 
          {%- for warn_phone in warn_phones.entities -%}
            {% if loop.first -%} your {% elif loop.last -%} and {% else -%}, {% endif -%} {{ warn_phone.name }}
          {% endfor -%}
          {% if warn_phones.entities | length == 1 -%} isn't {% else -%} aren't {% endif -%} charging. Don't forget to plug {% if warn_phones.entities | length == 1 -%} it {% else -%} them {% endif -%} in!
        {% endif %}

Surely there’s a better way…

Couple of things I hate about this solution:

  • I need to execute the notify.alexa_media with an empty string if there is nothing to report. I couldn’t figure out how to wrap the checks to include the entire service declaration within the sequence.
  • I can’t reuse the logic that pairs the device_tracker with the binary sensor
  • I can’t reuse the logic for turning a list into an English list (“one, two and three”) nor the logic for the pronouns / verbs. Functions would be perfectly suited for something like this.

I considered trying the Python Scripts integration but I stopped when I read this:

It is not possible to use Python imports with this integration. If you want to do more advanced scripts, you can take a look at AppDaemon or pyscript

So even if I built up a suite of Python helper scripts, I don’t think I would be able to invoke or reference them at all. I’m hoping I’m wrong and that I’m missing something greatly.

Any help is greatly appreciated.

well… Using a group means you must manually update the group.
With get entities it would find the phones automatically.

Also since you use node red, then you can use javascript in the function node and thus be able to create any conditions.

Or what is the reason you can’t use device tracker to binary sensor logic? Is the names not suitable?

I’m not worried about updating the group. That was my way of distinguishing phones so I wouldn’t have to hard-code them in the script. How would I use get_entities to distinguish phones from other trackers (tablets, phone wifi trackers to connect for router connectivity, etc.)?

It would be great to define functions that take a device_tracker (or maybe just the common root) and convert it to the binary_sensor so that I can use them in multiple templates/functions. My understanding is that the only way to do that is with a macro, but even that is only scoped to the current template.

Do you have a wifi tracker with gps?
Tablets, sure that could be an issue, but there might be some other setting or name way to distinguish them?

Continuing from your other thread. You should be using template sensors as your ‘subroutines’ that return values. You can create a template sensor that returns any value. These template sensors update based on what entities are in them. For example create at template that counts the number of phones that are charging. Then in your automation check the template sensor and see if it’s greater than zero, etc.

That’s an interesting idea, and I’ll consider it. Seems like I would have to create quite a few template sensors to do what I need, and even then they wouldn’t be resilient to change. Is there a way to tie all sensors related to a device back to a single entity? For example, if I create template sensors for binary_sensor.<phone>_battery_level and binary_sensor.<phone>_is_charging (and possibly multiple templates each to combine this data in each way I need to use it in a script), if one of us got a new phone and the device name changed, I would have to update every template/sensor/script that referenced it. I was hoping to tie these sensors back to a single entity, and then be able to define a group of phones that all could be acted on. Then only the group would have to be updated to add/remove/edit references.

Can the template sensor define multiple attributes that reflect states of other discrete binary_sensors? That way, I can have one entity that contains all information about a “phone”, and I know how to access its values in a predictable and straightforward way, instead of having to “join” two entities in every template/script where I need multiple pieces of data.

Yes, here’ s an example of a ‘people at home’ sensor I created for people I care about:

Here’s the group that I add/remove from:

people:
  entities:
  - person.a
  - person.b
  - person.c
  - person.d
  - person.e
  - input_boolean.company

and here’s the template sensor that has various information that I access:

  - platform: template
    sensors:
      people_at_home:
        unique_id: people_at_home
        friendly_name: People at Home
        value_template: >
          {%- set people = expand('group.people') %}
          {{ people | selectattr('state', 'in', ['home', 'on'] ) | list | count }}
        icon_template: >
          {%- set icons = ['account-off', 'account', 'account-multiple'] %}
          {%- set people = expand('group.people') %}
          {%- set cnt = people | selectattr('state', 'in', ['home', 'on'] ) | list | count %}
          {%- if cnt >= 0 %}
            mdi:{{ icons[cnt] if cnt in range(icons | count) else 'account-group' }}
          {%- else %}
            mdi:account-alert
          {%- endif %}
        attribute_templates:
          people: > 
            {%- set people = expand('group.people') | selectattr('state', 'eq', 'home') | map(attribute='name') | list %}
            {%- set company = expand('group.people') | selectattr('state', 'eq', 'on') | map(attribute='name') | list %}
            {%- set people = people + company %}
            {{ people }}
          and: >
            {%- set people = expand('group.people') | selectattr('state', 'eq', 'home') | map(attribute='name') | list %}
            {%- set company = expand('group.people') | selectattr('state', 'eq', 'on') | map(attribute='name') | list %}
            {%- set people = people + company %}
            {%- if people | count > 0 %}
              {{- [people[:-1] | join(', '), 'and', people[-1]] | join(' ') if people | count > 1 else people[0] }}
            {%- else %}unknown
            {%- endif %}
          or: >
            {%- set people = expand('group.people') | selectattr('state', 'eq', 'home') | map(attribute='name') | list %}
            {%- set company = expand('group.people') | selectattr('state', 'eq', 'on') | map(attribute='name') | list %}
            {%- set people = people + company %}
            {%- if people | count > 0 %}
              {{- [people[:-1] | join(', '), 'or', people[-1]] | join(' ') if people | count > 1 else people[0] }}
            {%- else %}unknown
            {%- endif %}
          count: >
            {%- set people = expand('group.people') | selectattr('state', 'eq', 'home') | map(attribute='name') | list %}
            {%- set company = expand('group.people') | selectattr('state', 'eq', 'on') | map(attribute='name') | list %}
            {%- set people = people + company %}
            {{ people | count }}

And the resulting sensor:

OK, now this is pretty cool. I’ll give it a shot and post an update. Thanks!

BTW, what do ‘and’ and ‘or’ represent in this example?

Oh it’s just a simple string that I can use in sentences. Basically “a, b or c are home” vs “a, b, or c used the door”.

BTW, is there any way to deference an entity_name? For instance, could I make an attribute the name of an entity_id, and then get its state value in a script?

I tried {{ states('{{ state_attr(''sensor.phone_name'', ''is_charging_name'') }}')}} and a bunch of other things but no luck. Is the {{ }} operator recursive?

{{ }} isn’t an operator. It tells the template that this line returns a value. The full line, not the singular item inside. {% %} tells the template this line does not return anything. You cannot put {{ }} inside {% %} or vice versa.

"{{ states(state_attr('sensor.phone_name', 'is_charging_name')) }}"