Ok, so this is some ugly ass code, but it is what is in my system currently and it works (well enough, though sometimes the system gets all confused and has to be restarted). Total time waiting for each is about 6 seconds before sound is played.
The regrouping is the biggest pain as the save & restore doesn’t work for groupings, so I manually do it. The one main issue with this code is it is only as good as the bindings information. if someone manually sets a group or starts/stops playback right before this code goes off, the grouping information in the binding can be out of date and it can restore the state wrong.
Also, the say command uses the default audiosink which is SonosMaster.
.items declaration
String SonosSayText
Switch houseAsleep // switch to indicate that the master bedroom is asleep, if this is ON, then no sound plays
Switch asleepDownstairs // switch to indicate if the downstairs rooms are asleep (kids rooms) if this is ON the sound only plays upstairs
the rest of the items are mapped to Sonos Thing channels, the names of the items are representative, .eg:
SonosMaster_ZoneGroup is the thing SonosMaster link to the ZoneGroup channel
items starting with 'g' are groups of items and the name is also representative
gSonosNotificationVolumes is a group for all the NotificationVolume channels
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rule "Alert the House"
when
Item SonosSayText changed
then
var textToSay = ""
var toAll = false
if(!SonosSayText.state.toString.equals(""))
{
textToSay = SonosSayText.state.toString
SonosSayText.postUpdate("")
}
if(!textToSay.equals("") && !textToSay.equals("NULL"))
{
if(houseAsleep.state == OFF)
{
// backup the zone configuration
var zonesXML = SonosMaster_ZoneGroup.state.toString
// save the state of all the Sonoses
SonosSaveAll.sendCommand(ON)
// adjust the NotificationVolume on all sonoses
gSonosNotificationVolumes.sendCommand(50)
if(asleepDownstairs.state == OFF)
{
SonosMaster_StandAlone.sendCommand(ON)
Thread::sleep(500)
SonosMaster_Add.sendCommand("RINCON_B8E937DD8A2801400") // kitchen
SonosMaster_Add.sendCommand("RINCON_5CAAFD2C5D6401400") // master bath
SonosMaster_Add.sendCommand("RINCON_5CAAFD2A673E01400") // bellas
SonosMaster_Add.sendCommand("RINCON_5CAAFD2A66A601400") // gabes
SonosMaster_Add.sendCommand("RINCON_5CAAFD4627D401400") // kidsbath
SonosMaster_Add.sendCommand("RINCON_5CAAFD159A2A01400") // theater
Thread::sleep(5000)
toAll = true
logInfo("AudioRules", String::format("Saying %s to the whole house", textToSay))
say(textToSay)
}
else
{
SonosMaster_StandAlone.sendCommand(ON)
Thread::sleep(500)
SonosMaster_Add.sendCommand("RINCON_B8E937DD8A2801400") // kitchen
SonosMaster_Add.sendCommand("RINCON_5CAAFD2C5D6401400") // master bath
Thread::sleep(5000)
logInfo("AudioRules", String::format("Saying %s to just the Upstairs", textToSay))
say(textToSay)
}
Thread::sleep(1000)
// restore the groupings
var groupCount = Integer::parseInt(transform("XPATH", "count(/ZoneGroups//ZoneGroup)", zonesXML))
var i = 1
while((i=i+1) < groupCount+1)
{
var slaveCount = Integer::parseInt(transform("XPATH", String::format("count(/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember)", i), zonesXML))
var coordinatorID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]/@Coordinator", i), zonesXML)
var alone = false
var playerName = ""
if(slaveCount <= 1)
{
alone = true
}
if(coordinatorID == "RINCON_5CAAFD16993F01400") // Master
{
playerName = "Master"
if(alone)
{
SonosMaster_StandAlone.sendCommand(ON)
}
else
{
var j = 1
while((j=j+1) < slaveCount+1)
{
var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
SonosMaster_Add.sendCommand(slaveID)
logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
}
}
}
else if(coordinatorID == "RINCON_B8E937DD8A2801400") // kitchen
{
playerName = "Kitchen"
if(alone)
{
SonosKitchen_StandAlone.sendCommand(ON)
}
else
{
var j = 1
while((j=j+1) < slaveCount+1)
{
var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
SonosKitchen_Add.sendCommand(slaveID)
logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
}
}
}
if(toAll)
{
if(coordinatorID == "RINCON_5CAAFD2C5D6401400") // master bath
{
playerName = "Master Bath"
if(alone)
{
SonosMasterBath_StandAlone.sendCommand(ON)
}
else
{
var j = 1
while((j=j+1) < slaveCount+1)
{
var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
SonosMasterBath_Add.sendCommand(slaveID)
logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
}
}
}
else if(coordinatorID == "RINCON_5CAAFD2A673E01400") // bellas
{
playerName = "Bellas"
if(alone)
{
SonosBellas_StandAlone.sendCommand(ON)
}
else
{
var j = 1
while((j=j+1) < slaveCount+1)
{
var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
SonosBellas_Add.sendCommand(slaveID)
logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
}
}
}
else if(coordinatorID == "RINCON_5CAAFD2A66A601400") // gabes
{
playerName = "Gabes"
if(alone)
{
SonosGabes_StandAlone.sendCommand(ON)
}
else
{
var j = 1
while((j=j+1) < slaveCount+1)
{
var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
SonosGabes_Add.sendCommand(slaveID)
logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
}
}
}
else if(coordinatorID == "RINCON_5CAAFD4627D401400") // kidsbath
{
playerName = "Kids Bath"
if(alone)
{
SonosKidsBath_StandAlone.sendCommand(ON)
}
else
{
var j = 1
while((j=j+1) < slaveCount+1)
{
var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
SonosKidsBath_Add.sendCommand(slaveID)
logInfo("TEST", String::format("%s has %s as a slave", playerName, slaveID))
}
}
}
else if(coordinatorID == "RINCON_5CAAFD159A2A01400") // theater
{
playerName = "Theater"
if(alone)
{
SonosTheater_StandAlone.sendCommand(ON)
}
else
{
var j = 1
while((j=j+1) < slaveCount+1)
{
var slaveID = transform("XPATH", String::format("/ZoneGroups//ZoneGroup[%d]//ZoneGroupMember[%d]/@UUID", i, j), zonesXML)
SonosTheater_Add.sendCommand(slaveID)
}
}
}
}
}
Thread::sleep(1000)
SonosRestoreAll.sendCommand(ON)
}
}
end