Raumfeld/Teufel WLAN rule/HABPanel: maybe it helps someone

after there is no nice and smooth way to control my raumfelds, i started with a rule - and it grows - but i will post it, maybe it helps someone

p.s. bugs are made to find them :wink:

setup:

  • raumserver at http://192.168.1.205:8080
  • 4 RFs named AUD_K, AUD_B, …
  • HABPanel
  • OpenHAB 2.3

scope:

  • activate/deactivate RFs in seperate zones or multiroom with less clicks
  • set a fav-internetstations
  • change volume

what you get:

  • click on station, click on the first RF and the RF will play the station (1)
  • click on the next RF and the RF will play also the same station (multiroom) (2)
  • click on an other station and click on the 3rd RF, this RF will play the new station (2+1)
  • doubleclick on station, all RFs will play them same station (3)

section::declaration

var Integer nPHYS_DEVICES = 4    // number of physical devices
var deviceName = newArrayList()   

var guiVolumeItem = newArrayList()
var guiActiveItem = newArrayList()
var guiStateItem  = newArrayList()

var Integer AUD_K_Index = 0        // change this section
var Integer AUD_WZ_Index = 1
var Integer AUD_B_Index = 2
var Integer AUD_WC_Index = 3

var Boolean debug = false
var Boolean deviceStateOK = false

var Integer volumeStep = 2

var deviceState = newArrayList('','','','')

var String delimiter = ";"
var String actionDelimiter = "#"
var String errorString = "failed"
var String actionQueueON  = "speakerON"
var String actionQueueOFF = "speakerOFF"
var String actionZone = "zoneMod"

var getRequest = newArrayList('','','','')

var rsURL = "http://192.168.1.205:8080/raumserver/controller/"   // put in your raumserver-url

var Integer changeDelay = 1000
var Integer noChangeDelay = 1001
var Integer requestDelay
var Integer timeOut = 15000

var Timer requestTimer

var String safe_GLOBAL_INT_stationURI = ""

section::init

rule "GM_init"
when
        System started
then
        postUpdate (GLOBAL_AUD_runningCycle, 0)   // number

        logInfo ("AUD","init::started ...")
        postUpdate (GLOBAL_INT_updateRF, OFF)      // switch

        Thread::sleep (5000)

        postUpdate (GLOBAL_AUD_actionQueue, "")    // string

        safe_GLOBAL_INT_stationURI = GLOBAL_INT_stationURI.state.toString

        guiVolumeItem.add (K_INT_speakerVolume)     // number
        guiActiveItem.add (K_INT_speakerActive)     // switch
        guiStateItem.add  (K_INT_speakerState)      // string
        deviceName.add ("AUD_K")                    // rename it as your RFs

        guiVolumeItem.add (WZ_INT_speakerVolume)
        guiActiveItem.add (WZ_INT_speakerActive)
        guiStateItem.add  (WZ_INT_speakerState)
        deviceName.add ("AUD_WZ")

        guiVolumeItem.add (BAD_INT_speakerVolume)
        guiActiveItem.add (BAD_INT_speakerActive)
        guiStateItem.add  (BAD_INT_speakerState)
        deviceName.add ("AUD_B")

        guiVolumeItem.add (WC_INT_speakerVolume)
        guiActiveItem.add (WC_INT_speakerActive)
        guiStateItem.add  (WC_INT_speakerState)
        deviceName.add ("AUD_WC")

        Thread::sleep (changeDelay)
        postUpdate(GLOBAL_INT_updateRF, ON)

        logInfo ("AUD","init::done")

section::getDeviceState

rule "GM_getDeviceStates"
when
        Item GLOBAL_INT_updateRF changed to ON
