Proud to present: My own Bose Soundtouch Multiroom implementation

Hello there,
because of the fact, that the soundtouch binding doesnt support Multiroom and the soundtouch app is not the way it should be used, i created my own rule and itemset , that implements Multiroom in openhab.
This HowTo relies on the soundtouch and http binding. The ruleset is completely written in group behaviour so the rules scale with a undefined number of soundtouches. Currently i have 3 of them and i could test with all of them.

All i wanted to do is to post my code. Maybe you can use it for yourself ;).
What you need to know is, that i parse the items names to know what device triggers the rule and concat this with other items. therefore it is necessary to rewrite my parsing string or keep my item naming scheme.
All the Groups in items file are necessary, because all the rules base on them.
The Sitemap behaves dynamically on multiroom States :slight_smile: When you have a multiroom Group you can add and remove speakers to them.
In This Ruleset there is a feature implemented:
When there is only one speaker A playing and its not TV and you turn on another speaker B C . B C automatically connect to A as multiroom speakers. Disadvantage: When you want to play different stuff on them, you have to disconnect them from multiroom -> Multiroom item OFF.
I made this ruleset because i didnt like the way you have to configure multiroom groups. It should be solved automatically and therefore you need to have these basic rules. You can make your Own soup now :slight_smile:
For example: When Speaker A turns on, Always connect Speaker B to it or always play on all speakers (i called it partymode). Tell me what you think and feel free to ask me ! I know its a bunch of code ;).

Best regards

