Custom assist sentence/intent to use the address attribute of device_tracker

Hi experts,

All my persons in my house have device_trackers with a filled attribute named address. The default sentence/intent can only handle persons and is only looking into the state of the entity. So when I ask for the location of someone, I only get the „at home“ / „not at home“ response. How can i expand the system to respond with the address if available? I think i need a modified sentence, intent and response. But as I am no developer, I am a bit lost.

Regards
Lukas

You could set up a custom intent and response but I would consider the easier route of setting up an automation with the custom sentence as the trigger like this for example:

triggers:
  - trigger: conversation
    command:
      - where is bob
      - what is bob's location
      - etc etc
conditions: []
actions:
  - action: assist_satellite.announce
    metadata: {}
    data:
      message: >-
        Bob can currently be found at {{ state_attr('person.bob', 'named_address') }}  ## GUESSED ATTRIBUTE - CHANGE TO YOUR OWN ##
    target:
      entity_id: assist_satellite.home_assistant_voice  ## CHANGE TO YOUR OWN ENTITY ID ##
1 Like

You don’t have to define everything in the intent - you could create a template sensor to manipulate the address attribute into a format of your liking. For example:

Custom sentence:

language: "en"
intents:
  CustomWhatTime:
    data:
      - sentences:
          - "(What | What's | What is | Tell me) [the] time [is it]"

Intent:

  CustomWhatTime: 
    action: 
      - service: script.willow_tts_response
        data:
          tts_sentence: It's {{ states('sensor.speaking_clock') }}

In this case sensor.speaking_clock is is a rather unwieldy template that converts now() into colloquial phrases like “half past five”. It makes the intents easier to manage - and of course you can use the same sensor in different sentences.

(script.willow_tts_response just takes tts_sentence and speaks it on a Sonos speaker in whichever room I’m in.)

Hi,

In both ideas, i need a tts output, how can I modify the idea to also work for any kind of conversation, independent of the final device. So it should also work for a chat. Is there a replacement service call, to the assist_satellite.announce?

This maybe a stupid question but having created something like this to set my thermostat temperatures (as set temp does not appear to be a supported intent at present) I failed to fully grasp why some word are bracketed and some not.

Could not really find any documentation to fully explain it and although I managed to create what I needed and it works I would love to expand on this and understand the above format a little better.

Clearly the starting brackets denote I can start the sentence with any one of those words, but why for example is ‘the’ in square brackets yet ‘time’ is not in any bracket, does that suggest that ‘time’ is a required word for example?

Any pointers or better still links to relevant info much appreciated.

Thought I got it done via blueprint, but the trigger itself seems npt to be able to handle the variables. Any idea?


blueprint:
  name: Wo ist
  description: Nutzt die Adress Information eines Device Trackers um im Assist die Position wieder zu geben
  domain: automation
  input:
    device_tracker:
      name: device_tracker
      description: Der Device Tracker der Person
      selector:
        entity:
          filter:
            - domain: device_tracker
variables:
    device_tracker: !input device_tracker
    nick_of_person: '{{ states[device_tracker].attributes.nickname }}'
    full_name_of_person: '{{ states[device_tracker].attributes.full_name }}'
    address: '{{ states[device_tracker].attributes.address }}'
triggers:
  - trigger: conversation
    command: 'Wo ist [denn] ({{ nick_of_person }} | {{ full_name_of_person }}) [gerade]'
conditions: []
actions:
  - set_conversation_response: >-
      {{ nick_of_person }} ist gerade bei {{ address }}

Docs are here:

1 Like

Many thanks :+1:t2:

Changed blueprint to use trigger_variables also not working, as it seems, that the command trigger is nit able to use even them


blueprint:
  name: Wo ist
  description: Nutzt die Adress Information eines Device Trackers um im Assist die Position wieder zu geben
  domain: automation
  input:
    device_tracker:
      name: Device Tracker
      description: Der Device Tracker der Person
      selector:
        entity:
          filter:
            - domain: device_tracker
    nick_of_person:
        name: Nick Name
        description: kurzer Rufname
    full_name_of_person:
        name: Name
        description: Vor und Zuname
trigger_variables:
    nick_of_person: !input nick_of_person
    full_name_of_person: !input full_name_of_person
variables:
    device_tracker: !input device_tracker
    address: '{{ states[device_tracker].attributes.address }}'
triggers:
  - trigger: conversation
    command: 'Wo ist [denn] ({{ nick_of_person }} | {{ full_name_of_person }}) [gerade]'
conditions: []
actions:
  - set_conversation_response: >-
      {{ nick_of_person }} ist gerade bei {{ address }}

So as all of this is not working, i found the only way, which seems possible with the current version of home assistant.

  • New intents do not work, as you cant add new intent responses right now
  • custom intent scripts cant map the selected device to the response text and you would need to query by name alias and so on, which is no way to manage that.
  • automation or blueprints is also not possible, as the conversation trigger cant handle jinja2 or variables
  • dirty hack with copying the right response codes into the container, also no option, as these files are only compiled there and not as yaml config