then		

	while (GLOBAL_INT_updateRF.state == ON)
	{

	postUpdate (GLOBAL_AUD_runningCycle, (GLOBAL_AUD_runningCycle.state as DecimalType) + 1)

	val tempDeviceState = newArrayList()
	var tempSortedDeviceState = newArrayList()

	var String zoneConfigRequest = sendHttpGetRequest(rsURL + "getZoneConfig")


	if ((zoneConfigRequest !== null) && (transform ("JSONPATH","$.error", zoneConfigRequest) == "false") && (zoneConfigRequest.contains ('name')))
	{
		var Integer nRooms = Integer::parseInt(transform("JSONPATH","$.data.zoneConfig.$.numRooms", zoneConfigRequest).toString)

		if (zoneConfigRequest.contains ("zones"))
		{
			var Integer nActiveZones = Integer::parseInt(transform("JSONPATH","$.data.zoneConfig.zones[0].zone.length()", zoneConfigRequest).toString)

			var iZone = -1
			while ((iZone=iZone+1) < nActiveZones)
			{
				var Integer nRoomsInZone = Integer::parseInt(transform("JSONPATH","$.data.zoneConfig.zones[0].zone[" + iZone.toString + "].room.length()", zoneConfigRequest).toString)
				var String zone = transform("JSONPATH","$.data.zoneConfig.zones[0].zone[" + iZone.toString + "].$.udn", zoneConfigRequest)
			
				var iRoom = -1
				while ((iRoom=iRoom+1) < nRoomsInZone)
				{
					var String roomName = transform("JSONPATH","$.data.zoneConfig.zones[0].zone[" + iZone.toString + "].room[" + iRoom.toString + "].$.name",zoneConfigRequest).toString
					var String roomUDN = transform("JSONPATH","$.data.zoneConfig.zones[0].zone[" + iZone.toString + "].room[" + iRoom.toString + "].$.udn",zoneConfigRequest).toString
					var String powerState = transform ("JSONPATH","$.data.zoneConfig.zones[0].zone[" + iZone.toString + "].room[" + iRoom.toString + "].$.powerState", zoneConfigRequest) 

					var deviceIndex = -1
					while (((deviceIndex=deviceIndex+1) < nPHYS_DEVICES) && (roomName != deviceName.get(deviceIndex).toString)) {}

					var Integer roomsInState = Integer::parseInt(transform ("JSONPATH","$.data.zoneConfig.zones[0].zone[" + iZone.toString + "].room.length()", zoneConfigRequest).toString)
					var String rendererStateRequest = null

					if (!powerState.contains ("AUTOMATIC_STANDBY") && !powerState.contains ("MANUAL_STANDBY")) 
					{
						rendererStateRequest = sendHttpGetRequest(rsURL + "getRendererState?id=" + roomName)
					} else {
						tempDeviceState.add (deviceIndex.toString + delimiter + roomName + delimiter + powerState + delimiter + "null" + delimiter + "STOPPED" + delimiter + "null" + delimiter + "null" + delimiter + "1")
					}

					if ((rendererStateRequest !== null) && (transform ("JSONPATH", "$.error", rendererStateRequest) == "false") && (rendererStateRequest.contains ('name') && (rendererStateRequest.contains('RoomVolumes'))))
					{
						var thisRoom = -1
						while (((thisRoom=thisRoom+1) < roomsInState) && (transform ("JSONPATH","$.data[0].rooms["+thisRoom.toString+"].name", rendererStateRequest).toString.contains(deviceName.get(deviceIndex).toString) == false)) {}
					
						var String transportState = transform ("JSONPATH","$.data[0].TransportState", rendererStateRequest)
						var String currentTrackURI = transform ("JSONPATH","$.data[0].CurrentTrackURI", rendererStateRequest)
						var String volumeState = transform ("JSONPATH","$.data[0].RoomVolumes", rendererStateRequest).toString	
		
						var String roomVolume

						if (volumeState.contains (roomUDN))
						{
							val roomVolumes = volumeState.split(",")

							var iVolume = -1

							while (((iVolume=iVolume+1) < roomVolumes.length) && (!roomVolumes.get(iVolume).toString.contains(roomUDN))) {}
		
							roomVolume = roomVolumes.get(iVolume).toString.replace(roomUDN,'').replace('=','')
							var Integer vol = Integer::parseInt(roomVolume.toString)
								
							val volItem = guiVolumeItem.get(deviceIndex) as NumberItem
							
							if (((volItem.state as DecimalType).intValue != vol) && (vol > 0) && (transportState == "PLAYING") && (requestDelay == noChangeDelay)) postUpdate (volItem, vol)

						} else {
							roomVolume = "-1"
						}
	
						tempDeviceState.add (deviceIndex.toString + delimiter + roomName + delimiter + powerState + delimiter + zone + delimiter + transportState + delimiter + currentTrackURI + delimiter + roomVolume + delimiter + roomsInState.toString)
					} else {deviceStateOK = false}
				} 
			} 
		}

		if ((tempDeviceState.size < nRooms) && (zoneConfigRequest.contains ('unassignedRooms')))
		{

			var Integer nUnassignedRooms = Integer::parseInt(transform("JSONPATH","$.data.zoneConfig.unassignedRooms[0].room.length()", zoneConfigRequest).toString)	

			var iRoom = -1
			while ((iRoom=iRoom+1) < nUnassignedRooms)
			{
				var String roomName = transform("JSONPATH","$.data.zoneConfig.unassignedRooms[0].room[" + iRoom.toString + "].$.name", zoneConfigRequest).toString
				var String powerState = transform("JSONPATH","$.data.zoneConfig.unassignedRooms[0].room[" + iRoom.toString + "].$.powerState", zoneConfigRequest).toString

				var deviceIndex = -1
				while (((deviceIndex=deviceIndex+1) < nPHYS_DEVICES) && (roomName != deviceName.get(deviceIndex).toString)) {}

				tempDeviceState.add (deviceIndex.toString + delimiter + roomName + delimiter + powerState + delimiter + "null" + delimiter + "STOPPED" + delimiter + "null" + delimiter + "null" + delimiter + "1")
			}
		}

		if (tempDeviceState.size == nPHYS_DEVICES)
		{
			var i = -1
			while ((i=i+1) < nPHYS_DEVICES)
			{
				var j = -1
				while ((j=j+1) < nPHYS_DEVICES)
				{
					var str = tempDeviceState.get(j).toString
					if (str.substring(0,1) == i.toString) tempSortedDeviceState.add (str.substring(2,str.length))
				}
			}
			deviceStateOK = true
		} else {deviceStateOK = false}
	} else {deviceStateOK = false}

	if (deviceStateOK)
	{
		var n = -1;
		while ((n=n+1) < nPHYS_DEVICES)
		{				
			if (deviceState.get(n).toString.contains(tempSortedDeviceState.get(n).toString) == false) {logInfo ("AUD",tempSortedDeviceState.get(n).toString)}
                        
			val stateItem = deviceState.get(n).toString.split(delimiter)
			if (stateItem.length >= 3)
			{
				val gStateItem = guiStateItem.get(n) as StringItem

				var String powerState = stateItem.get(1).toString
				var String zone = stateItem.get(2).toString
				var String transportState = stateItem.get(3).toString


				var String shortZone
				if (zone.contains ("-"))
				{
					var tempZone = zone.split ("-")
					shortZone = tempZone.get(0).toString
				} else {
					shortZone = "uuid:--------"
				}


				postUpdate (gStateItem, powerState + ", " + transportState + ", " + shortZone)
			}


		}
		deviceState = tempSortedDeviceState
	}
	
	Thread::sleep(1000)


	}
