Sonos Multiroom Control rule

UPDATE This post is deprecated. See the following post for the new rule that is compatible with openHAB 3 https://community.openhab.org/t/sonos-multiroom-rule-for-openhab-3/108305

UPDATE: I made some changes on the sitemaps and also some corrections in t´he rule, so please update your files. The rule can be replaced, but ceep your configuration of the Userinput area. Additionally I added some screenshots of the sitemap.

Hello together,
today I want to share my Sonos Multiroom Control rule. I designed this because I don’t want to control my sonos devices fom the sonos app. I want to control all devices from my sitemaps. Also I will not write for each room a rule that checks if some other room is currently active. So I come up to the decision that I have to write a rule that can manage all this in one. The only thing have to do in my presence detection rules of each room ist o provide the information if the player should be started or stopped. All other things like grouping and Volume Control for each room should be done automatically. Now this rule is running quite well. Of course I’m still in testing but maybe someone of you is interested on such a rule. So I decided to provide it to you.
And if this rule is in the wrong category, please admins move it to the right position. Thanks :wink:

The rule is able to manage the grouping of zones. You can check if some player is currently active, if yes, the given player will be added (Multiroom). If no player is available, a default uuri will be played (Play) Of course you can add some own code to the play section if you know what you do ;). Additionally, you can easily remove a player from the zone, unless if he is the zone coordinator or a player. The rule will manage it automatically (Remove). Then you can make a player to a standalone player with one command including the remove of all other players (Standalone). The next thing I added was the possibility to ungroup inactive players automatically if they are inactive for 60 seconds. I found this very useful because in some cases I switched from sonos to iPhone and back to sonos. Then the music was in all rooms and not only in the room where I was because I was not ungrouping the devices during the switch to my iPhone. After this my grouping rule worked quite well, but there was the next problem. If I control the volume from my sitemap, I have to manage each room itself. So I added some Volume feature to the rule that can easily change the volume of all devices if one device changes (MultiroomVolume). But then I realized that it makes no sense to have the same volume in each room. There is always some offset from room to room needs because in each room the given volume is different because of the amount of speakers and so on. So I added the function of an offset. After this I recognized that not always a player is active and if no player is active, how can I define a default volume. This is needed to avoid extreme loud music if I go. in the kitchen in the night and the music starts with the last given volume… This feature can be called manually or if you use MultiroomVolume and no player is available. it will automatically use the default volume that is defined for each hour of the day. Of course it will also use the offset of each zone (DefaultVolume). Then I also want to disable the Multiroom Volume for one specific room if needs, so I also added this feature. Now this is the current status and maybe you have some more ideas that should be added to this rule.

To control the the rule, there are different things needed. For the Volume control, you have to define a Offset for the room in the “SonosZone_Offset” Item. Additionally, you have to enable the Volume feature with the Switch “SonosZone_MultiroomVolume”. To control the grouping, you have to use the Item “SonosMultiroom”. You can send a String command to it. This string can contain one to more commands and one or more Sonos Players. You can use the following commands Multiroom, Remove, Standalone, MultiroomVolume, DefaultVolume and Play for more detils look in the description. After each feature description the feature is visible in (). The Syntax is the following:

Command/Command…/Devices=Zone1/Zone2…

So for example: Multiroom/MultiroomVolume/Devices=Livingroom
This will check if currently some zone is running, if yes, it will add Livingroom to the zone and change the volume of the zone to active zone. Thereby the offset of both zones will be used! If no player is available, it will use a predefined uuri that you have to defined in the rule itself. (maybe I will change this to a item so that you can change the uuin during the runtime. But again it is only used if no player is available.) If there was no zone active before it will use the Defaultvolume that is predefined in the rule for each hour of the day. Of course the offset of the player will be used again.

To get this rule running, you need the following Sonos Items. Some of them are general Items and some are needed for each device. The name Syntax is always the same (“Sonos” + Zonename + “_Channelname”). It is important that you are using the same groups like I have defined them. Of course you can add additional groups if they are needed from your current sitemaps or rules. Then there are some Items that need a link to a channel of a sonos device and some are needs for rules and so there is no sonos channel available for them (so don’t be confused).
I recommend to use a persistence Service for the items of group gSonosMultiroom* to avoid a reset after a reboot.

Items:

General Items that are only needed once:

Group gSonos	
Group:Dimmer:AVG gSonosVolume					(gSonos)
Group:Number:COUNT("PLAYING") gSonosState		(gSonos)
Group:Number:COUNT("ON") gSonosMultiroom		(gSonos)
Group:Number:AVG gSonosMultiroomOffset 			(gSonos)
String	SonosMultiroom			<mediacontrol>	(gSonos)
Switch 	SonosMultiroomUpdatePlayers			//Do not use this item manually!!!	

Items that you need for each room. The example is now with the Livingroom:

Switch	SonosLivingroom_MultiroomVolume		<switch>		(gSonosMultiroom)										
Dimmer	SonosLivingroom_Offset			    <soundvolume>	(gSonosMultiroomOffset)									
Dimmer	SonosLivingroom_Volume		        <soundvolume>	(gSonosVolume)	         { channel="" }
Player	SonosLivingroom_Control		        <player>		(gSonosState)		     { channel="" }
Image   SonosLivingroom_CurrentAlbumArt				        (gSonos)	             { channel="" ]
String  SonosLivingroomr_CurrentArtist					    (gSonos)			     { channel="" }
String	SonosLivingroom_Add						        	(gSonos)		         { channel="" }
String	SonosLivingroom_Coordinator					        (gSonos)			     { channel="" }
Switch  SonosLivingroom_Mute						     	(gSonos)		     	 { channel="" }		
String  SonosLivingroom_PlayURI					         	(gSonos)		  	     { channel="" }
String	SonosLivingroom_Remove					        	(gSonos)			     { channel="" }
String  SonosLivingroom_Repeat					        	(gSonos)                 { channel="" }
Switch  SonosLivingroom_Shuffle					         	(gSonos)			     { channel="" }
Switch	SonosLivingroom_StandAlone				        	(gSonos)			     { channel="" }
	

Some excample siemps:

Player inactive:

Player active

Multiroom Control options:
MultiroomControl

