Node-red tts.speak to multiple devices (without using groups)

I used to have a node-red automation that speaks to 2 of my Google Home speakers. It looked like this:
(see screenshot 1 below)
(note that Service was changed from “tts.google_say” or something like that, to “speak” during the course of the year, because … Year Of The Voice)

Now, it doesn’t speak, and I get the following error message:
(see screenshot 2 below)
Message for Googlability: “Call-service error. required key not provided @ data[‘media_player_entity_id’]”

I am now trying to get it to work again. This is what I have so far, which “works”.
(see screenshot 3 below)
The problem with this is that I have to specify the device ids inside the data, which entirely removes the use of the “Entity” options which allows me to select the entities from a list (like a good UI should).

Screenshots (because new user):

Can anyone assist me, please?

You might get more visibility on your post if you tagged with Third Party Integrations: Node-RED.

Your first configuration uses Mustache Templates, and you have a simple string in ‘Data’ using {{payload}} to pass the payload as a variable. This works, but really only where you are using a simple string expression like this.

In many services, the Data field needs to be an object - this is a dictionary that holds one or more parameters (fields), and the service call can find and pick out what it needs.

To build an object for the Data field, we need to use JSONata. Mustache templating does not work (well) for this task as it is difficult to build complex object correctly.

In JSONata, which you are using for your new configuration (see the J:) we enter a valid JSONata expression to build our data object.

JSONata uses the { } to create an object, and expects keys (fields) and values as in

{“key”: “value”} where value can be a string, number, Boolean, or array / object.

The neat thing about JSONata is that it evaluates everything in the expression as an expression. Literals, such as “message” are evaluated as themselves. You are using “mediaplayer.googlehome7252” as the field value - in JSONata this could be any expression including variables, so yes it is possible to pass a variable entity ID to the service call.

JSONata will take something like msg.payload (set to the string “mediaplayer.googlehome7252”) and you can then say

{“media_player”: payload}

and JSONata will evaluate ‘payload’ and replace it with the value in msg.payload.

Note that you do not need the leading msg.

Note too, that the entity_id contains a ‘.’, which requires a bit of care.

You can pass in to the service call node your entity_id using any msg.field - so msg.payload is fine, but also msg.myplayer would work, thus

{“media_player”: myplayer}

You can do more fancy things if you like, given that you have a list of media players to work with, but hopefully that should give you some ideas.

Thank you for the reply, Geoff!

I understand a bit better now, and managed to get it working like this:

{ "message": msg.payload, "media_player_entity_id":msg.devices }

with a “change” node just before it to set the devices.

What I don’t understand is why we can’t use the Device and Entity drop downs:

I tried adding Office Speaker to both, but it only played on the devices specified in msg.devices.

Tested it, and it doesn’t seem to be an issue (yet).

Have a great day!

A second look (always a good idea) and I now see that you are trying to use tts:speak.

The TTS (Text To Speech) integration is a Building Block integration, and we cannot use this directly.

https://www.home-assistant.io/integrations/tts

Strange as it may seem, this is one of the integrations that adds services, but the services can only be used by other integrations that set up the service calls correctly.

If you look a the basic tts:speak service call using the HA developer > services, you can see that it requires a target (area / device / entity) to base the service on, and a media player entity to do the actual broadcast on.

Since the integration cannot be used directly, the target cannot be filled in - I have no entities in the drop down list. I can add the ‘media_player_entity_id’ in the data call, but not the service target entity_id.

I have a user-based tts integration, in Text-to-speech (TTS): Say a TTS message with google_translate which comes from another integration, and this uses the building block tts services. This secondary integration offers just the one ‘say message with google translate’ service, and this service call does work as we would expect, since this integration becomes the necessary target entity for tts.
The usable service call can be set with an entity_id from the list of media_players, and therefore using this service call in Node-RED call:service means it works as you would expect.

The short answer - you can’t do what you are trying to do, and if it works, then it will only do so on the ‘media_device’ specified in the data field. You should use a user-integration based on TTS.

A learning exercise.

Hi Geoff

Please could you elaborate on this? I might have been using HA for about 2 years, but only on the surface, so I still consider myself a relative-noob.

On that note, I do have “Google Translate text-to-speech” integration installed, and according to the documentation I am doing it “correctly”:


However, I also saw in your link that tts.speak is a building block, so now I am still lost :see_no_evil:

I also tried adding 2 other “pallets” (google-translate-tts, and node-red-contrib-tts-ultimate), but neither seems to be solving my issue (in fact, I now need to find a way to uninstall node-red-contrib-tts-ultimate, but that’s a different battle)

Thanks for all your assistance so far! I really appreciate it.

1 Like

I can at least try…

I have been using HA for about 2 years, and I am finding the best way to learn about it is to try and answer questions on this forum - it forces me to try and better understand what is going on. If I can’t understand something I really can’t explain it!

So, a bit more exploring, I have found out lots of stuff I did not know, and I am (now) a little bit wiser.

