[OH2] Rule help to play a random sound file

I had an idea to randomize some responses when rules run via playSound (specifically Marvin and the ship from Hitchhiker’s guide to the Galaxy) . This would give OpenHab a little more “personality”.

The question is how to do this. Sound files would be in the sound folder (unless there is another way). Since all responses wouldn’t necessarily be in context to all requests/rules there would need to be a way to designate which sound files to draw from.

For example:

At sunset…
Turn on xx lights…
Play 1 of the following (AAA.wav, BBB.wav, CCC.wav)

I would guess you could have a random number created and saved as a variable and use that variable to select the file such as

Var=Random number between 1-3
If Var=1 play AAA.wav
If Var=2 play BBB.wav
If Var=3 play CCC.wav

I just don’t know enough about programming to put this into proper rule context. Here’s a rule of mine, if someone could adjust it to include the above I’d appreciate it.

rule "Lights off at 2300"
when
	Time cron "0 00 23 * * ?"
then
	FrontDoorLight.sendCommand(OFF)
	FrontDoorDecorations.sendCommand(OFF)
	FoyerLanternLoadLevelStatus.sendCommand(0)
	MasonJarLight.sendCommand(OFF)
	PlantAccent.sendCommand(OFF)
	GreatRoomFrontWindowPlug.sendCommand(OFF)
	GreatRoomSideWindowPlug.sendCommand(OFF)
	GreatRoomSideWindowPlug.sendCommand(OFF)
	DiningRoomFrontWindowPlug.sendCommand(OFF)
	MasterBedroomFrontWindowPlug.sendCommand(OFF)
	OfficePlug.sendCommand(OFF)
	FoyerPlug.sendCommand(OFF)
end

“Oh drat. Back to the drawing board.”

import java.util.concurrent.ThreadLocalRandom;

rule "Lights off at 2300"
when
	Time cron "0 00 23 * * ?"
then
	FrontDoorLight.sendCommand(OFF)
	FrontDoorDecorations.sendCommand(OFF)
	FoyerLanternLoadLevelStatus.sendCommand(0)
	MasonJarLight.sendCommand(OFF)
	PlantAccent.sendCommand(OFF)
	GreatRoomFrontWindowPlug.sendCommand(OFF)
	GreatRoomSideWindowPlug.sendCommand(OFF)
	GreatRoomSideWindowPlug.sendCommand(OFF)
	DiningRoomFrontWindowPlug.sendCommand(OFF)
	MasterBedroomFrontWindowPlug.sendCommand(OFF)
	OfficePlug.sendCommand(OFF)
	FoyerPlug.sendCommand(OFF)

        switch ThreadLocalRandom::current().nextInt(0, 2+ 1): {
            case 0: play AAA.wav
            case 1: play BBB.wav
            case 2: play CCC.wav
        }
end

Note: If you have more than one place that you want to do this I recommend the Separation of Behaviors Design Pattern which will let you code this once and call it from multiple rules (not a lambda).

It would look something like this:

Switch PlayRandom
rule "Play random sound"
when
    Item PlayRandom received command
then
        switch ThreadLocalRandom::current().nextInt(0, 2+ 1): {
            case 0: play AAA.wav
            case 1: play BBB.wav
            case 2: play CCC.wav
        }    
end

And you would call it with

PlayRandom.sendCommand(ON)

Also, just for an idea of what you can do, if you look at my Time of Day Design Pattern for calculating time periods and the following code I use to control my lighting you will see some things you can do to make the rules slight more flexible. I’m not advocating you do it this way, just showing what is possible.

Group:Switch:OR(ON,OFF) gLights_ALL "All Lights"

Group:Switch:OR(ON, OFF) gLights_ON
Group:Switch:OR(ON, OFF) gLights_OFF
Group:Switch:OR(ON, OFF) gLights_ON_MORNING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_MORNING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_DAY        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_DAY       (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_AFTERNOON  (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_AFTERNOON (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_EVENING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_EVENING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_NIGHT      (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_NIGHT     (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_BED        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_BED       (gLights_OFF)

Switch aFrontLamp "Front Room Lamp"
  (gLights_ALL, gLights_ON_MORNING, gLights_OFF_DAY, gLights_OFF_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node3:switch_binary" }
  
Switch aFamilyLamp "Family Room Lamp"
  (gLights_ALL, gLights_OFF_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node10:switch_binary" }
  
Switch aPorchLight "Front Porch"
  (gLights_ALL, gLights_OFF_MORNING, gLights_OFF_DAY, gLights_OFF_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node6:switch_binary" }

Above there is an ON group and an OFF group for each Time of Day. The lights themselves are added to the appropriate groups base don whether the light should be ON or OFF during their time period. Then those groups are added to the appropriate gLights_ON or gLights_OFF group.

val logName = "lights"

rule "Set lights based on Time of Day"
when
  Item vTimeOfDay changed
then
    val offGroupName = "gLights_OFF_"+vTimeOfDay.state.toString
    val onGroupName = "gLights_ON_"+vTimeOfDay.state.toString

    logInfo(logName, "Turning off lights in " + offGroupName)
    val GroupItem offItems = gLights_OFF.members.filter[g|g.name == offGroupName].head as GroupItem
    offItems.members.filter[l|l.state != OFF].forEach[l | l.sendCommand(OFF)]

    logInfo(logName, "Turning on lights for " + onGroupName)
    val GroupItem onItems = gLights_ON.members.filter[g|g.name == onGroupName].head as GroupItem
    onItems.members.filter[l|l.state != ON].forEach[l | l.sendCommand(ON)]
  
end

The above rule gets triggered when vTimeOfDay changes. It pulls the lights ON group from gLights_ON based on the current Time of Day. It turns all of those ON. It pulls the OFF group from gLights_OFF based on the current Time of Day and turns all of those OFF.

You could expand that to work with Dimmers fairly easily a number of ways (exercise left to the student).

Thus, the entirety of my lighting is controlled by a mere 12 lines of code (not counting the Time of Day code).

Thank you very much! I’ll try it out tomorrow.

For those that may stumble upon this in the future I learned (and rlkoshack can correct me if I’m wrong) that you adjust the number of possible cases in the “nextInt(0, 2+ 1)” part. It’s nextint(Min,Max) but because the max is not inclusive you need to add “+1” to it so that it would be "nextint(Min,Max+1)

Thanks for steering me in the right direction!

1 Like

Wanted to say, I’ve implemented both methods you documented (in rule and via switch) and they have worked perfectly.

One slight change I did was to change the PlayRandom switch in the rule to “received command ON” and set up the switch in the item file to return to OFF via the Expire binding.

And not that it matters, but there really wasn’t a plethora of Marvin sound clips online (much to my children’s dismay) so I just used the say command to create my own responses. Now when given a command “she (Betty)” can have an unlimited amount of random responses.

In a few days I’ll create a cleaned up tutorial thread of how to do this.

1 Like