Frame label="Sonos"{
		Default item=SonosLivingroom_Control label="Player" icon="player"
		Text icon="none"
		Selection item=SonosMultiroom label="Multiroom Control"  icon="mediacontrol" mappings=["Multiroom/MultiroomVolume/Devices=Livingroom"="Add to/Create Multiroom Group", 
			"Standalone/Devices=Livingroom"="Set to Standalone", "MultiroomTarget/DefaultVolume/Devices=Livingroom/Bedroom"="Add Bedroom to Group", 
			"MultiroomTarget/Devices=Livingroom/Bathroom"="Add Bathroom to Group", "EnableMultiroomVolume/Devices=Livingroom"="Enable Multiroom Volume", 
			"DisableMultiroomVolume/Devices=Livingroom"="Disable Multiroom Volume"]
		Switch item=SonosLivingroom_Repeat label="Repeat: " visibility=[SonosLivingroom_Control=="PLAY"] mappings=[ALL="All", OFF="None", ON="This track"]
		Switch item=SonosLivingroom_MultiroomVolume label="Multiroom Volume" mappings=[ON="On", OFF="Off"]
		Switch item=SonosLivingroom_Shuffle label="Shuffel: " visibility=[SonosLivingroom_Control=="PLAY"] mappings=[ON="On", OFF="Off"]
		Slider item=SonosLivingroom_Volume label="Volume" icon="soundvolume"
		Text item=SonosLivingroom_CurrentTitle label="Titel: " visibility=[SonosLivingroom_Control=="PLAY"]
		Slider item=SonosLivingroom_Offset label="Offset" icon="soundvolume" 
		Text item=SonosLivingroom_CurrentArtist label="Artist: "  visibility=[SonosLivingroom_Control=="PLAY"]
		Switch item=SonosLivingroom_Mute label="Mute: " visibility=[SonosLivingroom_Control=="PLAY"] mappings=[ON="On", OFF="Off"]
		Text item=SonosLivingroom_CurrentAlbum label="Album: "  visibility=[SonosLivingroom_Control=="PLAY"]
		Image item=SonosLivingroom_CurrentAlbumArt label="Cover" visibility=[SonosLivingroom_Control=="PLAY"]
		}

Now you are almost done. The last thing is the rule file. In this you have to define for each Room (like Livingroom), the room name and the uuri of the sonos player. Therefore search for the field SonosPlayer and SonosUURI, they are in the User Input section. Then add each Player and uri separated. I is important that the uuri and the playername are on the same position in the array. One example with three rooms is available in the rule. Just replace the three rooms with your rooms, of yourse you can add so many rooms like you need. Then you have to define a default volume for each hour in the DefaultVolume array. The first entry is for 00:00, the next entry for 01:00, 02:00…23:00 so you should have 2 entries. As last step you have to define the default play uri that should be used if no zone is active. I used a radio uri.

The rule will be splitted in two parts, because it is to big for one post:


import java.util.List
import java.util.ArrayList
import org.eclipse.xtext.xbase.lib.Functions
import java.util.concurrent.locks.ReentrantLock
import org.eclipse.xtext.xbase.lib.Procedures
import org.eclipse.smarthome.model.script.actions.Timer

//Userinput needed:
val ArrayList<String> SonosPlayer = newArrayList("Livingroom", "Bedroom", "Bathroom")
val ArrayList<String> SonosUUID = newArrayList("RINCON_XXAAFDFXX2CC01400", "RINCON_XX9F3EFEXX2801400", "RINCON_XX28CAXXCAAE01400")
val List<Integer> DefaultSpeakerVolume = newArrayList(8, 8, 8, 8, 8, 8, 8, 10, 12, 14, 16, 18, 18, 20, 20, 20, 20, 18, 18, 16, 14, 12, 10, 8)
val String DefaultPlayUri = "x-rincon-mp3radio://http://hr-youfm-live.cast.addradio.de/hr/youfm/live/mp3/128/stream.mp3?ar-distributor=f0a1"

//Userinput end, please do not change settings below if you are not sure that you know what you arre doing!

var Boolean firstRun = true
var Boolean MultiroomVolumeServiceRunning = false
var List<Integer> PlayerStatusUpdateState = newArrayList(0)		
var List<Integer> MultiroomVolumeOffset =newArrayList(0)
var List<Integer> MultiroomVolumeAvailable = newArrayList(0) 
val ReentrantLock PlayerStatusUpdateLock = new ReentrantLock()
val ReentrantLock MultiroomVolumeLock  = new ReentrantLock()  
var Timer AutoremoveTimer = null

val Functions$Function1<String, Boolean> prerequiremets= [ s |
	var JobSuccesfull = true
	if(gSonosState.state === NULL) {
		logError("SonosMultiroom.Service", "Please check input items Sonos..._State. The import of the groupitem.state failed.")
		JobSuccesfull = false
		}
	if(gSonosVolume.state === NULL) {
		logError("SonosMultiroom.Service", "Please check input items Sonos..._Volume. The import of the groupitem.state failed.")
		JobSuccesfull = false
		}
	if(gSonosMultiroom.state === NULL) {
		logError("SonosMultiroom.Service", "Please check input items Sonos..._MultiroomVolume. The import of the groupitem.state failed.")
		JobSuccesfull = false
		}
	if(gSonosMultiroomOffset.state === NULL) {
		logError("SonosMultiroom.Service", "Please check input items Sonos..._Offset. The import of the groupitem.state failed.")
		JobSuccesfull = false
		}
	if(JobSuccesfull == false) {
		logError("SonosMultiroom.Service", "Sonos Multiroom stops because not all prerequirements are available.")
		true
		} else {
		false
		}
	]
val Functions$Function1<List<String>, List<Integer>> initMultiroomVolumeAvailable= [ myArray |
	var Number Cursor = 0
	var String Command
	var List<Integer> functionArray = newArrayList(0)logInfo("SonosMultiroom.Service", "Initialise Sonos Multiroom Service")
	while(Cursor < myArray.size()) {
		Command = "Sonos" + myArray.get((Cursor).intValue) + "_MultiroomVolume"
		val ImportMultiroom = gSonosMultiroom.members.findFirst[ i | i.name == Command ]
		if(ImportMultiroom.state == ON) {
		Command = "Sonos" + myArray.get((Cursor).intValue) + "_Volume"
			val ImportVolume = gSonosVolume.members.findFirst[ i | i.name == Command ]
			functionArray.add(((Cursor).intValue), (Integer::parseInt(ImportVolume.state.toString)))	
			} else {
			logInfo("SonosMultiroom.Service", "Multiroom Volume is inactive in Zone " + myArray.get(Cursor.intValue) + ".")
			functionArray.add(((Cursor).intValue), -1)
			}
		Cursor = Cursor+1
		}
	functionArray
	]

val Functions$Function1<List<String>, List<Integer>> initMultiroomVolumeOffset= [ myArray |
	var Number Cursor = 0
	var String Command
	var List<Integer> functionArray = newArrayList(0)
	while(Cursor < myArray.size()) {
		Command = "Sonos" + myArray.get((Cursor).intValue) + "_Offset"
		val ImportOffset = gSonosMultiroomOffset.members.findFirst[ i | i.name == Command ]
		functionArray.add(((Cursor).intValue), (Integer::parseInt(ImportOffset.state.toString)))	
		Cursor = Cursor+1
		}
	functionArray
	]
	
val Functions$Function1<List<String>, List<Integer>> initPlayerStatusUpdateState= [ myArray |
	var Number Cursor = 0
	var String Command
	var List<Integer> functionArray = newArrayList(0)
	logInfo("SonosMultiroom.Service", "Initialise Sonos Player Update Service")
	while(Cursor < myArray.size()) {
		Command = "Sonos" + myArray.get((Cursor).intValue) + "_Control"
		val ImportControl = gSonos.members.findFirst[ i | i.name == Command ]
		if(ImportControl.state == PLAY) {
			functionArray.add((Cursor.intValue), 0)
			}
		if(ImportControl.state != PLAY) {
			functionArray.add((Cursor.intValue), -1)
			}
		Cursor = Cursor +1
		}
	functionArray
	]
	