end

section::speakerON

rule "GM_speakerON"
when
        Item GLOBAL_AUD_actionQueue changed
then   
        if (GLOBAL_AUD_actionQueue.state.toString.length > 0)
        {
                var Integer i = -1
                val action = GLOBAL_AUD_actionQueue.state.toString.split (delimiter)

                if ((action.length > 0) && (action.get(0).toString.contains(actionQueueON)))
                {
			requestDelay = changeDelay
			var iState = 0
			while (((iState=iState+1) < (timeOut / requestDelay)) && (deviceStateOK == false)) 
			{
				logInfo ("AUD","speakerON::waiting for deviceStateOK ...")
				Thread::sleep (100)
			}

                        postUpdate (GLOBAL_AUD_actionQueue, "!" + GLOBAL_AUD_actionQueue.state.toString.substring (1, GLOBAL_AUD_actionQueue.state.toString.length))

                        logInfo ("AUD","speakerON::started action ..." + action.get(0).toString)

                        val items = action.get(0).toString.split (actionDelimiter)

                        var Integer ID = Integer::parseInt(items.get(1).toString)
			val volItem = guiVolumeItem.get(ID) as NumberItem
			var Integer safeVol = (volItem.state as DecimalType).intValue



			logInfo ("AUD","speakerON::deviceState: " + deviceState.get(ID).toString + ", safeVol: " + safeVol.toString)

                        var String newStationURI = items.get(2).toString

                        var Integer zoneMatchIndex = -1

                        val stateItem = deviceState.get(ID).toString.split(delimiter)

			var String powerState = stateItem.get(1).toString
			var String zone = stateItem.get(2).toString
			var String transportState = stateItem.get(3).toString
			var String uri = stateItem.get(4).toString


			if ((transportState != "PLAYING") || (powerState != "ACTIVE"))
			{
				var Integer nRoomsInZone = Integer::parseInt(stateItem.get(6).toString)

				var String matchTransportState = ""
				var String matchUri = ""
				var String matchZone = ""

				i = -1
				while (((i=i+1) < nPHYS_DEVICES) && (zoneMatchIndex == -1))
				{
					val stateItem = deviceState.get(i).toString.split(delimiter)

					matchTransportState = stateItem.get(3).toString
					matchUri = stateItem.get(4).toString
					matchZone = stateItem.get(2).toString

					if ((matchUri == newStationURI) && (matchTransportState == "PLAYING")) {zoneMatchIndex = i}
				}

				if (zoneMatchIndex != -1)			// zone found
				{
					logInfo ("AUD","speakerON::add " + deviceName.get(ID) + " to Zone " + matchZone)	
					sendHttpGetRequest(rsURL + "addToZone?id=" + deviceName.get(ID) + "&zoneID=" + matchZone)

					var i = 0
					while (((i=i+1) < (timeOut / requestDelay)) && (zone != matchZone))
					{
						val stateItem = deviceState.get(ID).toString.split(delimiter)
						zone = stateItem.get(2).toString

						logInfo ("AUD","speakerON::waiting for add " + deviceName.get(ID).toString + " to zone " + matchZone + " (" + zone + ")")
						Thread::sleep (requestDelay)
					}	
					sendHttpGetRequest(rsURL + "setVolume?value=" + safeVol.toString + "&id=" + deviceName.get(ID) + "&scope=room")

	                        } else {	// no zoneMatch
					if (zone == "null") // no zonematch and no zone
					{
						sendHttpGetRequest (rsURL + "createZone?id=" + deviceName.get(ID))
						var i = 0
						while (((i=i+1) < (timeOut / requestDelay)) && (zone == "null"))
						{
							val stateItem = deviceState.get(ID).toString.split(delimiter)
							zone = stateItem.get(2).toString

							logInfo ("AUD","speakerON::waiting on " + deviceName.get(ID).toString + " to get a own zone (" + zone + ")")
							Thread::sleep (requestDelay)
						}
						if (powerState != "ACTIVE")
						{
							sendHttpGetRequest (rsURL + "leaveStandby?id=" + deviceName.get(ID))
							var i = 0
							while (((i=i+1) < (timeOut / requestDelay)) && (powerState != "ACTIVE"))
							{
								val stateItem = deviceState.get(ID).toString.split(delimiter)
								powerState = stateItem.get(1).toString

								logInfo ("AUD","speakerON::waiting on " + deviceName.get(ID).toString + " to wakeup (" + powerState + ")")
								Thread::sleep (requestDelay)
							}
						}
						sendHttpGetRequest(rsURL + "setVolume?value=" + safeVol.toString + "&id=" + deviceName.get(ID) + "&scope=room")

					} else {				// no zonematch and into other zone
						if (nRoomsInZone > 1)		// with other rooms inside
						{
							sendHttpGetRequest (rsURL + "dropFromZone?id=" + deviceName.get(ID))
							var i = 0
							while (((i=i+1) < (timeOut / requestDelay)) && (zone != "null"))
							{
								val stateItem = deviceState.get(ID).toString.split(delimiter)
								zone = stateItem.get(2).toString	

								logInfo ("AUD","speakerON::waiting on " + deviceName.get(ID).toString + " to get dropped from zone (" + zone + ")")
								Thread::sleep (requestDelay)
							}

							sendHttpGetRequest (rsURL + "createZone?id=" + deviceName.get(ID))
							i = 0
							while (((i=i+1) < (timeOut / requestDelay)) && (zone == "null"))
							{
								val stateItem = deviceState.get(ID).toString.split(delimiter)
								zone = stateItem.get(2).toString

								logInfo ("AUD","speakerON::waiting on " + deviceName.get(ID).toString + " to get a own zone (" + zone + ")")
								Thread::sleep (requestDelay)
							}

						} else {
							if (powerState != "ACTIVE")
							{
								sendHttpGetRequest (rsURL + "leaveStandby?id=" + deviceName.get(ID))
								var i = 0
								while (((i=i+1) < (timeOut / requestDelay)) && (powerState != "ACTIVE"))
								{
									val stateItem = deviceState.get(ID).toString.split(delimiter)
									powerState = stateItem.get(1).toString

									logInfo ("AUD","speakerON::waiting on " + deviceName.get(ID).toString + " to wakeup (" + powerState + ")")
									Thread::sleep (requestDelay)
								}
							}
						}
					}
					logInfo ("AUD","speakerON::load  " + newStationURI + " to " + deviceName.get(ID))

					sendHttpGetRequest(rsURL + "loadUri?id=" + deviceName.get(ID) + "&value="+newStationURI)
				}

	                        i = -1
				transportState = stateItem.get(3).toString
				while (((i=i+1) < (timeOut / requestDelay)) && (transportState != "PLAYING"))
				{
					val stateItem = deviceState.get(ID).toString.split(delimiter)
					transportState = stateItem.get(3).toString	

	                                logInfo ("AUD","speakerON::waiting on " + deviceName.get(ID).toString + " for PLAYING-state ... (" + transportState + ")")
					Thread::sleep (requestDelay)
				}
	                        logInfo ("AUD","speakerON::" + deviceName.get(ID).toString + " -> " + transportState)

			} else {logInfo ("AUD","speakerON::transportState == PLAYING ... nothing to do ... done!")}

		       postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString.replace ("!" + action.get(0).toString.substring(1,action.get(0).toString.length) + delimiter,'')) 

			requestDelay = noChangeDelay
		}
	}