bose.items
Group:Switch                    gSoundtouch_Master "Gruppe fĂĽr Multiroom Master"
Group:String                    gSoundtouch_MultiroomMaster "Gruppe fĂĽr Multiroom Master"
Group:Switch                    gSoundtouch_AvailableForMultiroom
Group:Switch:OR(ON,OFF)         gSoundtouch_Multiroom
Group:Switch                    gSoundtouch_PotentialMaster
Group:String                    gSoundtouch_MacPlaying
Group:Switch                    gSoundtouch_AvailableForMaster
Group:Switch                    gSoundtouch_Power
Group:String                    gSoundtouch_Source
Group:String                    gSoundtouch_MAC
Group:Dimmer                    gSoundtouch_Volume
Group:Switch                    gSoundtouch_PowerTrigger
Group:Switch                    gSoundtouch_VolumeUp
Group:Switch                    gSoundtouch_VolumeDown
Group:String                    gSoundtouch_IP
Switch  WZ_Soundtouch300_PowerTrigger                      "Power: [%s]"          <switch> (gSoundtouch_PowerTrigger)      [ "Switchable" ] { channel="bosesoundtouch:300:C4F3129$
//Switch  WZ_Soundtouch300_PowerTrigger             "Power: [%s]"       <switch> (gSoundtouch_PowerTrigger) [ "Switchable" ] { http=">[ON:POST:] >[OFF:POST:]"

Switch  WZ_Soundtouch300_Power                      "Power: [%s]"       <switch> (gSoundtouch_Power)     [ "Switchable"]
Dimmer  WZ_Soundtouch300_Volume                     "Volume: [%d %%]"      <volume> (gSoundtouch_Volume)      { channel="bosesoundtouch:300:C4F3129FF042:volume" }
Number  WZ_Soundtouch300_Bass                       "Bass: [%d %%]"        <volume>      { channel="bosesoundtouch:300:C4F3129FF042:bass" }
Switch  WZ_Soundtouch300_Mute                       "Mute: [%s]"           <volume_mute> { channel="bosesoundtouch:300:C4F3129FF042:mute" }
String  WZ_Soundtouch300_OperationMode              "OperationMode: [%s]"  <text>        { channel="bosesoundtouch:300:C4F3129FF042:operationMode" }
Player  WZ_Soundtouch300_PlayerControl              "Wohnzimmer Soundtouch Player: [%s]" <text> (gSoundtouchPlayer) { channel="bosesoundtouch:300:C4F3129FF042:playerControl" }
Switch  WZ_Soundtouch300_Player_Play                "WZ_Soundtouch300 Player Play" [ "Switchable" ]
Switch  WZ_Soundtouch300_Player_Pause                "WZ_Soundtouch300 Player Pause" [ "Switchable" ]
Number  WZ_Soundtouch300_Preset                     "Preset: [%d]"         <text>        { channel="bosesoundtouch:300:C4F3129FF042:preset" }
String  WZ_Soundtouch300_PresetControl              "Preset Control: [%s]" <text>        { channel="bosesoundtouch:300:C4F3129FF042:presetControl" }
Number  WZ_Soundtouch300_SaveAsPreset               "Save as Preset: [%d]" <text>        { channel="bosesoundtouch:300:C4F3129FF042:saveAsPreset" }
String  WZ_Soundtouch300_KeyCode                    "Key Code: [%s]"       <text>        { channel="bosesoundtouch:300:C4F3129FF042:keyCode" }
Switch  WZ_Soundtouch300_RateEnabled                "Rate: [%s]"           <switch>      { channel="bosesoundtouch:300:C4F3129FF042:rateEnabled" }
Switch  WZ_Soundtouch300_SkipEnabled                "Skip: [%s]"           <switch>      { channel="bosesoundtouch:300:C4F3129FF042:skipEnabled" }
Switch  WZ_Soundtouch300_SkipPreviousEnabled        "SkipPrevious: [%s]"   <switch>      { channel="bosesoundtouch:300:C4F3129FF042:skipPreviousEnabled" }
String  WZ_Soundtouch300_nowPlayingAlbum            "Album: [%s]"          <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingAlbum" }
String  WZ_Soundtouch300_nowPlayingArtist           "Artist: [%s]"         <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingArtist" }
String  WZ_Soundtouch300_nowPlayingArtwork          "Art: [%s]"            <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingArtwork" }
String  WZ_Soundtouch300_nowPlayingDescription      "Description: [%s]"    <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingDescription" }
String  WZ_Soundtouch300_nowPlayingGenre            "Genre: [%s]"          <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingGenre" }
String  WZ_Soundtouch300_nowPlayingItemName         "Playing: [%s]"        <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingItemName" }
String  WZ_Soundtouch300_nowPlayingStationLocation  "Radio Location: [%s]" <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingStationLocation" }
String  WZ_Soundtouch300_nowPlayingStationName      "Radio Name: [%s]"     <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingStationName" }
String  WZ_Soundtouch300_nowPlayingTrack            "Track: [%s]"          <text>        { channel="bosesoundtouch:300:C4F3129FF042:nowPlayingTrack" }
Switch  WZ_Soundtouch300_playall                     "Musik von Soundtouch 300 auf allen Geräten" <switch>
Switch  WZ_Soundtouch300_RemoveNode                 "Remove Node from multiroom group" <switch> [ "Switchable" ]
Switch  WZ_Soundtouch300_VolumeUp                    "Step Volume Up" (gSoundtouch_VolumeUp) [ "Switchable" ]
Switch  WZ_Soundtouch300_VolumeDown                  "Step Volume Down" (gSoundtouch_VolumeDown) [ "Switchable" ]
String  WZ_Soundtouch300_Source                     "Now Playing Source" <text> (gSoundtouch_Source)  { http="<[http://192.168.2.96:8090/now_playing:3000:XPATH(/nowPlaying/@sour$
Switch  WZ_Soundtouch300_SwitchToTV                 "Switch to TV" <switch> [ "Switchable" ]
Switch  WZ_Soundtouch300_Multiroom                  "Enables or Disables multiroom for this Soundtouch" (gSoundtouch_Multiroom)
Switch  WZ_Soundtouch300_AvailableForMaster         "Sets Availability for Master" (gSoundtouch_AvailableForMaster)
String  WZ_Soundtouch300_MultiroomMaster            "Displays to which Master the Soundtouch is connected" (gSoundtouch_MultiroomMaster) { http="<[http://192.168.2.96:8090/getZo$
Switch  WZ_Soundtouch300_AvailableForMultiroom      "Sets Availability for Multiroom" (gSoundtouch_AvailableForMultiroom)
Switch  WZ_Soundtouch300_Master      "Is currently a Master " (gSoundtouch_Master)
String  WZ_Soundtouch300_MAC         "MAC from API"     (gSoundtouch_MAC) { http="<[http://192.168.2.96:8090/info:20000:XPATH(/info/@deviceID)]" }
String Soundtouch_Multiroom_Master "Multiroom Master Switcher"
String Soundtouch_Multiroom_Master_IP "Multiroom Master Switcher IP"
Switch Soundtouch_Multiroom "Multiroom on/off" [ "Switchable" ]
bose.rules
rule "Powerupdate - Member of Group changed its powerstate"
when
        Member of gSoundtouch_Source changed from "STANDBY"
then
        //initialize variables from all needed groups
        val devicename = triggeringItem.name.toString.split('_').get(0) +"_" +triggeringItem.name.toString.split('_').get(1)
        val devicename_Power = devicename + "_Power"
        val devicename_AvailableForMultiroom = devicename + "_AvailableForMultiroom"
        val devicename_AvailableForMaster = devicename + "_AvailableForMaster"
        val devicename_MAC = devicename + "_MAC"
        val devicename_Master = devicename + "_Master"
        val devicename_Multiroom = devicename + "_Multiroom"
        val devicename_Multiroom_item = gSoundtouch_Multiroom.members.findFirst[ d | d.name == devicename_Multiroom ]
        val devicename_Master_item = gSoundtouch_Master.members.findFirst[ d | d.name == devicename_Master ]
        val devicename_Power_item = gSoundtouch_Power.members.findFirst[ d | d.name == devicename_Power ]
        val devicename_AvailableForMultiroom_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMultiroom ]
        val devicename_AvailableForMaster_item = gSoundtouch_AvailableForMaster.members.findFirst[ d | d.name == devicename_AvailableForMaster ]
        val devicename_MAC_item = gSoundtouch_MAC.members.findFirst[ d | d.name == devicename_MAC ]
        val Number MACPlaying = gSoundtouch_MacPlaying.members.filter[ NumberItem i | i.state!="" ].size
        val Number MastersCount = gSoundtouch_Master.members.filter[ NumberItem i | i.state==ON ].size

        logInfo("current Devices Playing:", "Currently Playing:" +MACPlaying + "MastersCount: " + MastersCount)
        postUpdate(devicename_Power_item, ON)
        // Add speaker to Master if only one Master exists - Feature for automatically adding speakers to Multiroom when turned on
        if(MastersCount==1 || Soundtouch_Multiroom_Master.state!="" && MastersCount==0){
                sendCommand(devicename_Multiroom_item,ON)
        }
        postUpdate(devicename_AvailableForMaster_item,OFF)
end
rule "Powerupdate - Member of Group changed its powerstate to STANDBY"
when
        Member of gSoundtouch_Source changed to "STANDBY"
then
        //initialize variables from all needed groups
        val devicename = triggeringItem.name.toString.split('_').get(0) +"_" +triggeringItem.name.toString.split('_').get(1)
        val devicename_Power = devicename + "_Power"
        val devicename_AvailableForMultiroom = devicename + "_AvailableForMultiroom"
        val devicename_AvailableForMaster = devicename + "_AvailableForMaster"
        val devicename_MAC = devicename + "_MAC"
        val devicename_Master = devicename + "_Master"
        val devicename_Multiroom = devicename + "_Multiroom"
        val devicename_Multiroom_item = gSoundtouch_Multiroom.members.findFirst[ d | d.name == devicename_Multiroom ]
        val devicename_Master_item = gSoundtouch_Master.members.findFirst[ d | d.name == devicename_Master ]
        val devicename_Power_item = gSoundtouch_Power.members.findFirst[ d | d.name == devicename_Power ]
        val devicename_AvailableForMultiroom_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMultiroom ]
        val devicename_AvailableForMaster_item = gSoundtouch_AvailableForMaster.members.findFirst[ d | d.name == devicename_AvailableForMaster ]
        val devicename_MAC_item = gSoundtouch_MAC.members.findFirst[ d | d.name == devicename_MAC ]
        val Number MACPlaying = gSoundtouch_MacPlaying.members.filter[ NumberItem i | i.state!="" ].size
        val Number MastersCount = gSoundtouch_Master.members.filter[ NumberItem i | i.state==ON ].size
        logInfo("current Devices Playing:", "MastersCount: " + MastersCount + " Multiroom_Master: " +Soundtouch_Multiroom_Master.state)
        if(triggeringItem.state==PLAY){
                if(devicename_Source_item.state!="PRODUCT"){
                        logInfo("New Playing Member:", "MAC:" +triggeringItem.name)
                        gSoundtouch_MacPlaying.addMember(devicename_MAC_item)
                }
                if (MastersCount==0 && Soundtouch_Multiroom_Master.state=="" || Soundtouch_Multiroom_Master.state==NULL){
                        gSoundtouch_MacPlaying.members.filter[ NumberItem i | i.state!="" ].forEach[ i |
                                postUpdate(Soundtouch_Multiroom_Master,i.state)
                                postUpdate(Soundtouch_Multiroom_Master_IP,i.state) ]
                        logInfo("New Master:", "MAC:" +Soundtouch_Multiroom_Master.state)
                }
        }
        else {
                gSoundtouch_MacPlaying.removeMember(devicename_MAC_item)
        }
end
rule "Powerupdate - Member of Group received Powercommand"
when
        Member of gSoundtouch_Power received command
then
        //initialize variables from all needed groups
        val devicename = triggeringItem.name.toString.split('_').get(0) +"_" +triggeringItem.name.toString.split('_').get(1)
        val devicename_Power = devicename + "_Power"
        val devicename_Power_item = gSoundtouch_Power.members.findFirst[ d | d.name == devicename_Power ]
        val devicename_IP = devicename + "_IP"
        val devicename_IP_item = gSoundtouch_IP.members.findFirst[ d | d.name == devicename_IP ]
        val devicename_Source = devicename + "_Source"
        val devicename_Source_item = gSoundtouch_Source.members.findFirst[ d | d.name == devicename_Source ]
        val url = "http://" + devicename_IP_item.state + ":8090/key"
        val xmlstring = '<key state="press" sender="Gabbo">POWER</key>'

        if(receivedCommand==ON && devicename_Source_item.state=="STANDBY" || receivedCommand==OFF && devicename_Source_item.state!="STANDBY"){
                sendHttpPostRequest(url , "application/xml", xmlstring)
        }
        logInfo("Powertrigger","Power for: " +devicename + " "+ devicename_IP_item + "receivedCommand: " + receivedCommand)
//      postUpdate(devicename_Power_item,receivedCommand)

end
rule "Multiroom - Member of Group becomes master"
when
        Member of gSoundtouch_AvailableForMaster changed to ON
then
        //initialize variables from all needed groups
        val devicename = triggeringItem.name.toString.split('_').get(0) +"_" +triggeringItem.name.toString.split('_').get(1)
        val devicename_AvailableForMultiroom = devicename + "_AvailableForMultiroom"
        val devicename_Master = devicename + "_Master"
        val devicename_Multiroom = devicename + "_Multiroom"
        val devicename_MAC = devicename + "_MAC"
        val devicename_AvailableForMaster = devicename + "_AvailableForMaster"
        val devicename_AvailableForMultiroom_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMultiroom ]
        val devicename_AvailableForMaster_item = gSoundtouch_AvailableForMaster.members.findFirst[ d | d.name == devicename_AvailableForMaster ]
        val devicename_Master_item = gSoundtouch_Master.members.findFirst[ d | d.name == devicename_Master ]
        val devicename_MAC_item = gSoundtouch_MAC.members.findFirst[ d | d.name == devicename_MAC ]
        val devicename_Multiroom_item = gSoundtouch_Multiroom.members.findFirst[ d | d.name == devicename_Multiroom ]
        val devicename_IP = devicename + "_IP"
        val devicename_IP_item = gSoundtouch_IP.members.findFirst[ d | d.name == devicename_IP ]

        logInfo("ChangeToMaster","ChangeToMaster: " + devicename_AvailableForMaster_item)
        postUpdate(devicename_Master_item,ON)
        postUpdate(Soundtouch_Multiroom_Master,devicename_MAC_item.state)
        postUpdate(Soundtouch_Multiroom_Master_IP,devicename_IP_item.state)
        sendCommand(devicename_Multiroom_item,ON)
        postUpdate(devicename_AvailableForMultiroom_item,OFF)