val Functions$Function2<List<String>, String, Number> getPlayerID= [ myArray, player |
	var Number Cursor = 0
	var Number Pointer
	while(Cursor < myArray.size()) {
		if(player == myArray.get(Cursor.intValue)) {
			Pointer = Cursor
			Cursor = myArray.size()
			}
		Cursor = Cursor +1
		}
	Pointer
	]

val Functions$Function2<List<String>, String, Number> findCommand= [ myArray, command |
	var Number Cursor = 0
	var Boolean Commandfound = false
	while(Cursor < myArray.size()) {
		if(command == myArray.get(Cursor.intValue)) {
			Commandfound = true
			Cursor = myArray.size()
			}
		Cursor = Cursor +1
		}
	Commandfound
	]	

val Procedures.Procedure2<String, String> setUpdate = [myItem, myCommand |
	var String Device
	Device = "Sonos" + myItem
	Device.sendCommand(myCommand)
	]
	
	
rule "Sonos Multiroom"
when
Item SonosMultiroom received command
then
logInfo("SonosMultiroom.Service", "Multiroom Steuerung active")

var ArrayList<String> SonosCoordinatorList = newArrayList("empty")
var List<String> SonosControl
var List<String> SonosMode
var String SonosCommand
var Boolean AutoremoveServiceRunning = false
var Boolean JobSuccesfull = false
var Boolean MultiroomForced = false
var Boolean ResetCursor = false
var Number CursorPointer
var Number Cursor
var Number SonosControlCursor
var Number SonosModeCursor

JobSuccesfull = prerequiremets.apply("run")

if(MultiroomVolumeAvailable.size() < SonosPlayer.size()) {
	MultiroomVolumeAvailable = initMultiroomVolumeAvailable.apply(SonosPlayer)
	}
	
if(MultiroomVolumeOffset.size() < SonosPlayer.size()) {
	MultiroomVolumeOffset = initMultiroomVolumeOffset.apply(SonosPlayer)
	}

if(PlayerStatusUpdateState.size() < SonosPlayer.size()) {
	PlayerStatusUpdateState = initPlayerStatusUpdateState.apply(SonosPlayer)
	}

if(PlayerStatusUpdateLock.isLocked) {
	AutoremoveServiceRunning = true
	}

Cursor = 0
while(Cursor < SonosPlayer.size()) {
	SonosCommand = "Sonos" + SonosPlayer.get((Cursor).intValue) + "_Coordinator"
	val ImportCoordinator = gSonos.members.findFirst[ i | i.name == SonosCommand ]
	SonosCoordinatorList.add(((Cursor).intValue), ImportCoordinator.state)
	Cursor = Cursor+1
	}
Cursor = 0
if(Cursor == 0) {
	val List<String> ReadCommands = SonosMultiroom.state.toString.split("/Devices=")
	SonosControl=ReadCommands.get(1).split("/")
	SonosMode=ReadCommands.get(0).split("/")
	}