end

section::speakerOFF

rule "GM_speakerOFF"
when
        Item GLOBAL_AUD_actionQueue changed
then
        if (GLOBAL_AUD_actionQueue.state.toString.length > 0)
        {
                var Integer i = -1
                val action = GLOBAL_AUD_actionQueue.state.toString.split (delimiter)

                if ((action.length >0) && (action.get(0).toString.contains(actionQueueOFF)))
                {
			requestDelay = changeDelay
			var iState = 0
			while (((iState=iState+1) < (timeOut / requestDelay)) && (deviceStateOK == false)) 
			{
				logInfo ("AUD","speakerON::waiting for deviceStateOK ...")
				Thread::sleep (100)
			}

                        postUpdate (GLOBAL_AUD_actionQueue, "!" + GLOBAL_AUD_actionQueue.state.toString.substring (1, GLOBAL_AUD_actionQueue.state.toString.length))

                        val items = action.get(0).toString.split (actionDelimiter)                                                                                                                                                                 
			var Integer ID = Integer::parseInt(items.get(1).toString)
			val stateItem = deviceState.get(ID).split (delimiter)
			var String transportState = stateItem.get(3).toString
			var String powerState = stateItem.get(1).toString

			logInfo ("AUD","speakerOFF::deviceState: " + deviceState.get(ID).toString)


			if ((transportState == "PLAYING") && (powerState == "ACTIVE") && (transportState != "NO_MEDIA_PRESENT"))
			{

				var Integer nRoomsInZone = Integer::parseInt(stateItem.get(6).toString) 
				var String zone = stateItem.get(2).toString

				logInfo ("AUD","speakerOFF::started action ..." + action.get(0).toString)

				if (nRoomsInZone > 1)
				{
					logInfo ("AUD","speakerOFF::drop " + deviceName.get(ID) + " from Zone " + zone)
					sendHttpGetRequest (rsURL + "dropFromZone?id=" + deviceName.get(ID) + "&zoneId=" + zone)
				} else {
					logInfo ("AUD","speakerON::stop " + deviceName.get(ID))
					sendHttpGetRequest (rsURL + "stop?id=" + deviceName.get(ID))
				}
				
				i = -1
				while (((i=i+1) < (timeOut / requestDelay)) && (transportState != "STOPPED"))
				{
					val stateItem = deviceState.get(ID).toString.split(delimiter)
					transportState = stateItem.get(3).toString
					logInfo ("AUD","speakerOFF::waiting on " + deviceName.get(ID).toString + " for STOPPED-state ... (" + transportState + ")")

	                                Thread::sleep (requestDelay)
				}	
				
				logInfo ("AUD","speakerOFF::" + deviceName.get(ID).toString + " -> " + transportState + " ... done!")

			} else {logInfo ("AUD","speakerOFF::transportState != PLAYING or not ACTIVE... nothing to do ... done!")}

		       postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString.replace ("!" + action.get(0).toString.substring(1,action.get(0).toString.length) + delimiter,'')) 

			requestDelay = noChangeDelay


                }
        }