end
rule "Multiroom ON - Member of Group received Multiroom ON"
when
        Member of gSoundtouch_Multiroom received command ON
then
        //initialize variables from all needed groups
        val devicename = triggeringItem.name.toString.split('_').get(0) +"_" +triggeringItem.name.toString.split('_').get(1)
        val devicename_Power = devicename + "_Power"
        val devicename_AvailableForMultiroom = devicename + "_AvailableForMultiroom"
        val devicename_AvailableForMaster = devicename + "_AvailableForMaster"
        val devicename_Power_item = gSoundtouch_Power.members.findFirst[ d | d.name == devicename_Power ]
        val devicename_AvailableForMultiroom_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMultiroom ]
        val devicename_AvailableForMaster_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMaster ]
        val devicename_MAC = devicename + "_MAC"
        val devicename_MAC_item = gSoundtouch_MAC.members.findFirst[ d | d.name == devicename_MAC ]
        val devicename_IP = devicename + "_IP"
        val devicename_IP_item = gSoundtouch_IP.members.findFirst[ d | d.name == devicename_IP ]

        postUpdate(devicename_Power_item,ON)
        logInfo("Multiroomtriggered", "Multiroom ON for device:" + devicename + " Master: " + Soundtouch_Multiroom_Master.state)
        val url_client = "http://" + devicename_IP_item.state + ":8090/setZone"
        val url_master = "http://" + Soundtouch_Multiroom_Master_IP.state + ":8090/setZone"
        val xmlstring_client = '<zone master="' + Soundtouch_Multiroom_Master.state + '" senderIPAddress="' + Soundtouch_Multiroom_Master_IP.state + '" senderIsMaster="true">"<m$
        val xmlstring_master = '<zone master="' + Soundtouch_Multiroom_Master.state + '" senderIPAddress="' + Soundtouch_Multiroom_Master_IP.state + '"><member ipaddress="' + de$
        logInfo("Multiroomtriggered", "Xml String fĂĽr Client:" + xmlstring_client)
        logInfo("Multiroomtriggered", "Xml String fĂĽr Master:" + xmlstring_master)
        sendHttpPostRequest(url_client , "application/xml", xmlstring_client)
        sendHttpPostRequest(url_master , "application/xml", xmlstring_master)
        postUpdate(devicename_AvailableForMultiroom_item,OFF)
        postUpdate(devicename_AvailableForMaster_item,NULL)