HA provides services that can be called - actually it is the integrations that add entities and at the same time may also provide services that can be used to set / change some of their (the integration owned) entities.

Service calls therefore require the service to call, an entity (or an area containing associated entities, or a device with associated entities) and some data to pass to the call, which when called will do something to/with the entity (usually the state).

A brand new HA out of the box starts with no integrations - no entities, and therefore no service calls.

However, there are a few service calls that HA provides itself. These are the home assistant one (hassio, homeassistant, core etc), but there are also the ‘Building Block Integration’ integrations.

These ‘integrations’ are built into HA, but don’t have their own entities, just service calls. They are intended to be used by other integrations, which then build on top of them.

  • todo (lists)
  • tts (text to speech)
  • weather
  • vaccum
  • time
  • stt (speech to text)

and so on - now I look there are loads of them!

Each of these, in the documentation, has a note to the effect that it cannot be directly used

Building block integration

The light integration cannot be directly used. You cannot create your own light entities using this integration. This integration is a building block for other integrations to use, enabling them to create light entities for you.

There you have it. The building block integrations don’t have entities.

Interestingly, most of these “you can’t use” integrations have services but do not show up in the Developer > services test list. Which is quite sensible really - if you can’t use them why show them?

However - at least two do. The ‘todo’ list and the ‘tts’ show up.

So, here are two services that we can’t use. But we can. Just difficult different to use.

The reason that these ‘building block integrations’ are difficult to use, is that they do not work on entities. Just targets (and the target is supposed to be another integration that builds on them with its own entities).

If you want to use a service in Node-RED, a good place to start is to test it out in Home Assistant first!

So here is the tts:speak service - yes I can select it, but hey it is asking for a ‘target’!

So, what is the ‘target’? Turns out that this is another integration - in my case here I had added the Google Translate Text-to-speech integration, which adds just the one entity, which I can select here as the ‘target’.

So what I think is happening is, the building block integration text-to-speech provides basic services for doing the HA work, but requires another integration to do the talking. tts:speak takes the message, and passes it to a ‘media integration’, and this integration passes the audio to the ‘media-player’ entity that the ‘media integration’ knows about.

Here is the tts:speak, using the tts.google_en_co_uk integration as the target, and setting the ‘media_player.kitchen_display’ as the thing to speak on inside the data object.

The important things to note here are

  • you need two integrations to make this work. One for the target, and one for the media devices
  • the media device is set in the data object, and is passed to the target integration, and therefore you set the media here

How to use more than one media device? Use an array!

service: tts.speak
target:
  entity_id:
    - tts.google_en_co_uk
    - tts.google_uk_co_uk
data:
  cache: false
  media_player_entity_id: [media_player.bedroom_display,media_player.kitchen_display]
  message: This is my message folkes!

I have experimented and added two entities under the Google Translate - Text to Speech integration, and yes you can have multiple targets too (it just does not seem to work with both, just the last one in the list).

Here are my integrations - you can see I have the Google Cast (which is the one that produces the media_player entities) and the Google Translate text-to-speech integration, which has two entities. These entities are the targets for the tts:speak service call!

So back to Node-RED

Using the call service node requires the service domain and service to start with.
In this case, we are using tts: for the domain
If you use ‘speak’ then the entity must be the target, that is the Google Translate entity, and the media player (one or array) has to be in the data field

What happens here is, the service call is to the Building Block tts:speak, which must have the Google Translate entity as its target, and the media device(s) to play on go into the data field as shown.
The service call calls tts:speak, this calls google_uk_co_uk, and passes the (list of) media players, which then do the speaking.

This is why you need to add the media player in the data field, and if you want a list of them, use an array

{
   "message":"My name is hanna",
   "media_player_entity_id": [
       "media_player.bedroom_display",
       "media_player.kitchen_display"
   ]

But why can’t I have the media list in the entity list as I want?

Since we are in effect calling a service call to call a service that then calls another service, this is not going to fit into the call service node as you would expect. This is ‘service call forwarding’ (I don’t know if that is a technical term, I just made it up).

Now, the Google Translate text-to-speech integration adds its own service. This shows up as a new service

Text-to-speech (TTS): Say a TTS message with google_translate or tts:google_translate_say

in the list. What this service does is offer the ability to speak, but using the building block tts (so backward call service if you like).

Here is the same attempt at calling a speaking service - this time I am using the google_translate_say (which knows about and uses the tts:speak) and I can now enter the media players as the entities, in a list, as expected.

This works, but as you kindly pointed out, google_translate_say is now legacy, and we should use tts:speak, with the Google Translate entity as the target.

Conclusion:
Use tts.speak
Set up something like Google Translate (to get the target entity)
Call the service, using the Google Translate entity (target) as ‘entity’
Put the (list of) media players into the data object under media_player_entity_id

Well, I’m glad we managed to clear that one up, although I think the documentation on tts should be updated to stop saying it cannot be used directly!

4 Likes

Thank you for the in-depth info!