Node Red Sonos Volume with two speakers

Migrating from HE where I am trying to replicate an ‘App’ to assist with my Sonos Volume. In my kitchen I have a Sonos Beam under the TV and two (2) in ceiling speakers controlled by a Sonos Connect.

There is existed an App which would accomplish the following (which I know is possible through NR).

When volume of Beam changes, change Volume of Connect but the Connect must always be 20% higher than the Beam except when the Beam = 0 then Connect also = 0.

Example: If I change the Beam to 25, then the connect would be 45. If I change the Beam to 50, the connect would be 70 and if I change the Beam to 0, the connect would also be 0.

Any ideas?

Two call service nodes one for each.
One function node before them that sends different message to each of them.

Where do you get the input from? Is it from the speaker itself?

I would think so and I would need to know/understand the function. Especially to calculate the offset.

It will be a lot easier for you if you give us the entity names so that we can help you with this.

My bad.

media_player.kitchen_tv_speaker ← is the main TV volume (Master)

media_player.kitchen_overhead ← is the overhead TV speaker (Follower)

Something like:

[{"id":"e1178c19.fd803","type":"delay","z":"f1ca98b1.b2e218","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":460,"y":220,"wires":[["21bc6a08.9dfc86"]]},{"id":"f4814901.6f0b88","type":"server-state-changed","z":"f1ca98b1.b2e218","name":"","server":"4bbca37b.1700ec","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"media_player.kitchen_tv_speaker","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"x":180,"y":220,"wires":[["e1178c19.fd803"]]},{"id":"21bc6a08.9dfc86","type":"function","z":"f1ca98b1.b2e218","name":"","func":"msg1 = {};\nmsg2 = {};\n\nmsg1.volume = msg.data.new_state.attributes.volume_level;\nmsg2.volume = msg.data.new_state.attributes.volume_level -0.2;\n\nreturn [msg1, msg2];","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":220,"wires":[["bd26bd03.ba53d","b0aaa5f6.b7ec98"],["93e3563e.bb1878","c2f66dd2.82b45"]]},{"id":"bd26bd03.ba53d","type":"debug","z":"f1ca98b1.b2e218","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":810,"y":180,"wires":[]},{"id":"93e3563e.bb1878","type":"debug","z":"f1ca98b1.b2e218","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":810,"y":240,"wires":[]},{"id":"c2f66dd2.82b45","type":"api-call-service","z":"f1ca98b1.b2e218","name":"","server":"4bbca37b.1700ec","version":1,"debugenabled":false,"service_domain":"media_player","service":"volume_set","entityId":"media_player.kitchen_overhead ","data":"{ \"volume_level\" : {{ volume}} }","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":870,"y":340,"wires":[[]]},{"id":"b0aaa5f6.b7ec98","type":"api-call-service","z":"f1ca98b1.b2e218","name":"","server":"4bbca37b.1700ec","version":1,"debugenabled":false,"service_domain":"media_player","service":"volume_set","entityId":"media_player.kitchen_tv_speaker","data":"{ \"volume_level\" : {{ volume}} }","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":870,"y":120,"wires":[[]]},{"id":"4bbca37b.1700ec","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

You are amazing. I am so excited to learn more Node Red as I have almost all my automations transferred over from HE.

I made a minor tweak to your formula as the volume of the overhead speaker needed to be louder.

[{"id":"e1178c19.fd803","type":"delay","z":"d4f9ac9d.d5ff08","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":320,"y":620,"wires":[["21bc6a08.9dfc86"]]},{"id":"f4814901.6f0b88","type":"server-state-changed","z":"d4f9ac9d.d5ff08","name":"Kitchen TV Vol","server":"4bbca37b.1700ec","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"media_player.kitchen_tv_speaker","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":620,"wires":[["e1178c19.fd803"]]},{"id":"21bc6a08.9dfc86","type":"function","z":"d4f9ac9d.d5ff08","name":"","func":"msg1 = {};\nmsg2 = {};\n\nmsg1.volume = msg.data.new_state.attributes.volume_level;\nmsg2.volume = msg.data.new_state.attributes.volume_level +0.2;\n\nreturn [msg1, msg2];","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":620,"wires":[["bd26bd03.ba53d","b0aaa5f6.b7ec98"],["93e3563e.bb1878","c2f66dd2.82b45"]]},{"id":"bd26bd03.ba53d","type":"debug","z":"d4f9ac9d.d5ff08","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":580,"wires":[]},{"id":"93e3563e.bb1878","type":"debug","z":"d4f9ac9d.d5ff08","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":640,"wires":[]},{"id":"c2f66dd2.82b45","type":"api-call-service","z":"d4f9ac9d.d5ff08","name":"Overhead Vol.","server":"4bbca37b.1700ec","version":3,"debugenabled":false,"service_domain":"media_player","service":"volume_set","entityId":"media_player.kitchen_overhead ","data":"{ \"volume_level\" : {{ volume}} }","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":680,"y":740,"wires":[[]]},{"id":"b0aaa5f6.b7ec98","type":"api-call-service","z":"d4f9ac9d.d5ff08","name":"TV Vol.","server":"4bbca37b.1700ec","version":3,"debugenabled":false,"service_domain":"media_player","service":"volume_set","entityId":"media_player.kitchen_tv_speaker","data":"{ \"volume_level\" : {{ volume}} }","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":650,"y":520,"wires":[[]]},{"id":"4bbca37b.1700ec","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

Two more questions:

  1. Is there a way through the same function to make it so that if media_player.kitchen_tv_speaker < 10 then media_player.kitchen_overhead = 0

  2. On this message board… how can I make my Nodes appear as a single line of copied text.

I cleaned up the code slightly to make it easier to read and fixed a few feature bugs.
Such as when TV volume is > 80%, that means the overhead should be more than 100% which would create an error.

msgTV = {};
msgOverhead = {};

var TV_volume = msg.data.new_state.attributes.volume_level;

if (TV_volume >= 0.1 && TV_volume < 0.8){
    msgTV.volume = TV_volume;
    msgOverhead.volume = TV_volume +0.2;
}else if (TV_volume >= 0.8 ){
    msgTV.volume = TV_volume;
    msgOverhead.volume = 1;
}else{
    msgTV.volume = TV_volume;
    msgOverhead.volume = 0;
}

return [msgTV, msgOverhead];
[{"id":"e1178c19.fd803","type":"delay","z":"f1ca98b1.b2e218","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":420,"y":300,"wires":[["21bc6a08.9dfc86"]]},{"id":"f4814901.6f0b88","type":"server-state-changed","z":"f1ca98b1.b2e218","name":"Kitchen TV Vol","server":"4bbca37b.1700ec","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"media_player.kitchen_tv_speaker","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"x":220,"y":300,"wires":[["e1178c19.fd803"]]},{"id":"21bc6a08.9dfc86","type":"function","z":"f1ca98b1.b2e218","name":"","func":"msgTV = {};\nmsgOverhead = {};\n\nvar TV_volume = msg.data.new_state.attributes.volume_level;\n\nif (TV_volume >= 0.1 && TV_volume < 0.8){\n    msgTV.volume = TV_volume;\n    msgOverhead.volume = TV_volume +0.2;\n}else if (TV_volume >= 0.8 ){\n    msgTV.volume = TV_volume;\n    msgOverhead.volume = 1;\n}else{\n    msgTV.volume = TV_volume;\n    msgOverhead.volume = 0;\n}\n\nreturn [msgTV, msgOverhead];","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":300,"wires":[["bd26bd03.ba53d","b0aaa5f6.b7ec98"],["93e3563e.bb1878","c2f66dd2.82b45"]]},{"id":"bd26bd03.ba53d","type":"debug","z":"f1ca98b1.b2e218","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":770,"y":260,"wires":[]},{"id":"93e3563e.bb1878","type":"debug","z":"f1ca98b1.b2e218","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":770,"y":320,"wires":[]},{"id":"c2f66dd2.82b45","type":"api-call-service","z":"f1ca98b1.b2e218","name":"Overhead Vol.","server":"4bbca37b.1700ec","version":3,"debugenabled":false,"service_domain":"media_player","service":"volume_set","entityId":"media_player.kitchen_overhead ","data":"{ \"volume_level\" : {{ volume}} }","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":780,"y":420,"wires":[[]]},{"id":"b0aaa5f6.b7ec98","type":"api-call-service","z":"f1ca98b1.b2e218","name":"TV Vol.","server":"4bbca37b.1700ec","version":3,"debugenabled":false,"service_domain":"media_player","service":"volume_set","entityId":"media_player.kitchen_tv_speaker","data":"{ \"volume_level\" : {{ volume}} }","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":750,"y":200,"wires":[[]]},{"id":"4bbca37b.1700ec","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

Place the code between two rows of three ` or select the code and press the “</>” button.

1 Like

This is amazing! Thank you so much for your help!

Ok. This morning I woke up and the Flow inexplicably did not work. I even reverted back to the original Node with no result.

I can control my Sonos from HA volume without issue. The flow simply does not respond.

Has the entity changed name?
Make sure the names of the entities are correct in the sequence.

The blue nodes

It might be related to the updated FLow with the updated formula. I reimported the original Flow and that one works.

I ran a debut and got the following message:

"TypeError: Cannot read property 'new_state' of undefined"

I believe the entity must be on in Home Assistant.
If it’s off then it does not work.
I just tried it with my google speaker and it didn’t react when it was “off” in HA, but as soon as it was “on” then it worked.

Not sure how to solve that…

It is a Sonos speaker, there is no entity switch for on/off.

It’s a media player.
And they are off in HA if they are not used by HA.


This is a google home mini and it’s on and responsive but HA reports it as off.

I wanted to update this flow because it does ALMOST exactly what it is intended to do with one exception.

If the user is using a remote control to adjust the volume, the adjustment is sent as many messages. (ie. going from 10 to 20 may result in a series of messages 10, 11, 12, 13 . . . 20). This causes the automation to run for each incremental adjustment causing the volume to not behave as expected.

Would there be a way to rewrite the function or node so that function or node would only make the adjustment to the second speaker 5 seconds after no further adjustments have been made to the first speaker.. So if a user uses the remote to adjust from 10 to 20, the second speaker would adjust 5 seconds after the speaker no longer adjusted.

A while back, this group helped me create a function that would adjust the volume of speakers within a group proportionately to prevent the sound from being too loud or too soft.

This function no longer works and simply adjusts the volume of one speaker to MATCH the other. Any idea why?


// Initialize objects with volume properties
var msgTV = { volume: 0.0 };
var msgProjector = { volume: 0.0 };
var msgPool = { volume: 0.0 };

var TV_volume = msg.data.new_state.attributes.volume_level;

if (TV_volume >= 0.15 && TV_volume < 0.8) {
    msgTV.volume = TV_volume;
    msgProjector.volume = TV_volume *0.5;
} else if (TV_volume >= 0.8) {
    msgTV.volume = TV_volume;
    msgProjector.volume = TV_volume -0.4;
} else {
    msgTV.volume = TV_volume;
    msgProjector.volume = 0.0;
    msgPool.volume = 0.0;
}

return [msgTV, msgProjector, msgPool];

Anyone have any thoughts on why this code no longer adjusts the volume correctly? The volume adjusts without setting it to .5 of the main volume.

// Initialize objects with volume properties
var msgTV = { volume: 0.0 };
var msgProjector = { volume: 0.0 };
var msgPool = { volume: 0.0 };

var TV_volume = msg.data.new_state.attributes.volume_level;

if (TV_volume >= 0.15 && TV_volume < 0.8) {
    msgTV.volume = TV_volume;
    msgProjector.volume = TV_volume *0.5;
} else if (TV_volume >= 0.8) {
    msgTV.volume = TV_volume;
    msgProjector.volume = TV_volume -0.4;
} else {
    msgTV.volume = TV_volume;
    msgProjector.volume = 0.0;
    msgPool.volume = 0.0;
}

return [msgTV, msgProjector, msgPool];