Input_select/set_options also update the current state, too - what to do?

Hi all,

I’m trying to make a few automations, allowing the user to move music from one device, to another. For that, i’m using Spotcast (which allow me to me the current playback to another Google device - Yep, wish i had started with Sonos, but with plenty of Google devices, i can’t make the move to Sonos right not).

There’s many good posts about defining the options for a input_select-item, and i did make that part yesterday, with a Rest command. More details about that part, in this post:

However, i have problem - and i’m not sure what to do :slight_smile:

By defining the options for input_select.sound_target with api/services/input_select/set_options, the following automation is also triggered:

alias: Trigger Spotcast Sound Target Playback
trigger:
  platform: state
  entity_id: sensor.sound_target
action:
  service: spotcast.start
  data_template:
    device_name: "{{ states('sensor.sound_target') }}"

So, everytime the rest_command is executed, the sound-device changes, and that’s not good :slight_smile:

My current hack, is to disable the automation with:

alias: Update Sound Targets
initial_state: True 
trigger:
  - platform: homeassistant
    event: start
  - platform: time_pattern   
    minutes: '/30'
action:
  - service: automation.turn_off
    entity_id: automation.trigger_spotcast_sound_target_playback
  - delay:
      seconds: 30
  - service: rest_command.populate_input_select_from_sensor_chromecast_devices
  - delay:
      seconds: 5
  - service: automation.turn_on
    entity_id: automation.trigger_spotcast_sound_target_playback

Another hack could be to have a static value on the list, as the first element. “Pick one”, and ignore that value in the automation… However, it can’t be the only way to do it?! :smiley:

Do you guys have a good idea?

I love Node-RED, but i would like to make this within HA, if possible :slight_smile:


TL;DR:
Can i, somehow, use set_options for my input_select, but prevents the state to change? (Cause my automation is triggered, and the value is not longer the “correct one”)

Currently the state is updated to the first value on the sound_target-list (Eg. “Living room”), every time i use the set_options function.


My `rest_command` for set_options
url: "http://localhost:8123/api/services/input_select/set_options"
method: POST
headers:
  content-type: application/json
  authorization: !secret rest_command_token_ha
content_type: application/json
payload: >-
  {
    "entity_id": "input_select.sound_targets",
    "options": [{% set devices = states.sensor.chromecast_devices.attributes.devices_json | from_json %}
    {%- for device in devices -%}
      "{{ device.name }}"{% if not loop.last %}, {% endif %}
    {%- endfor %}]
  }

add a condition in the first auotmation to check the previous state and only do something if they aren’t equal

condition:
- condition: template
  value_template: "{{ trigger.from_state.state != trigger.to_state.state }}"

Hi petro

Great response-time. Huh. I couldn’t prepare a bit to ear, and a toilet visit before the first reply. Thanks :slight_smile:

Maybe it’s a good idea to explain each step.

Setup:

  • One input_select, which is updated with my rest_command
  • One automation, which defines the options to the input_select (triggered every 30 min.)
  • One automation, which is triggered every time the input_select changes.

The user (me) is using the input_select in the GUI, to select the input.

However, the rest_command are for some reason, also updating the state with set_options.

So adding a condition, won’t be a solution for this, cause the state will be locked, for both the automation, but also the user interaction, right? :frowning: :slight_smile: (And, if i enabling Google Home support in the future, too.)

I would like the input_select to keep the current state/value, instead of it’s changing every time i use the set_options API solution.

Yes, state changes on the whole state object trigger an event.

It will lock out state changes to the main state, ignoring attribute state changes.

For example, if a user updates the options, the state object changes causing a state change (because attributes updated). But the main state does not change. So the automation won’t fire.

It doesn’t work.

I’ve tried to implement the condition at multiple steps in the automation-flow, but it doesn’t work.

The state for my input_select.sound_targets is still updated, if i’m calling api/services/input_select/set_options with my rest_command.

And my sensor.sound_target is changed, too.

So, overall, the playback moves to the first device, in the payload from the rest_command. (Which is “Stue” aka. “Livingroom”).

condition:
- condition: template
  value_template: "{{ trigger.from_state.state != trigger.to_state.state }}"

Do you have another good idea, to how i can handle this?

I really hoped, that there was a “native logic” in HA, instead of going with a script-solution/Node-RED, or something else, which are not using the base logics and functions in HA :smiley:

The only thing i can think of, is to impement another rest_command before and after, which pickup the current state > update my options in the input_select > define the state, which is was, before the options was updated. And, delay the state trigger (within the automation, which triggers the spotcast.start-service), however, that’s pretty messy, a bit dump, and will result in a longer time for the overall solution to switch source :frowning:

The last thing could be a input_boolean, which controls if it’s OK to change the sensor-value. And, change the input_boolean, based on user input in the Lovelace GUI, maybe as a custom service with payload - but still… It just sounds to overkill, and still, not that “smart”. :smiley:

Hopefully someone has a better way to do it.

So I have a few quesitons because there is information you haven’t provided.

  1. What does the full state look like before you update using the rest command. When I say full state, I want to know the full state object. I.e. state.state and state.attributes.

  2. Post the rest command.

  3. Post the input_select.

  4. Post any automations based on the input_selection.

So, i saw your other thread. So you odn’t need to post 2, 3 or 4.

1 question though. Why aren’t you using the set options service call? What’s the point of the rest command? Are you trying to skirt around the dynamic list?-

You’re right. I’ve made a few changes on the way. Almost everything is present in the link, i provided in the first post, however, i’ve changed a few things today… :slight_smile:

And you’re right. I’m creating a dynamic list, based on spotcast’s Chromecast device-list.

Note:
I’ve made a new “fix”, which works a bit better. But i would love more feedback, to make it even better - or, allowing me to simplify it.

Or, at lease, to get even more knowledge about the “smartest way of doing things” (Not the “right way”, cause… as we know, not all humans, thinks the same :wink:).

I’m using:

  • 3 automations
  • 1 input_select
  • 1 sensor
  • 1 rest_command



Examples of states/values

Example of states, after HA has been restarted:


All good, as you can see, my sensor is not defined, cause no action has been made from user end.



Example where selection has been done on input_select, by a user:


All good, now with the state, both showing the current “target”.



And here’s the last one, just after rest_command has been executed (_and therefore, the select_options for input_select is updated):


It’s also working, as it should.



Code for automation and the rest_command

automation

Audio Update sensor.sound_target with input_select
alias: Audio Update sensor.sound_target with input_select
trigger:
  platform: state
  entity_id: input_select.sound_targets
condition:
- condition: template
  value_template: "{{ trigger.to_state.state != 'Vælg enhed'}}"
action:
  service: python_script.set_state
  data_template:
    entity_id: sensor.sound_target
    state: "{{ states('input_select.sound_targets') }}"
Audio Call Spotcast-service with sensor.sound_target
alias: Audio Call Spotcast-service with sensor.sound_target
trigger:
  platform: state
  entity_id: sensor.sound_target
action:
  service: spotcast.start
  data_template:
    device_name: "{{ states('sensor.sound_target') }}"
Audio Update input.sound_targets with rest_command*
alias: Audio Update input.sound_targets with rest_command
initial_state: True 
trigger:
  - platform: homeassistant
    event: start
  - platform: time_pattern   
    minutes: '/30'
action:
  - delay:
      seconds: 30
  - service: rest_command.populate_input_select_from_sensor_chromecast_devices

rest_command

populate_input_select_from_sensor_chromecast_devices.yaml
url: "http://localhost:{HAPORT}/api/services/input_select/set_options"
method: POST
headers:
  content-type: application/json
  authorization: !secret {SECRET_REFERENCE}
content_type: application/json
payload: >-
  {
    "entity_id": "input_select.sound_targets",
    "options": ["Vælg enhed", {% set devices = states.sensor.chromecast_devices.attributes.devices_json | from_json %}
    {%- for device in devices -%}
      "{{ device.name }}"{% if not loop.last %}, {% endif %}
    {%- endfor %}]
  }



And, it’s working, after i made the hack, with putting “Vælg enhede” (“Choose device”) in front of the other elements. And, checking if that element is select.

The select_options task all always defined the first option as the current state/selection, however, the user won’t use it - so everybody are happy.

"There must be a better way!" - @exetico 2019

:laughing:

Ok, another question. Why are you triggering this every 30 minutes? Why not trigger when sensor.chromecast_devices.attributes.devices_json changes?

And I see you’re using a python script to set the state. What’s that script look like?

I also feel like this is overly complicated… Is this a correct statement pertaining to your main goal:

You want to use an input select to move ‘playing music’ from one media player to another? When you select the user input, you want it to perform the change?

