Sonos group players for announcement - very slow grouping

I just worked on grouping some players triggered by an amazon Dash Button to send an announcement to the group and then ungroup the players again.

It works but so slow, that I need a very long Thread:sleep of almost 30 !!! Seconds.
Is there a way to improve performance?
I also tried around with SaveAll and RestoreAll to save the state of each player before grouping and having it restored after grouping but this also didn’t work out properly.

Save also takes a long time and Restore led to some confused players starting and stopping the radiostation which played before the grouping several times (ghost toggeling).

All other sonos commands are almost immediately executed.

rule "Testruf - Amazon Dash Button Gilette "
when
        Channel "amazondashbutton:dashbutton:ac-63-be-xx-yy-zz:press" triggered
    then
				gHome_sonos_Notificationsoundvolume.sendCommand("60")
				gHome_sonos_Control.sendCommand(PAUSE)
				gHome_sonos_Mute.sendCommand("OFF")

				og_bue_sonos_StandAlone.sendCommand(ON)
				og_bue_sonos_Add.sendCommand("RINCON_YYYYYYYYYYYYYY1400")
				og_bue_sonos_Add.sendCommand("RINCON_ZZZZZZZZZZZZZZ1400")
				og_bue_sonos_Add.sendCommand("RINCON_AAAAAAAAAAAAAA1400")
				og_bue_sonos_Add.sendCommand("RINCON_BBBBBBBBBBBBBB1400")
			    og_bue_sonos_Add.sendCommand("RINCON_CCCCCCCCCCCCCC1400")

				Thread::sleep(30000) // time for proper grouping very long
				say("Das ist ein Test","voicerss:deDE","sonos:PLAY3:RINCON_XXXXXXXXXXXXXX1400")			

				ug_jes_sonos_StandAlone.sendCommand(ON)
				eg_kue_sonos_StandAlone.sendCommand(ON)
				eg_wzi_sonos_StandAlone.sendCommand(ON)
				og_bue_sonos_StandAlone.sendCommand(ON)
				og_bad_sonos_StandAlone.sendCommand(ON)
				dg_syd_sonos_StandAlone.sendCommand(ON)


end

BTW:
I don’t see any log events in karaf console with org.openhab.binding.sonos being set to DEBUG.

Any idea appreciated on how to group 6 players performantly, play the “say” command (in the end I’d like to play a “gong” signal before the say command) and then ungroup them and allow them to play on from where they were before the grouping happend.

Ok, so this is some ugly ass code, but it is what is in my system currently and it works (well enough, though sometimes the system gets all confused and has to be restarted). Total time waiting for each is about 6 seconds before sound is played.

The regrouping is the biggest pain as the save & restore doesn’t work for groupings, so I manually do it. The one main issue with this code is it is only as good as the bindings information. if someone manually sets a group or starts/stops playback right before this code goes off, the grouping information in the binding can be out of date and it can restore the state wrong.

Also, the say command uses the default audiosink which is SonosMaster.


.items declaration
String SonosSayText
Switch houseAsleep         // switch to indicate that the master bedroom is asleep, if this is ON, then no sound plays
Switch asleepDownstairs  // switch to indicate if the downstairs rooms are asleep (kids rooms) if this is ON the sound only plays upstairs

the rest of the items are mapped to Sonos Thing channels, the names of the items are representative, .eg:
SonosMaster_ZoneGroup is the thing SonosMaster link to the ZoneGroup channel

items starting with 'g' are groups of items and the name is also representative
gSonosNotificationVolumes is a group for all the NotificationVolume channels
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rule "Alert the House"
when
	Item SonosSayText changed