So the way to go is to replace/extend the existing intent HassGetState with your definitions.


language: de
intents:
  HassGetState:
    data:
      # https://www.home-assistant.io/integrations/person/
      - sentences:
          - "wo (ist|befindet sich) <name>"
          - "wo hält sich <name> auf"
        response: address_wo
        requires_context:
          domain: device_tracker
        slots:
          domain: device_tracker

      - sentences:
          - "(ist|befindet sich) <name> <im_bereich>"
          - "hält sich <name> <im_bereich> auf"
        response: address_einzeln_janein
        requires_context:
          domain: device_tracker
        slots:
          domain: device_tracker

      - sentences:
          - "(ist|befindet sich) [irgend]jemand <im_bereich>"
          - "hält sich [irgend]jemand <im_bereich> auf"
        response: address_irgendeins
        slots:
          domain: device_tracker

      - sentences:
          - "([ist|befindet sich] jeder|[sind|befinden sich] alle) <im_bereich>"
          - "halten sich alle <im_bereich> auf"
        response: address_alle
        slots:
          domain: device_tracker

      - sentences:
          - "wer (ist|befindet sich) <im_bereich>"
          - "wer hält sich <im_bereich> auf"
        response: address_wer
        slots:
          domain: device_tracker

      - sentences:
          - "wie viele [(Leute|Personen) ](sind|befinden sich) <im_bereich>"
          - "wie viele [(Leute|Personen) ]halten sich <im_bereich> auf"
        response: address_wie_viele
        slots:
          domain: device_tracker

Place this file inside the custom_sentances folder (under the right language code)
Also create the replacing response file there:

language: de
responses:
  intents:
    HassGetState:
      address_einzeln: |
        {{ slots.name | capitalize }} ist {{ state.state_with_unit | replace(".", ",") if state.state|float(state.state) is number else state.state_with_unit }}

      address_einzeln_janein: |
        {% if query.matched %}
          Ja
        {% else %}
          Nein
        {% endif %}

      address_irgendeins: |
        {% if query.matched %}
          {% set match = query.matched | map(attribute="nickname") | sort | list %}
          {% if match | length > 4 %}
            Ja, {{ match[:3] | join(", ") }} und {{ (match | length - 3) }} weitere
          {%- else -%}
            Ja,
            {% for name in match -%}
              {% if not loop.first and not loop.last %}, {% elif loop.last and not loop.first %} und {% endif -%}
              {{ name }}
            {%- endfor -%}
          {% endif %}
        {% else %}
          Nein
        {% endif %}

      address_alle: |
        {% if not query.unmatched: %}
          Ja
        {% else %}
          {% set no_match = query.unmatched | map(attribute="nickname") | sort | list %}
          {% if no_match | length > 4 %}
            Nein, {{ no_match[:3] | join(", ") }} und {{ (no_match | length - 3) }} weitere sind es nicht
          {%- else -%}
            Nein,
            {% for name in no_match -%}
              {% if not loop.first and not loop.last %}, {% elif loop.last and not loop.first %} und {% endif -%}
              {{ name }}
            {%- endfor %} {% if no_match | length == 1 %}ist{% else %}sind{% endif %} es nicht
          {% endif %}
        {% endif %}

      address_welches: |
        {% if not query.matched %}
          Keins
        {% else: %}
          {% set match = query.matched | map(attribute="nickname") | sort | list %}
          {% if match | length > 4 %}
            {{ match[:3] | join(", ") }} und {{ (match | length - 3) }} weitere
          {% else %}
            {%- for name in match -%}
              {% if not loop.first and not loop.last %}, {% elif loop.last and not loop.first %} und {% endif -%}
              {{ name }}
            {%- endfor -%}
          {% endif %}
        {% endif %}

      address_wie_viele: |
        {{ query.matched | length }}

      address_wo: |
        {% if state.state == "not_home" %}
          {{ slots.name | capitalize }} ist gerade bei {{ state.attributes.get('address') }}.
        {% else %}
          {{ slots.name | capitalize }} ist im Bereich {{ state.state }}. 
          Das ist bei {{ state.attributes.get('address') }}.
        {% endif %}

      address_wer: |
        {% if not query.matched %}
          Niemand
        {% else: %}
          {% set match = query.matched | map(attribute="nickname") | sort | list %}
          {% if match | length > 4 %}
            {{ match[:3] | join(", ") }} und {{ (match | length - 3) }} weitere
          {% else %}
            {%- for name in match -%}
              {% if not loop.first and not loop.last %}, {% elif loop.last and not loop.first %} und {% endif -%}
              {{ name }}
            {%- endfor -%}
          {% endif %}
        {% endif %}

Now just expose the device_trackers of your choice :slight_smile: and all is working fine as expected.