end

section::HABPanel

rule "K_INT_speakerActive"
when
        Item K_INT_speakerActive changed
then
	if (K_INT_speakerActive.state == ON)
	{
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString +  actionQueueON + actionDelimiter + AUD_K_Index.toString + actionDelimiter + GLOBAL_INT_stationURI.state.toString + delimiter)
	} else {
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString + actionQueueOFF + actionDelimiter + AUD_K_Index.toString + delimiter)
	}
end

rule "WZ_INT_speakerActive"
when
        Item WZ_INT_speakerActive changed
then
	if (WZ_INT_speakerActive.state == ON)
	{
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString +  actionQueueON + actionDelimiter + AUD_WZ_Index.toString + actionDelimiter + GLOBAL_INT_stationURI.state.toString + delimiter)
	} else {
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString + actionQueueOFF + actionDelimiter + AUD_WZ_Index.toString + delimiter)
	}
end

rule "BAD_INT_speakerActive"
when
        Item BAD_INT_speakerActive changed
then
	if (BAD_INT_speakerActive.state == ON)
	{
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString +  actionQueueON + actionDelimiter + AUD_B_Index.toString + actionDelimiter + GLOBAL_INT_stationURI.state.toString + delimiter)
	} else {
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString + actionQueueOFF + actionDelimiter + AUD_B_Index.toString + delimiter)
	}