SonosModeCursor = 0
while(SonosModeCursor < SonosMode.size()) {
	
	//DefaultVolume Start
	if(SonosMode.get((SonosModeCursor).intValue) == "DefaultVolume") {
		var Number SetVolume
		var Number ZoneOffset
		SetVolume = DefaultSpeakerVolume.get(now.getHourOfDay)
		SonosControlCursor = 0
		JobSuccesfull = findCommand.apply(SonosMode, "MultiroomTarget")
		if(JobSuccesfull == true) {
			SonosControlCursor = 1
			}
		if(MultiroomVolumeLock.isLocked){
			//Warte eine Sekunde falls MultiroomVolume derzeit aktiv ist
			Thread::sleep(500)
			}
		logInfo("SonosMultiroom.Service", "MultiroomVolume Service locked.")
		MultiroomVolumeLock.lock()
		try {
			MultiroomVolumeServiceRunning = true
			if(MultiroomForced == false) {
				while(SonosControlCursor < SonosControl.size()) {
					Cursor = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
					if(MultiroomVolumeAvailable.get((Cursor).intValue) == -1) {
						logWarn("SonosMultiroom.Service", "MultiroomVolume für die Zone " + SonosPlayer.get(Cursor.intValue) + " ist deaktiviet, der Befehl wird ignoriert.")
						} else {
						ZoneOffset = ((SetVolume/100)*MultiroomVolumeOffset.get((Cursor).intValue)).intValue
						if(MultiroomVolumeAvailable.get((Cursor).intValue) != ZoneOffset){
							MultiroomVolumeAvailable.set(((Cursor).intValue), ZoneOffset)		
							SonosCommand = ZoneOffset.toString
							logInfo("SonosMultiroom.Service", "DefaultVolume will be applied for Zone " + SonosControl.get((SonosControlCursor).intValue) + ".")
							setUpdate.apply(SonosControl.get(SonosControlCursor.intValue) + "_Volume", SonosCommand)
							}
						}
					SonosControlCursor = SonosControlCursor+1
					}
				}
			if(MultiroomForced == true) {
				Cursor = 0	
				while(Cursor < SonosPlayer.size()) {
					if(MultiroomVolumeAvailable.get((Cursor).intValue) == -2) {
						ZoneOffset = ((SetVolume/100)*MultiroomVolumeOffset.get((Cursor).intValue)).intValue
						if(MultiroomVolumeAvailable.get((Cursor).intValue) != ZoneOffset) {				
							MultiroomVolumeAvailable.set(((Cursor).intValue), ZoneOffset)						
							SonosCommand = ZoneOffset.toString
							logInfo("SonosMultiroom.Service", "DefaultVolume will be applied for Zone " + SonosPlayer.get((Cursor).intValue) + ". ")
							setUpdate.apply(SonosPlayer.get(Cursor.intValue) + "_Volume", SonosCommand)
							}
						}
					Cursor = Cursor+1
					}
				}
			}
		finally{
			MultiroomVolumeLock.unlock()
			}
		}		
	//DefaultVolume Ende
	
	
	//MultiroomVolume Start
	if(SonosMode.get((SonosModeCursor).intValue) == "MultiroomVolume") {
		SonosControlCursor = 0
		JobSuccesfull = findCommand.apply(SonosMode, "MultiroomTarget")
		if(JobSuccesfull == true) {
			SonosControlCursor = 1
			}
		JobSuccesfull = false
		if(MultiroomVolumeLock.isLocked){
			Thread::sleep(500)
			}
		logInfo("SonosMultiroom.Service", "MultiroomVolume Service locked.")
		MultiroomVolumeLock.lock()
		try {
			MultiroomVolumeServiceRunning = true
			var Number ZoneCoordinatorOffset
			var Number ZonePlayerOffset
			while(SonosControlCursor < SonosControl.size()) {
				CursorPointer = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
				if(MultiroomVolumeAvailable.get((CursorPointer).intValue) < 0) {
					logWarn("SonosMultiroom.Service", "MultiroomVolume für die Zone " + SonosPlayer.get(CursorPointer.intValue) + " ist deaktiviet, der Befehl wird ignoriert.")
					}
				ZonePlayerOffset = MultiroomVolumeOffset.get((CursorPointer).intValue)
				Cursor = 0
				var Number MultiroomCursorPointer
				while(Cursor < SonosPlayer.size()) {
					if(SonosCoordinatorList.get((CursorPointer).intValue) == SonosUUID.get((Cursor).intValue) && SonosPlayer.get(Cursor.intValue) != SonosControl.get(SonosControlCursor.intValue)) {
						if(MultiroomVolumeAvailable.get((Cursor).intValue) >= 0) {
							MultiroomCursorPointer = Cursor
							ZoneCoordinatorOffset = MultiroomVolumeOffset.get((Cursor).intValue)
							JobSuccesfull = true
							Cursor = SonosPlayer.size()
							}
						}
					Cursor = Cursor+1
					}
				if(JobSuccesfull == true) {
					var Number mvolume
					mvolume= MultiroomVolumeAvailable.get((MultiroomCursorPointer).intValue)					
					mvolume = ((mvolume/ZoneCoordinatorOffset)*ZonePlayerOffset).intValue
					if(mvolume != MultiroomVolumeAvailable.get((CursorPointer).intValue) && MultiroomVolumeAvailable.get(CursorPointer.intValue) != -1) {
						SonosCommand = mvolume.toString
						logInfo("SonosMultiroom.Service", "MultiroomVolume sets volume of current zone player " + SonosControl.get((SonosControlCursor).intValue) + " equal to zone coordinator volume.")
						setUpdate.apply(SonosControl.get(SonosControlCursor.intValue) + "_Volume", SonosCommand)
						}
					}	
				if(JobSuccesfull == false) {
					if(MultiroomVolumeAvailable.get(CursorPointer.intValue) != -1) {
						logInfo("SonosMultiroom.Service", "Multiroom Volume will switch to DefaultVolume, there is no player with active MultiroomVolume to set volume available.")
						SonosMode.set(((SonosModeCursor).intValue), "DefaultVolume")
						MultiroomVolumeAvailable.set((CursorPointer.intValue), -2)
						MultiroomForced = true
						ResetCursor = true	
						}
					}					
				JobSuccesfull = false
				SonosControlCursor = SonosControlCursor+1	
				}
			}
		finally{
			MultiroomVolumeLock.unlock()
			}		
		}
	//MultiroomVolume Ende
	
	
	//DisableMultiroomVolume Start
	if(SonosMode.get((SonosModeCursor).intValue)=="DisableMultiroomVolume"){
		SonosControlCursor = 0
		while(SonosControlCursor < SonosControl.size()) {
			Cursor = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
			if(MultiroomVolumeAvailable.get(Cursor.intValue) != -1) {
				logInfo("SonosMultiroom.Service", "MultiroomVolume will be disabled for Zone " + SonosPlayer.get(Cursor.intValue) + ".")
				MultiroomVolumeAvailable.set((Cursor.intValue), -1)
				}	
			SonosControlCursor = SonosControlCursor + 1
			}
		}
	//DisableMultiroomVolume Ende
	
	//EnableMultiroomVolume Start
	if(SonosMode.get((SonosModeCursor).intValue)=="EnableMultiroomVolume"){
		SonosControlCursor = 0
		while(SonosControlCursor < SonosControl.size()) {
			Cursor = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
			if(MultiroomVolumeAvailable.get(Cursor.intValue) == -1) {
				logInfo("SonosMultiroom.Service", "MultiroomVolume will be enabled for Zone " + SonosPlayer.get(Cursor.intValue) + ".")
				SonosCommand = "Sonos" + SonosPlayer.get((Cursor).intValue) + "_Volume"
				val ImportVolume = gSonosVolume.members.findFirst[ i | i.name == SonosCommand ]
				MultiroomVolumeAvailable.set((Cursor.intValue), (Integer::parseInt(ImportVolume.state)))
				}
			SonosControlCursor = SonosControlCursor + 1
			}
		}
	//EnableMultiroomVolume Ende
	
	
	//Standalone Start 
	if(SonosMode.get((SonosModeCursor).intValue)=="Standalone"){
		SonosControlCursor = 0
		while(SonosControlCursor < SonosControl.size()) {
			CursorPointer = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
			Cursor = 0
			while(Cursor < SonosPlayer.size()) {
				if(SonosCoordinatorList.get((Cursor).intValue) == SonosCoordinatorList.get((CursorPointer).intValue) && CursorPointer != Cursor) {
					logInfo("SonosMultiroom.Service", "Standalone removes the Zoneplayer " + SonosPlayer.get((Cursor).intValue) +".")
					SonosCommand = SonosUUID.get((Cursor).intValue)
					setUpdate.apply(SonosControl.get(SonosControlCursor) + "_Remove", SonosCommand)
					SonosCoordinatorList.set(((Cursor).intValue), SonosUUID.get((Cursor).intValue))
					}
				Cursor = Cursor+1
				}
			SonosControlCursor = SonosControlCursor+1
			}
		}	
	//Standalone Ende
	
	
	//MultiroomTarget Start:
	if(SonosMode.get((SonosModeCursor).intValue)=="MultiroomTarget"){
		CursorPointer = getPlayerID.apply(SonosPlayer, SonosControl.get(0))
		Cursor = 0
		SonosControlCursor = 0
		JobSuccesfull = false
		while(SonosControlCursor < SonosControl.size()) {
			Cursor = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
			if(SonosControl.get(SonosControlCursor.intValue) != SonosPlayer.get(Cursor.intValue)) {
				logInfo("SonosMultiroom.Service", "MultiroomTarget add Zoneplayer " + SonosPlayer.get((Cursor).intValue) + " to Zone " + SonosControl.get(0) + ".")
				SonosCoordinatorList.set(((Cursor).intValue), SonosCoordinatorList.get((CursorPointer).intValue))
				setUpdate.apply(SonosControl.get(0) + "_Add", SonosUUID.get(Cursor.intValue))
				}
			SonosControlCursor = SonosControlCursor+1
			}
		}
	//MultiroomTarget Ende
	
	
//Multiroom Start	
	if(SonosMode.get((SonosModeCursor).intValue) == "Multiroom") {
		var Boolean	UseActivePlayers = false
		JobSuccesfull = findCommand.apply(SonosMode, "UseActivePlayer")
		if(JobSuccesfull == true) {
			logInfo("SonosMultiroom.Service", "Multiroom will modify active players if needed, because the option UseActivePlayer is used.")
			UseActivePlayers = true
			}
		SonosControlCursor = 0
		JobSuccesfull = false
		var Boolean SpeakerActive
		while(SonosControlCursor < SonosControl.size()) {
			SpeakerActive = false
			CursorPointer = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
			if(PlayerStatusUpdateState.get(CursorPointer.intValue) == 0 && UseActivePlayers == false) {
				SpeakerActive = true
				}
			if(SpeakerActive == false) {
				Cursor = 0
				while(Cursor < SonosPlayer.size()) {
					if(PlayerStatusUpdateState.get(Cursor.intValue) == 0 && SonosPlayer.get((Cursor).intValue) != SonosControl.get((SonosControlCursor).intValue)) {
						var Number MultiroomCursor = getPlayerID.apply(SonosUUID, SonosCoordinatorList.get(Cursor.intValue))
						JobSuccesfull = true
						logInfo("SonosMultiroom.Service", "Multiroom add " + SonosPlayer.get((CursorPointer).intValue) + " to Zone " + SonosPlayer.get((Cursor).intValue) + ".")
						SonosCoordinatorList.set(((CursorPointer).intValue), SonosUUID.get((MultiroomCursor).intValue))						
						setUpdate.apply(SonosPlayer.get((MultiroomCursor).intValue) +"_Add", SonosUUID.get((CursorPointer).intValue))
						Cursor = SonosPlayer.size()
						}
					Cursor = Cursor+1	
					}
				}
			SonosControlCursor = SonosControlCursor+1
			}
		if(JobSuccesfull == false  && SpeakerActive == false) {
			logInfo("SonosMultiroom.Service", "Multiroom will change to Play, because there is no active Zone available.")
			SonosMode.set(((SonosModeCursor).intValue), "Play")
			MultiroomForced = true
			ResetCursor = true
			}
		}
	//Multiroom Ende
	
	
	//Play Start
	if(SonosMode.get((SonosModeCursor).intValue) == "Play") {
		JobSuccesfull = false
		logInfo("SonosMultiroom.Service", "Play will play the Default URI in Zone " + SonosControl.get(0) + ".")
		setUpdate.apply(SonosControl.get(0) + "_PlayURI", DefaultPlayUri)
		JobSuccesfull = true
		ResetCursor = false
		JobSuccesfull = findCommand.apply(SonosMode, "MultiroomTarget")
		if(JobSuccesfull == true) {
			SonosMode.set(((SonosModeCursor).intValue), "MultiroomVolume")
			MultiroomForced = true
			ResetCursor = true	
			}			
		}
	//Play Ende
	
	
	//Remove Start 
	if(SonosMode.get((SonosModeCursor).intValue)=="Remove") {
		SonosControlCursor = 0
		JobSuccesfull = true
		while(SonosControlCursor < SonosControl.size()) {
			CursorPointer = getPlayerID.apply(SonosPlayer, SonosControl.get(SonosControlCursor.intValue))
			Cursor = getPlayerID.apply(SonosUUID, SonosCoordinatorList.get(CursorPointer.intValue))
			if(Cursor == CursorPointer) {
				SonosCommand = "ON"
				logInfo("SonosMultiroom.Service", "Remove Zoneplayer " + SonosControl.get((SonosControlCursor).intValue) + ".")
				setUpdate.apply(SonosControl.get((SonosControlCursor).intValue) + "_StandAlone", SonosCommand)
				SonosCoordinatorList.set(((Cursor).intValue), SonosUUID.get((Cursor).intValue))
				} else{
				SonosCommand = SonosUUID.get((CursorPointer).intValue)
				logInfo("SonosMultiroom.Service", "Remove Zoneplayer " + SonosPlayer.get((CursorPointer).intValue) + ".")
				setUpdate.apply(SonosPlayer.get((Cursor).intValue) + "_Remove", SonosCommand)
				SonosCoordinatorList.set(((Cursor).intValue), SonosUUID.get((Cursor).intValue))
				}	
			SonosControlCursor = SonosControlCursor+1
			}
		}
		//Remove Ende
	if(ResetCursor == true ) {
		SonosModeCursor = SonosModeCursor-1
		ResetCursor = false
		}
	SonosModeCursor = SonosModeCursor+1
	}