end
rule "Multiroom OFF - Member of Group received Multiroom OFF"
when
        Member of gSoundtouch_Multiroom received command OFF
then
        //initialize variables from all needed groups
        val devicename = triggeringItem.name.toString.split('_').get(0) +"_" +triggeringItem.name.toString.split('_').get(1)
        val devicename_Multiroom = devicename + "_Multiroom"
        val devicename_AvailableForMultiroom = devicename + "_AvailableForMultiroom"
        val devicename_AvailableForMaster = devicename + "_AvailableForMaster"
        val devicename_MultiroomMaster = devicename + "_MultiroomMaster"
        val devicename_Multiroom_item = gSoundtouch_Multiroom.members.findFirst[ d | d.name == devicename_Multiroom ]
        val devicename_AvailableForMultiroom_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMultiroom ]
        val devicename_AvailableForMaster_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMaster ]
        val devicename_MultiroomMaster_item = gSoundtouch_MultiroomMaster.members.findFirst[ d | d.name == devicename_MultiroomMaster ]
        val devicename_IP = devicename + "_IP"
        val devicename_IP_item = gSoundtouch_IP.members.findFirst[ d | d.name == devicename_IP ]
        val url_getZoneString = 'http://' + devicename_IP_item.state + ':8090/getZone'
        val url_removeZoneSlave = 'http://' + devicename_IP_item.state + ':8090/removeZoneSlave'
        val getZoneString = sendHttpGetRequest(url_getZoneString)
        val getZoneString_parsed = "<" + getZoneString.split('<').get(2) +"<" + getZoneString.split('<').get(3) + "<" + getZoneString.split('<').get(4) + "<" + getZoneString.spl$

        logInfo("Multiroom", "Multiroom OFF for device:" + devicename + " Master: " + Soundtouch_Multiroom_Master.state)
        logInfo("Zonestring", "Zonestring that will be removed:" + getZoneString_parsed)
        sendHttpPostRequest(url_removeZoneSlave , "application/xml", getZoneString_parsed)
        postUpdate(devicename_Multiroom_item,OFF)
        postUpdate(devicename_AvailableForMultiroom_item,ON)
        postUpdate(devicename_AvailableForMaster_item,NULL)