Well, I set up an easier approach to this for me to test. Turns out when you call the service ‘set options’ by default it selects the first option in the list. So you need to get around this. I personally can’t think of a way to do this without setting it all at once. This will be difficult though because what if the item no longer exists in the list?

So… I just made a python script that does what we want and you don’t need the rest_command or the sensor. Just 2 automations.

python script (update_options.py)

entity_id = data.get('entity_id')
options = data.get('options')
retain_state = data.get('retain_state', 'false')
retain_state = 'true' in retain_state.lower()

if entity_id is not None and options is not None:
    options = [ o.strip() for o in options.split(",") ]
    if entity_id.startswith('input_select') and entity_id.count('.') == 1:
        service_data = {'entity_id':entity_id, 'options':options}
        if retain_state:
            current = hass.states.get(entity_id)
            if current is not None and current.state in options:
                hass.states.set(entity_id, current.state, {'options':options})
            else:
                hass.services.call('input_select', 'set_options', service_data)
        else:
            hass.services.call('input_select', 'set_options', service_data)

Then your 2 automations…

automation 1

Update the sound targets when chromcast state changes

alias: Update Sound Targets
initial_state: True 
trigger:
- platform: state
  entity_id: sensor.chromecast_devices
action:
- service: python_script.update_options
  data_template:
    entity_id: input_select.sound_targets
    retain_state: "True"
    options: >
      {{ states.sensor.chromecast_devices.attributes.devices_json | from_json | map(attribute='name') | list | join(', ') }}

automation 2

Update based on input select changes

alias: Trigger Spotcast Sound Target Playback
trigger:
  platform: state
  entity_id: input_select.sound_targets
condition:
- condition: template
  value_template: "{{ trigger.from_state.state != trigger.to_state.state }}"
action:
  service: spotcast.start
  data_template:
    device_name: "{{ trigger.to_state.state }}"
1 Like

Thanks! :heart:

I saw a few python-scripts yesterday, however, many also mentioned the rest-way, and i just found it a bit easier to understand. However, this is way more simple - that’s for sure! And, it was also pretty dump, now that i’m pretty good in multiple other programming languages. I guess it was a nobrainer, after multiple hours wasted on fiddling (within HA’s documentation, Google Searches, and so on)

I’ve made a backup of the other solution, and moved to the one you provided. You’re 100 correct - it’s way more simple.

1 Like

I’m facing one problem.

After restarting HA, if i have an active playback, it’s moving the playback to the first device on the list (“Stue”). That’s a problem.

What do you think, is the best thing to do? :wink: :slight_smile:

what do you want it to do?

Prevent it from starting music, if HA is reloaded (cause that will initiate the set_options). And, playback starts, even if no music was playing. It’s also selecting “Stue” (which is the first on the list).

I’ve fixed if, by changing a few things. And, actually i like that “Vælg element” is the default option (“Choose device” in english).

Values provided to the Python-script:
Vælg enhed, {{ states.sensor.chromecast_devices.attributes.devices_json | from_json | map(attribute='name') | list | join(', ') }}

And i’ve changed the spotcast.start logics, both for moving the current playback, or start one, if nothing is ongoing. It also skips, if “Vælg enhed” is the current state:

alias: Trigger Spotcast Sound Target Playback
trigger:
  platform: state
  entity_id: input_select.sound_targets
condition:
  condition: and
  conditions:
    - condition: template
      value_template: "{{ trigger.from_state.state != trigger.to_state.state }}"
    - condition: template
      value_template: "{{ trigger.to_state.state != 'Vælg enhed'}}"
action:
  service: spotcast.start
  data_template: 
    device_name: "{{ trigger.to_state.state }}"
    uri: > 
      {% if is_state('media_player.spotify', 'idle') %}
      spotify:playlist:37i9dQZF1DWZdL27osEgj6
      {% endif %}
    random_song: >
      {% if is_state("media_player.spotify", "idle") %}true{% else %}false{% endif %}
    transfer_playback: >
      {% if is_state("media_player.spotify", "idle") %}false{% else %}true{% endif %}

:slight_smile:

Alright, fyi the random song templates can be shorted to

    random_song: >
      {{ is_state("media_player.spotify", "idle") }}
    transfer_playback: >
      {{ not is_state("media_player.spotify", "idle") }}

Yet another good word :smiley:
I actually tried something like tgat, but it didn’t go that well - so i just used the other formatting.

Thanks!