Correct way to call a script and return text to Home Assistant Voice / Assist

So i’m trying to create a custom action based on a query to the voice assistant.

I want to be able to turn on a media player (if off) and switch it to a specified source.

I have the following in a yaml file in custom_sentences/en:

CustomMediaSetSource:
    data:
      - sentences:
        - Watch [the] {mediaSource}
        - Set [Source] [to] {mediaSource}
        - Play [the] {mediaSource}
        - Change (TV|Stereo) [to] {mediaSource}
lists:
  mediaSource:
    values:
      - "PS5"
      - in: "Playstation"
        out: "PS5"
      - in: "Playstation 5"
        out: "PS5"
      - "Switch"
      - in: "Switch 2"
        out: "Switch"
      - "AppleTV"
      - "XBox"

(so the utterance “Watch the AppleTV” should turn on the media player and switch the source to “AppleTV”)

I have an intent:

CustomMediaSetSource:
  action:
    - action: script.tv_turn_on_and_set_source
      data_template:
        source: "{{ mediaSource }}"
  speech:
    text: "{{ action_response | pprint }}"

and the script is:

sequence:
  - variables:
      changed_on: false
      source_changed: false
      response:
        text: ""
  - if:
      - condition: not
        conditions:
          - condition: state
            entity_id: input_boolean.tv_stereo_power
            state: "on"
    then:
      - variables:
          changed_on: true
          response[text]: Power On
      - action: input_boolean.turn_on
        metadata: {}
        data: {}
        target:
          entity_id: input_boolean.tv_stereo_power
      - delay:
          hours: 0
          minutes: 0
          seconds: 5
          milliseconds: 0
  - if:
      - condition: not
        conditions:
          - condition: template
            value_template: >-
              {{ state_attr('media_player.marantz_cinema_60', 'source') ==
              source }}
    then:
      - variables:
          source_changed: true
          response[text]: >
            {{ response[text] }} {%- if (response[text] | length) > 0 %} and
            changed {%- else -%} 
              change
            {%- endif %} source to {{ source }}
      - action: media_player.select_source
        metadata: {}
        data:
          source: "{{ source }}"
        target:
          entity_id: media_player.marantz_cinema_60
  - if:
      - condition: template
        value_template: "{{(response[text] | length) == 0}}"
    then:
      - variables:
          response[text]: The TV is already on and set to {{ source }}
  - stop: Complete
    response_variable: response
alias: TV - Turn on and set source
description: ""
variables:
  source:
    name: Source to change to

Looking at traces and testing - the script fires fine, but I want to return text from it for the assistant to actually “say” (so it can tell me what happened)

Using the set_conversation_response: command inside the script itself generates the following error:

TypeError: can't access property "speech", a.data.intent_output.response.speech.plain is undefined
isToolResult (src/components/ha-assist-chat.ts:547:12)
event.type.startsWith (src/components/ha-assist-chat.ts:433:33)

So the current version sets response.text to what it should read out, and I thought that via script response variables I could pass this through to the intent_script call via response_variable but i can’t seem to be able to set anything.

Does anyone know if this approach will work? Am I just stuck doing this as an automation? (which has the hassle of needing to use wildcard matches which seem very greedy)

Pass the room name to the script, use it to select a satellite and use the announce action or tts.speak. the alternative in the gui does not necessarily require a wildcard, just specify all possible phrase options.
You can also support my request related to improving the experience when working with voice automations via the interface

I think the approach is fine, but variables in an intent script are all local, so if you want to pass your action_response to a script, then the script would have to have a field defined for it. The action in the intent script would be something like:

    - action: script.your_script
      data:
        <fieldname>: {{ action_response }}

Edit: What does the prettyprint filter do? I mean, I know what it does, but I’ve never seen it used in HA before. :grin:

I need the response from the script to the intent_script - will this data thing work? (it looks like it’ll take some previous response and put it into the data/variables for the script)

I’m not sure if the defalt template expansion does it, but it’s supposed to nicely format the response (I’m just trying to debug stuff for the moment)

I just spent way too long trying to figure this out, and got it working.

The response part in the script looks basically the same in my script, but for completeness, I’m ending with these two steps:

  - variables:
      response:
        text: Don't let the bed bugs bite.
  - stop: Return response
    response_variable: response

(of course the text will contain templates or be set like in your script, this is just for the proof of concept)

The missing piece was the actions in the intent configuration. Instead of just calling the script, it too needs a response_variable and stop action. That results in the ‘action_response’ variable being available in the speech template.

Bedtime:
  action:
    - action: script.good_night
      response_variable: response
    - stop: ""
      response_variable: response
  speech:
    text: "Test: {{ action_response.text }}"

Hope that helps!

Thank you so much! this works perfectly!

Now to modify all my stuff to use scripts!