end
rule "Multiroom - MultiroomMaster changed in Group"
when
        Member of gSoundtouch_MultiroomMaster changed
then
        val devicename = triggeringItem.name.toString.split('_').get(0) +"_" +triggeringItem.name.toString.split('_').get(1)
        val devicename_Power = devicename + "_Power"
        val devicename_Multiroom = devicename + "_Multiroom"
        val devicename_Master = devicename + "_Master"
        val devicename_AvailableForMaster = devicename + "_AvailableForMaster"
        val devicename_MultiroomMaster = devicename + "_MultiroomMaster"
        val devicename_AvailableForMultiroom = devicename + "_AvailableForMultiroom"
        val devicename_MAC = devicename + "_MAC"
        val devicename_Power_item = gSoundtouch_Power.members.findFirst[ d | d.name == devicename_Power ]
        val devicename_Multiroom_item = gSoundtouch_Multiroom.members.findFirst[ d | d.name == devicename_Multiroom ]
        val devicename_Master_item = gSoundtouch_Master.members.findFirst[ d | d.name == devicename_Master ]
        val devicename_AvailableForMaster_item = gSoundtouch_AvailableForMaster.members.findFirst[ d | d.name == devicename_AvailableForMaster ]
        val devicename_MultiroomMaster_item = gSoundtouch_MultiroomMaster.members.findFirst[ d | d.name == devicename_MultiroomMaster ]
        val devicename_AvailableForMultiroom_item = gSoundtouch_AvailableForMultiroom.members.findFirst[ d | d.name == devicename_AvailableForMultiroom ]
        val devicename_MAC_item = gSoundtouch_MAC.members.findFirst[ d | d.name == devicename_MAC ]
        val devicename_IP = devicename + "_IP"
        val devicename_IP_item = gSoundtouch_IP.members.findFirst[ d | d.name == devicename_IP ]
        //If Group is empty, it means no Multiroom is active. Reset any global items and set every soundtouch to available for multiroom
        if(gSoundtouch_MultiroomMaster.state==""){
                postUpdate(Soundtouch_Multiroom_Master,"")
                postUpdate(Soundtouch_Multiroom_Master_IP,"")
                sendCommand(gSoundtouch_AvailableForMultiroom, ON)
        }
        //If the new MultiroomMaster is the same as the Device, set the device as master
        if (triggeringItem.state==devicename_MAC_item.state){
                postUpdate(devicename_Master_item,ON)
                postUpdate(devicename_AvailableForMaster_item,NULL)
        }
        //If empty it means that the device does not belong to any multiroom group anymore -> update multiroom, master, availability
        if (triggeringItem.state==""){
                postUpdate(devicename_Multiroom_item,OFF)
                postUpdate(devicename_Master_item,OFF)
                postUpdate(devicename_AvailableForMultiroom_item,ON)
                if(devicename_Power_item.state==OFF){
                        postUpdate(devicename_AvailableForMaster_item,NULL)
                }
                else {
                        postUpdate(devicename_AvailableForMaster_item,OFF)
                }
        }
        //If not empty, update global variables
        else {
                logInfo("MultiroomMaster change detected:", "triggeringItem triggered a Masterchange:" +triggeringItem.name + "TriggeringState: " + triggeringItem.state)
                postUpdate(Soundtouch_Multiroom_Master, devicename_MultiroomMaster_item.state)
                postUpdate(Soundtouch_Multiroom_Master_IP, devicename_IP_item.state)
                postUpdate(devicename_Multiroom_item,ON)
                postUpdate(devicename_AvailableForMultiroom_item,OFF)
        }
