Sonos Plus - Questions

Hey @hklages

Loving your Sonos nodes!

I have a few questions :

  1. How can I change the shuffle and repeat options for a player?

  2. When you use your nodes, do you end up querying Sonos every second for every player to get status of things like volume, mute state, playstate (playing/paused)?

  3. I am querying the Sonos controller every second for playlist information i.e. the queue. Is this what you also do.

Question 2 & 3 are about performance. I am trying to reduce the number of queries, but am stuck with the idea that I have to ping every player every second to get up to date info on them.

Hi - thanks :slight_smile:

  1. I am proud of my just finished documentation (;-): link to command - search for
    group.set.queuemode and Values (the link to headers have to be improved :frowning:

  2. Not really. I use Node-RED for automation: events such as leave/return to home, sleep, awake trigger actions. For ad hoc I use Alexa (triggering Node-RED) and for visualization my Smartwatch (it shows title, station via the Smartphone SONOS app) or Smartphone with Dashboard.

Anyway you can use this sonos notify to get a trigger when there are change.

Does it help?

1 Like

You should be proud of it.

It's epic!!!

I need to carve out some time to understand what I can do now. Wow!

@hklages
I'm trying to figure out your comment here:

" #FF0000 Important: In node-red-contrib-sonos-plus standalone player are treated as a group (with size 1)! So you should use the group commands to control standalone player!"

What is the point of the player commands then?

I wrote all my flows to distinguish between the group (controller) and individual players. I'll re-write it, just trying to understand why.

Hi Alex. You don't need to rewrite anything. Everything is done inside the nodes(internally).
I have to improve that part of the documentation.

Example: There is a "group.play" - but there is no player.play.

So to play a song on a stand alone player, just use group.play in combination with a config node pointing to that player.

(To confuse your a bit more: You can also use a config node pointing to any active player but being overuled with msg.playerName = your standalone player)

I understand the confusing part of your statement to do with overriding the player using msg.playerName, however I don't get the first part about when to use group.x vs player.x.

Unfortunately I can't tack a picture of the drop down menu in your node, but you can see from this image that there are still player.x commands:

image

When would I use the player.x commands going forward?

On another note, I am not sure if you intended the MySonos node to work this way, but I had to split my playlist messages and send them to an individual MySonos node per player....

ie passing msg.playerName does not seem to work for the MySonos node. It's no big deal, but thought I'd mentioned it, in case this wasn't how you expected it to work.

.. let's have a look at a concrete example - get queue.

Assuming play:5 (ip addresss 192.168.178.1 with name KITCHEN) and play:3 (ip address 192.168.178.2, name BATH) are grouped and the KITCHEN is the coordinator.

group.get.queue will always output the queue from KITCHEN - even if you send it to BATH. Because in this group, KITCHEN is coordinator and this queue is relevant.

player.get.queue will output the queue from BATH if you send it to BATH.

If you have stand alone player both commands will provide the same result.

Understood!

So before I was ALWAYS sending the coordinator = Kitchen (in your example above) to get the queue, now I can use any player in the group for group commands.

I just tested it for group.get.volume and it works that way you have said i.e. I sent the command to a player that was not the coordinator and yet the group volume was returned.

Thank you!

Can you please send the flow? With export. That flow looks strange. What would you like to achieve?

Of course, however I need to explain what I am doing first.

From uibuilder I am sending requests to control various players. One of those request is to change the playlist for a group.

Can you give me two mins to test your new nodes to see if the same applies to MySonos as the Universal node i.e. I'm wondering if your new version doesn't need all these nodes now.

[{"id":"2a9179ea.f2ddc6","type":"sonos-universal","z":"e28c9ca5.73e4e","confignode":"193128a0.2db8a7","compatibilityMode":false,"command":"message","state":"","stateType":"str","name":"","x":2400,"y":1600,"wires":[["3c100ca2.933b14"]]},{"id":"3c100ca2.933b14","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2610,"y":1600,"wires":[]},{"id":"b9fea7cd.7e0778","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2390,"y":1540,"wires":[]},{"id":"4844384a.aaea98","type":"sonos-manage-mysonos","z":"e28c9ca5.73e4e","confignode":"193128a0.2db8a7","compatibilityMode":false,"command":"mysonos.queue.item","state":"","stateType":"str","name":"","x":2920,"y":1740,"wires":[["e5d5ef5d.1b76a"]]},{"id":"5b16e9ce.5ebf38","type":"function","z":"e28c9ca5.73e4e","name":"Send Payload","func":"//\"playerName\"   : msg.playername,\n\nnode.send\n    ({\n    \"payload\"      : msg.payload,\n    })\n\n// setTimeout(function()\n//             { \n//             if (msg.type == \"playlist\") {node.warn([\"up to here now at last\", msg])\n//             return msg.payload}    \n//             }, 1500);","outputs":1,"noerr":0,"initialize":"","finalize":"","x":2680,"y":1740,"wires":[["4844384a.aaea98","65fb1e92.f2032"]]},{"id":"c8d16fd5.581c5","type":"switch","z":"e28c9ca5.73e4e","name":"PlayerName Route","property":"playerName","propertyType":"msg","rules":[{"t":"eq","v":"Office AV1","vt":"str"},{"t":"eq","v":"Office AV2","vt":"str"},{"t":"eq","v":"Kit Cabinet","vt":"str"},{"t":"eq","v":"Kit Garden","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":2410,"y":1820,"wires":[["5b16e9ce.5ebf38"],["385128f4.39d478"],["b4f902ce.95674"],["749cf01c.71c8"]]},{"id":"a87f6ad5.b36408","type":"sonos-manage-mysonos","z":"e28c9ca5.73e4e","confignode":"ecfbb4f2.891d98","compatibilityMode":false,"command":"mysonos.queue.item","state":"","stateType":"str","name":"","x":2920,"y":1800,"wires":[["8377fac7.e7eb18"]]},{"id":"385128f4.39d478","type":"function","z":"e28c9ca5.73e4e","name":"Send Payload","func":"//\"playerName\"   : msg.playername,\n\nnode.send\n    ({\n    \"payload\"      : msg.payload,\n    })\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":2680,"y":1800,"wires":[["a87f6ad5.b36408"]]},{"id":"e5d5ef5d.1b76a","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":3130,"y":1740,"wires":[]},{"id":"8377fac7.e7eb18","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":3130,"y":1800,"wires":[]},{"id":"cb39b6f5.75bb98","type":"switch","z":"e28c9ca5.73e4e","name":"Playlist Routing","property":"type","propertyType":"msg","rules":[{"t":"neq","v":"playlist","vt":"str"},{"t":"eq","v":"playlist","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":2160,"y":1640,"wires":[["2a9179ea.f2ddc6","b9fea7cd.7e0778"],["c8d16fd5.581c5","e6411942.d18728"]]},{"id":"65fb1e92.f2032","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2870,"y":1680,"wires":[]},{"id":"e6411942.d18728","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2370,"y":1740,"wires":[]},{"id":"ba8d3d86.f86c1","type":"sonos-manage-mysonos","z":"e28c9ca5.73e4e","confignode":"7e7eaede.6ea5d","compatibilityMode":false,"command":"mysonos.queue.item","state":"","stateType":"str","name":"","x":2920,"y":1860,"wires":[["b0ec837d.8089c"]]},{"id":"b4f902ce.95674","type":"function","z":"e28c9ca5.73e4e","name":"Send Payload","func":"//\"playerName\"   : msg.playername,\n\nnode.send\n    ({\n    \"payload\"      : msg.payload,\n    })\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":2680,"y":1860,"wires":[["ba8d3d86.f86c1"]]},{"id":"b0ec837d.8089c","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":3130,"y":1860,"wires":[]},{"id":"c9837d9b.64789","type":"sonos-manage-mysonos","z":"e28c9ca5.73e4e","confignode":"175b8337.d329ad","compatibilityMode":false,"command":"mysonos.queue.item","state":"","stateType":"str","name":"","x":2920,"y":1920,"wires":[["4e6d05e7.9740dc"]]},{"id":"749cf01c.71c8","type":"function","z":"e28c9ca5.73e4e","name":"Send Payload","func":"//\"playerName\"   : msg.playername,\n\nnode.send\n    ({\n    \"payload\"      : msg.payload,\n    })\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":2680,"y":1920,"wires":[["c9837d9b.64789"]]},{"id":"4e6d05e7.9740dc","type":"debug","z":"e28c9ca5.73e4e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":3130,"y":1920,"wires":[]},{"id":"193128a0.2db8a7","type":"sonos-config","z":"","name":"Office AV1","serialnum":"00-0E-58-71-3B-8E:B","ipaddress":"192.168.1.80"},{"id":"ecfbb4f2.891d98","type":"sonos-config","z":"","name":"Office AV2","serialnum":"00-0E-58-85-16-D8:0","ipaddress":"192.168.1.216"},{"id":"7e7eaede.6ea5d","type":"sonos-config","z":"","name":"Kit Cabinet","serialnum":"00-0E-58-57-A1-A8:B","ipaddress":"192.168.1.205"},{"id":"175b8337.d329ad","type":"sonos-config","z":"","name":"Kit Garden","serialnum":"00-0E-58-77-47-AC:8","ipaddress":"192.168.1.97"}]

EDIT: Ok, I tested it and I still get this message if I do not sent the playlist request to the coordinator of the group:

image

I'm going to load you with a few things given your responding quickly :wink:

This next one is just feedback. I get it now, but it took me some research to figure out how to send commands.

In your drop downs you show commands like this: "group: get playback state"

image

However you expect the command to be sent like this: "get.playback.state"

Now this doesn't affect me anymore, as I get it now, however I would suggest that you show the command as it's required in the drop down menu. I get that it shows the correct way on the node itself, but for the uninitiated it's not that obvious. Very minor point of feedback. Take it or leave it.

Good point. My intention was to provide a help text but it looks as if that went wrong. "group.get.playbackstate" is easier to understand.

Thats something for one of the next releases.

Are all players grouped?
yes?: You can use 1x mysonos.export.item + 1 x group.play.export

If you only want to queue without playing then group.queue.export would be good - but i have to create it. Its still missing in the current version.

Ok, let me try that combination out. Thanks!

Fyi, Not ALL my players are grouped.

EDIT: Ok, I tested your suggestion and it works!

Much simpler flow, thanks again!
image

I was very pleased with myself figuring out this flow... haha, off to the trash bin you go!

image

@hklages
I finished making my integrated Sonos panel, you can see an image here:

What I learned is that if someone (read: you, hahaha) modified your Sonos Plus node to be based on the code in this node, that you suggested I look at using: https://flows.nodered.org/node/node-red-contrib-sonos-notify

....... the result would be the ultimate Sonos Plus node, something like Sonos Plus+

The sonos-notify node does a brilliant job at handling requests. I think it's doing something like pinging all data in one go every second (maybe 1 request per device), but I can't be sure without looking at the code.

All I can say is that the impact on network traffic when running it is difficult to measure,whereas when I use and abuse your nodes I can see huge jumps in traffic.

I appreciate that you didn't write your nodes to do what Sonos Notify does, but one can dream of the perfect node... that said, well done on such a great effort. Your node is amazingly specified, so you can do almost everything Sonos related and I will be implementing it across more web pages to come.

Hi Alex88 - your project looks great. I still don't use any floor plan - but that could be one of my next projects. Winter is long and Corona ensures, that I have time.

Regarding "notify": In fact notify and sonos-plus already use the same library node-sonos. Notify is great to get the status of your Sonos device. It uses the SONOS feature "subscribe" and therefore don't need to poll every second/minute. That's why I suggest to use that package for getting the status of SONOS devices. Both don't interfere - they work together fine.

So far I did not implement the "subscribe" (node-sonos based) because it requires additional ports and starts processes. That is critical on my home automation hardware. Ideal would be to use the available Node-RED resources and capabilities. Maybe in a few month you can also get the status in sonos-plus.

Thank you! All the code is available on github, but TBH I have learnt a lot since then and it could be written better... it was my first project though, so to be expected

The problem with the other node is that it does not cover enough data from Sonos. If you check, it is limited to a few properties only, so unlike your node which covers practically everything anyone would want, I can't use the other node fully and I don't want to mix and match as that's twice the code and four times the pain.

If it helps push you over the edge to add the functionality to your node I can do a little code swap with you... I'll help you build your floorplan. Only tricky bit is will you be ok to adopt uibuilder and VueJS as it would be a lot harder to do using other nodes and I'm not sure I want to spend time doing it the hard way.

Reach out later, I'm sure I'll be knee deep in something else at the time, but I am also sure I can spare some time to help you .... for example, if you drew the plan up, I think we could configure the bulk of the floorplan lighting in an afternoon.

We would have to drop things like turning automated lighting on/off and lighting timers, but you can control your lights pretty easily in a short space of time.

I started to work on the event "catcher". Trackinfo, volume, mute works in a demonstrator (in my private environment).
What do you need else?

1 Like

This is what I am currently querying Sonos:

[{"id":"14409c9d.f34b33","type":"function","z":"e28c9ca5.73e4e","name":"Send to Sonos","func":"var FP_Sonos_Data   = flow.get(\"FP_Sonos_Data\");\n\n// //if sonos panel is disabled, do not refresh player status\nif (FP_Sonos_Data[0].sonos_visible === false) {return}\n\n\nFP_Sonos_Data.forEach(item => \n{\n//get group status\nif (item.device == \"Group\") \n    {\n    if (item.coordinator != \"standalone\")\n        {\n        node.send\n                ({\n                \"topic\"         : \"group.get.volume\", \n                \"playerName\"    : item.coordinator_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"group.get.mutestate\", \n                \"playerName\"    : item.coordinator_name,\n        })\n        node.send\n                ({\n                \"topic\"         : \"group.get.playbackstate\",        //play/pause (minimal)\n                \"playerName\"    : item.coordinator_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"group.get.queue\",                //entire playlist (minor impact)\n                \"playerName\"    : item.coordinator_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"group.get.trackplus\",            //queueposition (minimal)\n                \"playerName\"    : item.coordinator_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"group.get.state\",                //includes shuffle etc (minimal)\n                \"playerName\"    : item.coordinator_name,\n                })\n        }\n    }\n//get player status\nelse \n    {\n        node.send\n                ({\n                \"topic\"         : \"player.get.volume\",               // largest drop\n                \"playerName\"    : item.device,\n                })\n        node.send\n                ({\n                \"topic\"         : \"player.get.mutestate\",            // minimal\n                \"playerName\"    : item.device,\n        })\n        node.send\n                ({\n                \"topic\"         : \"player.get.role\",                 //minimal\n                \"playerName\"    : item.device,\n        })\n    }\n\n});\n\n//runs once\n//get player details where player is single player in the group\nvar singleplayer_device_name = FP_Sonos_Data[0].singleplayergroup\nif (singleplayer_device_name !== \"\")\n    {\n         node.send\n                ({\n                \"topic\"         : \"player.get.volume\", \n                \"playerName\"    : singleplayer_device_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"group.get.mutestate\", \n                \"playerName\"    : singleplayer_device_name,\n        })\n       node.send\n                ({\n                \"topic\"         : \"group.get.playbackstate\",        //play/pause (minimal)\n                \"playerName\"    : singleplayer_device_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"player.get.queue\",                //entire playlist (minor impact)\n                \"playerName\"    : singleplayer_device_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"group.get.trackplus\",            //queueposition (minimal)\n                \"playerName\"    : singleplayer_device_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"group.get.state\",                //includes shuffle etc (minimal)\n                \"playerName\"    : singleplayer_device_name,\n                })\n        node.send\n                ({\n                \"topic\"         : \"player.get.role\",                 //get role\n                \"playerName\"    : singleplayer_device_name,\n        })\n    }\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1060,"y":1300,"wires":[["3df27e15.cd8402"]]}]

Great news BTW!!!

I have been thinking about rebuilding the Sonos integration on my floorplan to reduce the traffic to my Sonos players, but I'll wait to see how you progress.