then
	var textToSay = ""
	var toAll = false
	
	if(!SonosSayText.state.toString.equals(""))
	{
		textToSay = SonosSayText.state.toString
		SonosSayText.postUpdate("")
	}
	
	if(!textToSay.equals("") && !textToSay.equals("NULL"))
	{
		if(houseAsleep.state == OFF)
		{
			// backup the zone configuration
			var zonesXML = SonosMaster_ZoneGroup.state.toString	
			
			// save the state of all the Sonoses
			SonosSaveAll.sendCommand(ON)
			
			// adjust the NotificationVolume on all sonoses
			gSonosNotificationVolumes.sendCommand(50)
			if(asleepDownstairs.state == OFF)
			{		
				SonosMaster_StandAlone.sendCommand(ON)
				Thread::sleep(500)
				SonosMaster_Add.sendCommand("RINCON_B8E937DD8A2801400")	// kitchen
				SonosMaster_Add.sendCommand("RINCON_5CAAFD2C5D6401400")	// master bath
			 	SonosMaster_Add.sendCommand("RINCON_5CAAFD2A673E01400")	// bellas
			 	SonosMaster_Add.sendCommand("RINCON_5CAAFD2A66A601400")	// gabes
			 	SonosMaster_Add.sendCommand("RINCON_5CAAFD4627D401400")	// kidsbath
			 	SonosMaster_Add.sendCommand("RINCON_5CAAFD159A2A01400")	// theater
				Thread::sleep(5000)
				toAll = true
				
				logInfo("AudioRules", String::format("Saying %s to the whole house", textToSay))
				say(textToSay)	
			}
			else
			{
				SonosMaster_StandAlone.sendCommand(ON)
				Thread::sleep(500)
				SonosMaster_Add.sendCommand("RINCON_B8E937DD8A2801400")	// kitchen
				SonosMaster_Add.sendCommand("RINCON_5CAAFD2C5D6401400")	// master bath		
				Thread::sleep(5000)
				
				logInfo("AudioRules", String::format("Saying %s to just the Upstairs", textToSay))
				say(textToSay)
			}
			Thread::sleep(1000)
				
			// restore the groupings
			var groupCount = Integer::parseInt(transform("XPATH", "count(/ZoneGroups//ZoneGroup)", zonesXML))
			
			var i = 1
			while((i=i+1) < groupCount+1)
			{
				var slaveCount = Integer::parseInt(transform("XPATH", String::format("count(/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember)", i), zonesXML))
				var coordinatorID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]/@Coordinator", i), zonesXML)
				
				var alone = false
				var playerName = ""
				
				if(slaveCount <= 1)
				{
					alone = true
				}
				
				if(coordinatorID == "RINCON_5CAAFD16993F01400")	// Master
				{
					playerName = "Master"
					if(alone)
					{
						SonosMaster_StandAlone.sendCommand(ON)
					}
					else
					{
						var j = 1
						while((j=j+1) < slaveCount+1)
						{
							var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
							SonosMaster_Add.sendCommand(slaveID)
							logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
						}
					}
				}
				else if(coordinatorID == "RINCON_B8E937DD8A2801400")	// kitchen
				{
					playerName = "Kitchen"
					if(alone)
					{
						SonosKitchen_StandAlone.sendCommand(ON)
					}
					else
					{
						var j = 1
						while((j=j+1) < slaveCount+1)
						{
							var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
							SonosKitchen_Add.sendCommand(slaveID)
							logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
						}
					}
				}
				
				if(toAll)
				{
					if(coordinatorID == "RINCON_5CAAFD2C5D6401400")	// master bath
					{
						playerName = "Master Bath"
						if(alone)
						{
							SonosMasterBath_StandAlone.sendCommand(ON)
						}
						else
						{
							var j = 1
							while((j=j+1) < slaveCount+1)
							{
								var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
								SonosMasterBath_Add.sendCommand(slaveID)
								logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
							}
						}
					}
					else if(coordinatorID == "RINCON_5CAAFD2A673E01400")	// bellas
					{
						playerName = "Bellas"
						if(alone)
						{
							SonosBellas_StandAlone.sendCommand(ON)
						}
						else
						{
							var j = 1
							while((j=j+1) < slaveCount+1)
							{
								var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
								SonosBellas_Add.sendCommand(slaveID)
								logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
							}
						}
					}
					else if(coordinatorID == "RINCON_5CAAFD2A66A601400")	// gabes
					{
						playerName = "Gabes"
						if(alone)
						{
							SonosGabes_StandAlone.sendCommand(ON)
						}
						else
						{
							var j = 1
							while((j=j+1) < slaveCount+1)
							{
								var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
								SonosGabes_Add.sendCommand(slaveID)
								logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
							}
						}
					}
					else if(coordinatorID == "RINCON_5CAAFD4627D401400")	// kidsbath
					{
						playerName = "Kids Bath"
						if(alone)
						{
							SonosKidsBath_StandAlone.sendCommand(ON)
						}
						else
						{
							var j = 1
							while((j=j+1) < slaveCount+1)
							{
								var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
								SonosKidsBath_Add.sendCommand(slaveID)
								logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
							}
						}
					}
					else if(coordinatorID == "RINCON_5CAAFD159A2A01400")	// theater
					{
						playerName = "Theater"
						if(alone)
						{
							SonosTheater_StandAlone.sendCommand(ON)
						}
						else
						{
							var j = 1
							while((j=j+1) < slaveCount+1)
							{
								var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
								SonosTheater_Add.sendCommand(slaveID)
							}
						}
					}
				}	
			}
			
			Thread::sleep(1000)
			SonosRestoreAll.sendCommand(ON)
		}
	}
end