if(AutoremoveServiceRunning == true) {
	if(PlayerStatusUpdateLock.isLocked) {
		Thread::sleep(2000)
		if(PlayerStatusUpdateLock.isLocked) {
			PlayerStatusUpdateLock.unlock()
			}
		}
	}
if(MultiroomVolumeServiceRunning == true) {
	MultiroomVolumeServiceRunning = false
	if(MultiroomVolumeLock.isLocked()) {
		MultiroomVolume.unlock()
		}
	logInfo("SonosMultiroom.Service", "MultiroomVolume Service unlocked.")
	Cursor = 0
	logInfo("SonosMultiroom.Service", "MultiroomVolume Service refreshes the volume index.")
	while(Cursor < SonosPlayer.size()) {
		if(MultiroomVolumeAvailable.get(Cursor.intValue) != -1) {
			SonosCommand = "Sonos" + SonosPlayer.get((Cursor).intValue) + "_Volume"
			val ImportVolume = gSonosVolume.members.findFirst[ i | i.name == SonosCommand ]
			MultiroomVolumeAvailable.set(((Cursor).intValue), (Integer::parseInt(ImportVolume.state.toString)))	
			}
		Cursor = Cursor+1
		}
	}
logInfo("SonosMultiroom.Service", "Multiroom Service inactiv.")	
end


6 Likes

Part two of the rule, just add it in the same rule file:


rule "Sonos Multiroom Volume Service"
when 
Item gSonosVolume changed
then
if(!MultiroomVolumeLock.isLocked && MultiroomVolumeServiceRunning == false){
	logInfo("SonosMultiroom.Service", "MultiroomVolume Service locked.")
	MultiroomVolumeServiceRunning = true
	MultiroomVolumeLock.lock()
	try {
		Thread::sleep(1000)
		var ArrayList<String> MultiroomVolumeCoordinatorList = newArrayList("empty")
		var List<Integer> MultiroomVolumeCurrentVolume = newArrayList(0)
		var Number VolumeCursor = 0
		var Number VolumeCursor2 = 0
		var String MultiroomVolumeCommand
		var Boolean JobSuccesfull = prerequiremets.apply("run")
		if(JobSuccesfull == false) {
			if(MultiroomVolumeAvailable.size() < SonosPlayer.size()) {
				MultiroomVolumeAvailable = initMultiroomVolumeAvailable.apply(SonosPlayer)
				}
			if(MultiroomVolumeOffset.size() < SonosPlayer.size()) {
				MultiroomVolumeOffset = initMultiroomVolumeOffset.apply(SonosPlayer)
				}
			VolumeCursor = 0
			while(VolumeCursor < SonosPlayer.size()) {
				MultiroomVolumeCommand = "Sonos" + SonosPlayer.get((VolumeCursor).intValue) + "_Coordinator"
				val ImportCoordinator = gSonos.members.findFirst[ i | i.name == MultiroomVolumeCommand ]
				MultiroomVolumeCoordinatorList.add(((VolumeCursor).intValue), ImportCoordinator.state)
				MultiroomVolumeCommand = "Sonos" + SonosPlayer.get((VolumeCursor).intValue) + "_Volume"
				val ImportVolume = gSonosVolume.members.findFirst[ i | i.name == MultiroomVolumeCommand ]
				MultiroomVolumeCurrentVolume.add(((VolumeCursor).intValue), (Integer::parseInt(ImportVolume.state.toString)))	
				VolumeCursor = VolumeCursor+1
				}
			VolumeCursor = 0	
			while(VolumeCursor < SonosPlayer.size()) {
				if(MultiroomVolumeCurrentVolume.get((VolumeCursor).intValue) != MultiroomVolumeAvailable.get((VolumeCursor).intValue)) {
					if(MultiroomVolumeAvailable.get((VolumeCursor).intValue) >= 0) {
						VolumeCursor2 = 0
						while(VolumeCursor2 < SonosPlayer.size()) {
							if(MultiroomVolumeCoordinatorList.get((VolumeCursor).intValue) == MultiroomVolumeCoordinatorList.get((VolumeCursor2).intValue)) {
								if(MultiroomVolumeAvailable.get((VolumeCursor).intValue) >= 0) {
									var Number MultiroomVolumeSetVolume 
									MultiroomVolumeSetVolume = (((MultiroomVolumeCurrentVolume.get((VolumeCursor).intValue)/MultiroomVolumeOffset.get((VolumeCursor).intValue))*MultiroomVolumeOffset.get((VolumeCursor2).intValue)).intValue)
									MultiroomVolumeCommand = (MultiroomVolumeSetVolume).toString
									if(MultiroomVolumeSetVolume != MultiroomVolumeCurrentVolume.get((VolumeCursor2).intValue)) {
										logInfo("SonosMultiroom.Service", "MultiroomVolume Service changes the volume of Zone " + SonosPlayer.get((VolumeCursor2).intValue) + ", because Zone " + SonosPlayer.get((VolumeCursor).intValue) + " changed volume.")
										setUpdate.apply(SonosPlayer.get((VolumeCursor2).intValue) + "_Volume", MultiroomVolumeCommand)
										MultiroomVolumeCurrentVolume.set(((VolumeCursor2).intValue), (Integer::parseInt(MultiroomVolumeCommand)))	
										MultiroomVolumeAvailable.set(((VolumeCursor2).intValue), (Integer::parseInt(MultiroomVolumeCommand)))	
										}
									}
								}
							VolumeCursor2 = VolumeCursor2+1
							}
						}
					}
				VolumeCursor = VolumeCursor+1
				}
			}
		Thread::sleep(1000)
		}
	finally{
		MultiroomVolumeLock.unlock()
		MultiroomVolumeServiceRunning = false
		if(!MultiroomVolumeLock.isLocked){
			logInfo("SonosMultiroom.Service", "Multiroom Volume Service unlocked.")
			}
		}
	}
end


rule "Sonos Player Status Update Service"
when
Item gSonosState changed or 
Item SonosMultiroomUpdatePlayers received update ON
then
var Boolean AllPlayersOFF = true
if(triggeringItem.name == SonosMultiroomUpdatePlayers) {
	AutoremoveTimer.cancel()
	AutoremoveTimer = null
	}
if(!PlayerStatusUpdateLock.isLocked){
	logInfo("SonosMultiroom.Service", "Player Status Update Service locked.")
	PlayerStatusUpdateLock.lock()
	try {
		//Initialisiere Variablen
		var DateTime PlayerStatusDateTime = now
		var Boolean JobSuccesfull = true
		var Number PlayerStatusCounter
		var String PlayerStatusCommand

		JobSuccesfull = prerequiremets.apply("run")
		if(JobSuccesfull == false) {
			if(PlayerStatusUpdateState.size() < SonosPlayer.size()) {
				PlayerStatusUpdateState = initPlayerStatusUpdateState.apply(SonosPlayer)
				}
			PlayerStatusCounter = 0
			while(PlayerStatusCounter < SonosPlayer.size()) {
				PlayerStatusCommand = "Sonos" + SonosPlayer.get((PlayerStatusCounter).intValue) + "_Control"
				val ImportControl = gSonos.members.findFirst[ i | i.name == PlayerStatusCommand ]
				if(ImportControl.state == PLAY) {
					AllPlayersOFF = false
					PlayerStatusUpdateState.set((PlayerStatusCounter.intValue), 0)
					}
				if(ImportControl.state != PLAY) {
					PlayerStatusCommand = "Sonos" + SonosPlayer.get((PlayerStatusCounter).intValue) + "_Coordinator"
					val ImportCoordinator = gSonos.members.findFirst[ i | i.name == PlayerStatusCommand ]
					if(ImportCoordinator.state == SonosUUID.get(PlayerStatusCounter.intValue)) {			
						PlayerStatusUpdateState.set((PlayerStatusCounter.intValue), -1)
						}
					if(ImportCoordinator.state != SonosUUID.get(PlayerStatusCounter.intValue)) {	
						if(PlayerStatusUpdateState.get(PlayerStatusCounter.intValue) > 0) {
							if(PlayerStatusUpdateState.get(PlayerStatusCounter.intValue) <= PlayerStatusDateTime.getSecondOfDay || PlayerStatusUpdateState.get(PlayerStatusCounter.intValue) > 86399) {
								var Number PlayerStatusCounter2 = getPlayerID.apply(SonosUUID, ImportCoordinator.state)
								logInfo("SonosMultiroom.Service", "Autoremove removes the inactive Zoneplayer " + SonosPlayer.get((PlayerStatusCounter).intValue) + ".")
								setUpdate.apply(SonosPlayer.get((PlayerStatusCounter2).intValue) + "_Remove", SonosUUID.get(PlayerStatusCounter.intValue))
								PlayerStatusUpdateState.set((PlayerStatusCounter.intValue), -1)
								PlayerStatusCounter2 = SonosPlayer.size()
								}
									
							}
						else {
							JobSuccesfull = false
							PlayerStatusUpdateState.set((PlayerStatusCounter.intValue), (PlayerStatusDateTime.getSecondOfDay+60))
							}
						}
					}				
				PlayerStatusCounter = PlayerStatusCounter+1
				}
			if(AllPlayersOFF == true && JobSuccesfull == true && AutoremoveTimer !== null) {
				AutoremoveTimer.cancel()
				AutoremoveTimer = null
				}
			if(JobSuccesfull  == false && AutoremoveTimer === null) {
				PlayerStatusCounter = 0
				var Number AutoremoveNextTimer
				while(PlayerStatusCounter < SonosPlayer.size()) {
					if(PlayerStatusUpdateState.get(PlayerStatusCounter.intValue) > PlayerStatusDateTime.getSecondOfDay) {
						if(AutoremoveNextTimer === null) {
							AutoremoveNextTimer = PlayerStatusUpdateState.get(PlayerStatusCounter.intValue)
							}
						if(PlayerStatusUpdateState.get(PlayerStatusCounter.intValue) < AutoremoveNextTimer && AutoremoveNextTimer !== null) {
							AutoremoveNextTimer = PlayerStatusUpdateState.get(PlayerStatusCounter.intValue)
							}
						}
					PlayerStatusCounter = PlayerStatusCounter + 1
					}
				if(AutoremoveNextTimer !== null) {
					AutoremoveNextTimer = (AutoremoveNextTimer-PlayerStatusDateTime.getSecondOfDay).intValue
					logInfo("SonosMultiroom.Service", "Player Update Service will refresh in " +  AutoremoveNextTimer + " seconds.")			
					AutoremoveTimer = createTimer(now.plusSeconds(AutoremoveNextTimer), [|
						SonosMultiroomUpdatePlayers.postUpdate(ON)
						AutoremoveTimer = null
						])
					}
				}	
			}
		}
	finally{
		PlayerStatusUpdateLock.unlock()
		if(!PlayerStatusUpdateLock.isLocked){
			logInfo("SonosMultiroom.Service", "Player Status Update Service unlocked.")
			}
		}
	}