end

rule "WC_INT_speakerActive"
when
        Item WC_INT_speakerActive changed
then
	if (WC_INT_speakerActive.state == ON)
	{
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString +  actionQueueON + actionDelimiter + AUD_WC_Index.toString + actionDelimiter + GLOBAL_INT_stationURI.state.toString + delimiter)
	} else {
		postUpdate (GLOBAL_AUD_actionQueue, GLOBAL_AUD_actionQueue.state.toString + actionQueueOFF + actionDelimiter + AUD_WC_Index.toString + delimiter)
	}
end


rule "K_INT_volumeButtons"
when
        Item K_INT_volumeButtons received update
then
        if (K_INT_volumeButtons.state == ON)
        {
                sendHttpGetRequest (rsURL + "volumeUp?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_K_Index).toString  + "&scope=room")
        } else {
                sendHttpGetRequest (rsURL + "volumeDown?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_K_Index).toString  + "&scope=room")
        }
end
rule "WZ_INT_volumeButtons"
when
        Item WZ_INT_volumeButtons received update
then
        if (WZ_INT_volumeButtons.state == ON)
        {
                sendHttpGetRequest (rsURL + "volumeUp?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_WZ_Index).toString  + "&scope=room")
        } else {
                sendHttpGetRequest (rsURL + "volumeDown?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_WZ_Index).toString  + "&scope=room")
        }
end

rule "BAD_INT_volumeButtons"
when
        Item BAD_INT_volumeButtons received update
then
        if (BAD_INT_volumeButtons.state == ON)
        {
                sendHttpGetRequest (rsURL + "volumeUp?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_B_Index).toString  + "&scope=room")
        } else {
                sendHttpGetRequest (rsURL + "volumeDown?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_B_Index).toString  + "&scope=room")
        }
end

rule "WC_INT_volumeButtons"
when
        Item WC_INT_volumeButtons received update
then
        if (WC_INT_volumeButtons.state == ON)
        {
                sendHttpGetRequest (rsURL + "volumeUp?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_WC_Index).toString  + "&scope=room")
        } else {
                sendHttpGetRequest (rsURL + "volumeDown?value=" + volumeStep.toString + "&id=" + deviceName.get(AUD_WC_Index).toString  + "&scope=room")
        }
end

rule "GLOBAL_INT_volumeButtons"
when
        Item GLOBAL_INT_volumeButtons received update
then
        var Integer i = -1

        while ((i=i+1) < nPHYS_DEVICES)
        {
		val stateItem = deviceState.get(i).split (delimiter)
		var String transportState = stateItem.get(3)
		
		if (transportState == "PLAYING")
		{
	                if (GLOBAL_INT_volumeButtons.state == ON)
			{
				sendHttpGetRequest (rsURL + "volumeUp?value=" + volumeStep.toString + "&id=" + deviceName.get(i).toString + "&scope=room")
			} else {
				sendHttpGetRequest (rsURL + "volumeDown?value=" + volumeStep.toString + "&id=" + deviceName.get(i).toString + "&scope=room")
			}
		}
        }
end





rule "GLOBAL_INT_stationURI"
when
        Item GLOBAL_INT_stationURI received update
then
        logInfo ("AUD","GLOBAL_INT_stationURI::started")
        if (GLOBAL_INT_stationURI.state.toString == safe_GLOBAL_INT_stationURI)
        {

		var Boolean singleZone = true
		var String matchZone = ""
		var String matchUri = ""
		var Integer i = -1
		var String zone = ""

		while ((i=i+1) < nPHYS_DEVICES)
		{
			val stateItem = deviceState.get(i).split (delimiter)
			var String transportState = stateItem.get(3)
			var String uri = stateItem.get(4).toString
			zone = stateItem.get(2).toString
		
			if ((transportState == "PLAYING") && (uri != matchUri)) 
			{
				if (matchZone == "")		// first playing zone
				{
					matchZone = zone
					matchUri = uri
				} else {			// second playing zone
					singleZone = false
				}
			}  
		}


		if ((singleZone == true) && (matchZone.length > 0) && (matchUri.length > 0))
		{
			logInfo ("AUD","GLOBAL_INT_stationURI::singleZone switch URI")
			sendHttpGetRequest (rsURL + "loadUri?id=" + matchZone + "&value=" + GLOBAL_INT_stationURI.state.toString)
			
		} else {
			logInfo ("AUD","GLOBAL_INT_stationURI::multiZone switch URI")

	                val activeSafeItem = newArrayList()

			var Integer i = -1
			while ((i=i+1) < nPHYS_DEVICES)
			{
				val active = guiActiveItem.get(i) as SwitchItem
				if (active.state == ON)
				{
					postUpdate (active, OFF)
					Thread::sleep (200)	
					activeSafeItem.add (true)
				} else {
					activeSafeItem.add (false)
				}
			}

	                i = nPHYS_DEVICES
	                while ((i=i-1) > -1)
	                {
	                        val active = guiActiveItem.get(i) as SwitchItem
	                        if (activeSafeItem.get(i) == true)
	                        {
	                                postUpdate (active, ON)
	                                Thread::sleep (200)
	                        }
	                }
	        }
	}
        safe_GLOBAL_INT_stationURI = GLOBAL_INT_stationURI.state.toString
end

section::setActiveItems // set active HABPanel-Items on external manipulation on RFs

rule "GM_setActiveItems"
when
	Item GLOBAL_INT_updateRF changed to ON
then
	Thread::sleep (30000)

	logInfo ("AUD","setActiveItems::started..")

	while (GLOBAL_INT_updateRF.state == ON)

	{
		if ((requestDelay == noChangeDelay) && (deviceState.size > 0))
		{
			var i = -1
			while ((i=i+1) < nPHYS_DEVICES)
			{

				val stateItem = deviceState.get(i).toString.split(delimiter)
				var String transportState = stateItem.get(3).toString
				var String powerState = stateItem.get(1).toString
				
				val activeItem = guiActiveItem.get(i) as SwitchItem

				if ((transportState == "PLAYING") && (powerState == "ACTIVE") && (activeItem.state == OFF)) 
				{
					postUpdate (activeItem, ON)
					logInfo ("AUD","setActiveItems::" + deviceName.get(i).toString + " >> ON")
				}
				if (((transportState == "STOPPED") || (transportState == "PAUSED_PLAYBACK") || (transportState == "NO_MEDIA_PRESENT")) && (activeItem.state == ON)) 
				{
					postUpdate (activeItem, OFF)
					logInfo ("AUD","setActiveItems::" + deviceName.get(i).toString + " >> OFF")
				}
			}
		}

		Thread::sleep (changeDelay)
	}
end

section::GUI
the webchannel-buttons are “buttons” with set the webstation-URI as command to item GLOBAL_INT_stationURI

and the raumfeld-debug-site

p.p.p.s. any smugly comment will let explode the post by itself immediatly - so have fun ewith reverse engineering

The code is long and I don’t have a lot of time to review it right now. A few things stand out:

Sleeps longer than 500 are a really bad idea. It probably isn’t too big of a deal in your System started rule, though there was a bug not too long ago where this caused problems too.

The problem with long sleeps is you only get five threads to run Rules. That means only five Rules can run at the same time. When you have Rules that take a long time to run, you increase the likelihood that you will run out of those threads and any new Rules that need to trigger will have to wait for one of the five that are running to exit before they can run. In bad cases the backlog of rules ready to run can pile up to the point that all Rules stop executing.

Given this, a 30 second sleep is a REALLY bad idea. A while loop with a sleep in it is a really bad idea.

It is much better to use Timers and schedule the code to run later than it is to tie up a Rule thread doing nothing.