end
rule "Multiroom - Partymode"
when
        Item Soundtouch_Multiroom changed
then
        if (Soundtouch_Multiroom.state==ON){
                val Number MACPlaying = gSoundtouch_MacPlaying.members.filter[ NumberItem i | i.state!="" ].size
                val Number MastersCount = gSoundtouch_Master.members.filter[ NumberItem i | i.state==ON ].size
                logInfo("Playing and Masters:", "CurrentPlaying: " +MACPlaying + "Masterscount: " + MastersCount)
                if (MastersCount==1){
                        gSoundtouch_Multiroom.members.filter[ i | i.state != ON].forEach[ i | i.sendCommand(ON) ]
                }
        }
        else{
                postUpdate(Soundtouch_Multiroom_Master, "")
        }
end
Bose.sitemap
itemap Bose label="Bose" {
        Frame label="Multiroom Info" {
                Switch item=Soundtouch_Multiroom_Master label="Master" mappings=[C4F3129FF042="WZ",C4F312F8810A="KĂĽche", 2C6B7DB94CE6="Bad", ""="Aus"]
                Switch item=Soundtouch_Multiroom label="Soundtouch Partymode"
                Switch item=WZ_Soundtouch300_AvailableForMaster label="WZ Master werden" visibility=[WZ_Soundtouch300_AvailableForMaster!=NULL]
                Switch item=Bad_Soundtouch10_AvailableForMaster label="Bad Master werden" visibility=[Bad_Soundtouch10_AvailableForMaster!=NULL]
                Switch item=Kueche_Soundtouch10_AvailableForMaster label="Kueche Master werden" visibility=[Kueche_Soundtouch10_AvailableForMaster!=NULL]
}
        Frame label="Wohnzimmer Multiroom" visibility=[WZ_Soundtouch300_Master==ON] {
                Switch item=WZ_Soundtouch300_Multiroom label="Wohnzimmer Master"
                Switch item=WZ_Soundtouch300_Power label="An/Aus"
                Switch item=Bad_Soundtouch10_Multiroom label="Bad" visibility=[Bad_Soundtouch10_AvailableForMultiroom==ON, Bad_Soundtouch10_MultiroomMaster=="C4F3129FF042"]
                Switch item=Kueche_Soundtouch10_Multiroom label="KĂĽche" visibility=[Kueche_Soundtouch10_AvailableForMultiroom==ON,Kueche_Soundtouch10_MultiroomMaster=="C4F3129FF$
        }
        Frame label="Badezimmer Multiroom" visibility=[Bad_Soundtouch10_Master==ON] {
                Switch item=Bad_Soundtouch10_Multiroom label="Bad Master"
                Switch item=Bad_Soundtouch10_Power label="An/Aus"
                Switch item=WZ_Soundtouch300_Multiroom label="Wohnzimmer" visibility=[WZ_Soundtouch300_AvailableForMultiroom==ON, WZ_Soundtouch300_MultiroomMaster=="2C6B7DB94CE6$
                Switch item=Kueche_Soundtouch10_Multiroom label="KĂĽche" visibility=[Kueche_Soundtouch10_AvailableForMultiroom==ON,Kueche_Soundtouch10_MultiroomMaster=="2C6B7DB94$
        }
        Frame label="KĂĽche Multiroom" visibility=[Kueche_Soundtouch10_Master==ON] {
                Switch item=Kueche_Soundtouch10_Multiroom label="KĂĽche Master"
                Switch item=Kueche_Soundtouch10_Power label="An/Aus"
                Switch item=WZ_Soundtouch300_Multiroom label="Wohnzimmer" visibility=[WZ_Soundtouch300_AvailableForMultiroom==ON, WZ_Soundtouch300_MultiroomMaster=="C4F312F8810A$
                Switch item=Bad_Soundtouch10_Multiroom label="Bad" visibility=[Bad_Soundtouch10_AvailableForMultiroom==ON,Bad_Soundtouch10_MultiroomMaster=="C4F312F8810A"]
        }

        Frame label="Wohnzimmer Soundtouch300" {
                Switch item=WZ_Soundtouch300_Power label="An/Aus"
//              Switch item=WZ_Soundtouch300_OperationMode label="An/Aus" mappings=[PRODUCT="AN", STANDBY="AUS"]
                Default item=WZ_Soundtouch300_PlayerControl label="Soundtouch300 Player" visibility=[WZ_Soundtouch300_Power==ON]
                Text item=WZ_Soundtouch300_PlayerControl label="Play status: [%s]" visibility=[WZ_Soundtouch300_Power==ON]
                Text item=WZ_Soundtouch300_nowPlayingTrack label="Was spielt: [%s]" visibility=[WZ_Soundtouch300_Power==ON]
//              Text item=WZ_Soundtouch300_Source label="Quelle:[%s]" visibility=[WZ_Soundtouch300_Power==ON]
                Text item=WZ_Soundtouch300_Source label="Quelle:[%s]"
                Switch item=WZ_Soundtouch300_VolumeUp label="Volume Up +5" visibility=[WZ_Soundtouch300_Power==ON]
                Switch item=WZ_Soundtouch300_VolumeDown label="Volume Down -5" visibility=[WZ_Soundtouch300_Power==ON]
                Text item=WZ_Soundtouch300_MultiroomMaster label="Aktueller Master: [MAP(soundtouch.map):%s]" visibility=[WZ_Soundtouch300_Power==ON]
                Text item=WZ_Soundtouch300_IP label="IP: [%s]"
        }
        Frame label="Bad Soundtouch10" {
                Switch item=Bad_Soundtouch10_Power label="An/Aus"
                Default item=Bad_Soundtouch10_PlayerControl label="Soundtouch300 Player" visibility=[Bad_Soundtouch10_Power==ON]
                Text item=Bad_Soundtouch10_PlayerControl label="Play status: [%s]" visibility=[Bad_Soundtouch10_Power==ON]
                Text item=Bad_Soundtouch10_Source label="Quelle:[%s]"
                Text item=Bad_Soundtouch10_nowPlayingTrack label="Was spielt: [%s]" visibility=[Bad_Soundtouch10_Power==ON]
                Switch item=Bad_Soundtouch10_VolumeUp label="Volume Up +5" visibility=[Bad_Soundtouch10_Power==ON]
                Switch item=Bad_Soundtouch10_VolumeDown label="Volume Down -5" visibility=[Bad_Soundtouch10_Power==ON]
                Text item=Bad_Soundtouch10_MultiroomMaster label="Aktueller Master: [MAP(soundtouch.map):%s]" visibility=[Bad_Soundtouch10_Power==ON]
        }
        Frame label=" KĂĽche Soundtouch10" {
                Switch item=Kueche_Soundtouch10_Power label="An/Aus"
                Default item=Kueche_Soundtouch10_PlayerControl label="Soundtouch300 Player" visibility=[Kueche_Soundtouch10_Power==ON]
                Text item=Kueche_Soundtouch10_PlayerControl label="Play status: [%s]" visibility=[Kueche_Soundtouch10_Power==ON]
                Text item=Kueche_Soundtouch10_Source label="Quelle:[%s]"
                Text item=Kueche_Soundtouch10_nowPlayingTrack label="Was spielt: [%s]" visibility=[Kueche_Soundtouch10_Power==ON]
                Switch item=Kueche_Soundtouch10_VolumeUp label="Volume Up +5" visibility=[Kueche_Soundtouch10_Power==ON]
                Switch item=Kueche_Soundtouch10_VolumeDown label="Volume Down -5" visibility=[Kueche_Soundtouch10_Power==ON]
                Switch item=Kueche_Soundtouch10_Stopptanz label="Stopptanz An/Aus" visibility=[Kueche_Soundtouch10_Power==ON]
                Text item=Kueche_Soundtouch10_MultiroomMaster label="Aktueller Master: [MAP(soundtouch.map):%s]" visibility=[Kueche_Soundtouch10_Power==ON]
        }

}
3 Likes

Hi Dennis,
many thanks for sharing the implementation. It es great and I want to implement it as well.

I’m wondering about two things.

In “bose.items” some lines became cut may be due to limitation in this community blog. The line from this file stops at “@sour$” as you can see below. I assume it is “source” as seen at the “currected line” further below. Is this correct?
String WZ_Soundtouch300_Source “Now Playing Source” (gSoundtouch_Source) { http="<[http://192.168.2.96:8090/now_playing:3000:XPATH(/nowPlaying/@sour$

corrected line:
String WZ_Soundtouch300_Source “Now Playing Source” (gSoundtouch_Source) {http="<[http://192.168.178.46:8090/now_playing:3000:XPATH(/nowPlaying/@source]>"}

How does the following line proceed?
String WZ_Soundtouch300_MultiroomMaster “Displays to which Master the Soundtouch is connected” (gSoundtouch_MultiroomMaster) { http="<[http://192.168.2.96:8090/getZo$

“xpath” is defined to read from XML files. What IP address do I have to use in the line? Is it the WZ_Soundtouch300 IP address to be used?

Many thanks
Thorsten

Hey Thorsten,

sorry for the delayed reply. i Had to move to another House, so no time for Smart Things :).
I have uploaded a new Version of the Rules. These Contain a handling of Airplay2. This means, that the Multiroom will only turn on, when Source is not Airplay2.
bose_rules.txt (26.4 KB)

Hey Dennis,

Thanks so much for sharing your setup, this is fantastic! I’m trying to get this working for my setup and was wondering if you could also share your soundtouch.map file from the transform folder?

Dennis,

Would you be willing to share your full items, map file, sitemap, and rules file?

As Thorsten pointed out earlier, there were some lines that got cut off at the very end which happened to be the XML calls to the API’s of the speakers themselves. I’ve been able to piece them together and test, but I think there’s still something missing in my setup on this.

Really really do appreciate all your work on this. I’m totally new to openhab, but have a lot of programming/scripting experience, so your work is VERY much appreciated.

I’ll indeed share all of my progress and files to help others out.

Automation is well within our reach!!!

1 Like

Hi,

excuse the late reply. here it is!
Note. The Bose_dev sitemap is for debugging. It displays the specific items, so it could be to debug behaviour!

bose_rules.txt (26.4 KB) Bose_Dev.sitemap.txt (5.5 KB) Bose.sitemap.txt (14.3 KB) soundtouch.map.txt (109 Bytes)
soundtouch.items.txt (33.6 KB)