end


rule "Update MultiroomVolume Settings"
when
Item gSonosMultiroom changed
then
var Boolean JobsDone = false
var Number MultiroomStatusCursor
var String MultiroomStatusCommand
JobsDone = prerequiremets.apply("run")
if(JobsDone == false) {
	logInfo("SonosMultiroom.Service", "Refresh Multiroom Volume settings from items.")
	if(MultiroomVolumeAvailable.size() < SonosPlayer.size()) {
		MultiroomVolumeAvailable = initMultiroomVolumeAvailable.apply(SonosPlayer)
		JobsDone = true
		}
	if(JobsDone == false) {
		MultiroomStatusCursor = 0
		while(MultiroomStatusCursor < SonosPlayer.size()) {
			MultiroomStatusCommand = "Sonos" + SonosPlayer.get((MultiroomStatusCursor).intValue) + "_MultiroomVolume"
			val ImportMultiroom = gSonosMultiroom.members.findFirst[ i | i.name == MultiroomStatusCommand ]
			if(ImportMultiroom.state == ON) {
				MultiroomStatusCommand = "Sonos" + SonosPlayer.get((MultiroomStatusCursor).intValue) + "_Volume"
				val ImportVolume = gSonosVolume.members.findFirst[ i | i.name == MultiroomStatusCommand ]
				MultiroomVolumeAvailable.set(((MultiroomStatusCursor).intValue), (Integer::parseInt(ImportVolume.state.toString)))	
				} else {
				if(MultiroomVolumeAvailable.get(MultiroomStatusCursor.intValue) != -1) {
					logInfo("SonosMultiroom.Service", "Multiroom Volume is disabled in Zone " + SonosPlayer.get(MultiroomStatusCursor.intValue) + ".")
					MultiroomVolumeAvailable.set(((MultiroomStatusCursor).intValue), -1)
					}
				}		
			MultiroomStatusCursor = MultiroomStatusCursor+1
			}
		}
	}
end

rule "Update Multiroom Offset Settings"
when
Item gSonosMultiroomOffset changed
then
var Boolean JobSuccesfull = false
var Number MultiroomOffsetCursor
var String MultiroomOffsetCommand
JobSuccesfull = prerequiremets.apply("run")
if(JobSuccesfull == false) {
	logInfo("SonosMultiroom.Service", "Refresh Multiroom offset settings of items.")
	if(MultiroomVolumeAvailable.size() < SonosPlayer.size()) {
		MultiroomVolumeAvailable = initMultiroomVolumeAvailable.apply(SonosPlayer)
		}
	if(MultiroomVolumeOffset.size() < SonosPlayer.size()) {
		MultiroomVolumeOffset = initMultiroomVolumeOffset.apply(SonosPlayer)
		}
	if(JobSuccesfull == false) {
		if(MultiroomVolumeLock.isLocked) {
			Thread::sleep(1000)
			}
		if(!MultiroomVolumeLock.isLocked) {
			logInfo("SonosMultiroom.Service", "MultiroomVolume Service locked.")
			}
		MultiroomVolumeLock.lock()
		MultiroomVolumeServiceRunning = true
		try {
			MultiroomOffsetCursor = 0
			while(MultiroomOffsetCursor < SonosPlayer.size()) {
				if(MultiroomVolumeAvailable.get(MultiroomOffsetCursor.intValue) != -1) {
					MultiroomOffsetCommand = "Sonos" + SonosPlayer.get((MultiroomOffsetCursor).intValue) + "_Offset"
					val ImportOffset = gSonosMultiroomOffset.members.findFirst[ i | i.name == MultiroomOffsetCommand ]
					if(MultiroomVolumeOffset.get(MultiroomOffsetCursor.intValue) != (Integer::parseInt(ImportOffset.state.toString))) {
						logInfo("SonosMultiroom.Service", "Offset of Zone " + SonosPlayer.get(MultiroomOffsetCursor.intValue) + " will be changed.")
						var Number MultiroomOffsetValue = (MultiroomVolumeAvailable.get(MultiroomOffsetCursor.intValue)/MultiroomVolumeOffset.get(MultiroomOffsetCursor.intValue)*(Integer::parseInt(ImportOffset.state.toString))).intValue
						MultiroomVolumeOffset.set(((MultiroomOffsetCursor).intValue), (Integer::parseInt(ImportOffset.state.toString)))	
						MultiroomOffsetCommand = MultiroomOffsetValue.toString
						setUpdate.apply(SonosPlayer.get(MultiroomOffsetCursor.intValue) + "_Volume", MultiroomOffsetCommand)
						MultiroomVolumeAvailable.set(((MultiroomOffsetCursor).intValue), MultiroomOffsetValue)	
						}	
					}
				MultiroomOffsetCursor = MultiroomOffsetCursor+1
				}
			}
		finally{
			MultiroomVolumeLock.unlock()
			MultiroomVolumeServiceRunning = false
			if(!MultiroomVolumeLock.isLocked){
				logInfo("SonosMultiroom.Service", "Multiroom Volume Service unlocked.")
				}
			}	
		}
	}
end
2 Likes

Can you post an image of your site map?

Sounds like a good solution to the sonos app

I have just uploaded them

@buschif4 - OFF TOPIC:
Hah! Funny you have Yellow Claw playing on your screenshots. Saw them recently live in Cologne… :wink:

Hi Flo

Thank you so much for the items, sitemap and rule. I just started to use it in my setup. and there were two things bothering me:

  1. I did get some errors while playing the last In.tune radio station:
21:28:56.430 [INFO ] [smarthome.event.ItemStateChangedEvent] - SonosLivingroom_Control changed from PLAY to PAUSE
21:28:56.486 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentTitle' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:56.602 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentTitle' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:56.672 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentTitle' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:56.747 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item for widget org.eclipse.smarthome.model.sitemap.Text
21:28:56.807 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentArtist' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:56.874 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentArtist' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:56.943 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentArtist' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:57.012 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item for widget org.eclipse.smarthome.model.sitemap.Text
21:28:57.072 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentAlbum' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:57.163 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentAlbum' for widget org.eclipse.smarthome.model.sitemap.Text
21:28:57.266 [ERROR] [.ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item 'SonosLivingroom_CurrentAlbum' for widget org.eclipse.smarthome.model.sitemap.Text
21:29:33.447 [INFO ] [e.model.script.SonosMultiroom.Service] - MultiroomVolume Service locked.
21:29:34.528 [INFO ] [e.model.script.SonosMultiroom.Service] - Initialise Sonos Multiroom Service
21:29:34.816 [INFO ] [e.model.script.SonosMultiroom.Service] - Multiroom Volume is inactive in Zone Livingroom.
21:29:34.931 [INFO ] [e.model.script.SonosMultiroom.Service] - Multiroom Volume Service unlocked.
  1. I am using Visual Studio Code with OpenHab extension to edit the rules. There are currently about 29 warnings / errors reported, but I am not sure on the severity of those. In the screenshot below I’ve just selected one Type mismatch warning:

Any ideas and recommendations would be very much appreciated.

Cheers
Thomas

Awesome work here! I am having some problems controlling as a group. Also, is there a way to display what device is actually being controlled? Or display what the groups are?

I too am seeing all the errors, but it seems to work, with a few issues…

I have problems adding a room to group - What i’m I missing?

21:58:56.292 [INFO ] [smarthome.event.ItemCommandEvent     ] - Item 'SonosMultiroom' received command MultiroomTarget/DefaultVolume/Devices=Livingroom/Bedroom
21:58:56.292 [INFO ] [e.model.script.SonosMultiroom.Service] - Multiroom Steuerung active
21:58:56.296 [ERROR] [untime.internal.engine.RuleEngineImpl] - Rule 'Sonos Multiroom': For input string: "NULL"
'''

This looks quite cool. I just got my first Sonos ONE speaker and will be going thru the pain of understanding Sonos, and Alexa and how to manage Alexa and what Alexa manages vs what OpenHAB manages, and the Skills vs the Bindings. I digress. With this, is the expectation you have installed the Sonos Binding 2.4 before using this cool looking sitemap capabilities? Would be really cool if I could do two things:

  1. Play my local mp3 library and playlists.
  2. Select from my library. I assume the above is based on it’s already playing and now you can do next or previous but have no real control of what you want to select from your library?

I know number 2 above is a large expectation. Wondering if your sitemap can branch out to an App on your phone for music so even though it’s not built in, can go back and forth on your phone between the sitemap and a music app. In my case iOS.

Thanks.

JR

I’m getting the following when I put this all together. Did I miss something?

2019-02-15 18:30:11.340 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Sonos Multiroom Volume Service’: The name ‘MultiroomVolumeLock’ cannot be resolved to an item or type; line 505, column 5, length 19
2019-02-15 18:30:11.341 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Sonos Multiroom Volume Service’: The name ‘MultiroomVolumeLock’ cannot be resolved to an item or type; line 505, column 5, length 19
2019-02-15 18:30:11.343 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Sonos Player Status Update Service’: The name ‘triggeringItem’ cannot be resolved to an item or type; line 579, column 4, length 14

Thanks.

JR

Also showing the below in the logs:

2019-02-15 20:50:42.510 [WARN ] [arthome.core.items.dto.ItemDTOMapper] - State ‘PLAYING’ is not valid for a group item with base type ‘Number’
2019-02-15 20:50:42.512 [WARN ] [arthome.core.items.dto.ItemDTOMapper] - State ‘ON’ is not valid for a group item with base type ‘Number’
2019-02-15 20:50:42.551 [WARN ] [arthome.core.items.dto.ItemDTOMapper] - State ‘PLAYING’ is not valid for a group item with base type ‘Number’
2019-02-15 20:50:42.552 [WARN ] [arthome.core.items.dto.ItemDTOMapper] - State ‘ON’ is not valid for a group item with base type ‘Number’
2019-02-15 20:51:07.574 [WARN ] [me.internal.engine.RuleContextHelper] - Variable ‘MultiroomVolumeAvailable’ on rule file ‘sonosplayer.rules’ cannot be initialized with value ‘newArrayList()’: The name ‘newArrayList’ cannot be resolved to an item or type; line 21, column 46, length 15
2019-02-15 20:51:07.577 [WARN ] [me.internal.engine.RuleContextHelper] - Variable ‘PlayerStatusUpdateLock’ on rule file ‘sonosplayer.rules’ cannot be initialized with value ‘org.eclipse.xtext.xbase.impl.XConstructorCallImplCustom@47a36921 (invalidFeatureIssueCode: null, validFeature: false, explicitConstructorCall: true, anonymousClassConstructorCall: false)’: An error occurred during the script execution: null
2019-02-15 20:51:07.609 [WARN ] [me.internal.engine.RuleContextHelper] - Variable ‘MultiroomVolumeLock’ on rule file ‘sonosplayer.rules’ cannot be initialized with value ‘org.eclipse.xtext.xbase.impl.XConstructorCallImplCustom@3f377882 (invalidFeatureIssueCode: null, validFeature: false, explicitConstructorCall: true, anonymousClassConstructorCall: false)’: An error occurred during the script execution: null
2019-02-15 20:51:07.711 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Sonos Multiroom Volume Service’: The name ‘MultiroomVolumeLock’ cannot be resolved to an item or type; line 506, column 5, length 19
2019-02-15 20:51:07.711 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Sonos Multiroom Volume Service’: The name ‘MultiroomVolumeLock’ cannot be resolved to an item or type; line 506, column 5, length 19
2019-02-15 20:51:07.713 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Sonos Player Status Update Service’: The name ‘triggeringItem’ cannot be resolved to an item or type; line 580, column 4, length 14
2019-02-15 20:51:08.701 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item for widget org.eclipse.smarthome.model.sitemap.Text
2019-02-15 20:51:08.702 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item ‘SonosFamilyroom_CurrentTitle’ for widget org.eclipse.smarthome.model.sitemap.Text
2019-02-15 20:51:08.703 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item ‘SonosFamilyroom_CurrentTitle’ for widget org.eclipse.smarthome.model.sitemap.Text
2019-02-15 20:51:08.703 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item ‘SonosFamilyroom_CurrentTitle’ for widget org.eclipse.smarthome.model.sitemap.Text
2019-02-15 20:51:08.705 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item for widget org.eclipse.smarthome.model.sitemap.Text
2019-02-15 20:51:08.705 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item ‘SonosFamilyroom_CurrentAlbum’ for widget org.eclipse.smarthome.model.sitemap.Text
2019-02-15 20:51:08.706 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item ‘SonosFamilyroom_CurrentAlbum’ for widget org.eclipse.smarthome.model.sitemap.Text
2019-02-15 20:51:08.706 [ERROR] [ui.internal.items.ItemUIRegistryImpl] - Cannot retrieve item ‘SonosFamilyroom_CurrentAlbum’ for widget org.eclipse.smarthome.model.sitemap.Text

Hi
Does anyone put it in the files files. So you can see what has been linked to in the different channel

I just can’t figure out the different String

Hi @buschif4,

I would have some questions regarding you sonos solution.

How do you start playing? Do you start you tracks from other apps like spotify or tuneIn or do you have them integrated into your solution as well? From spotify app it is possible to play tunIn etc. Is this possible as well to play a favorite radio stream or sth. like that?

Kindly,
Woogi

Hi @buschif4 - this looks like exactly what I’m trying to do! Your third screenshot " Multiroom Control options" doesn’t seem to work - could you repost? Cheers