Running a rule asynch / background thread?

Hi all,

whenever someone calls me on my landline, my google home devices tell me who is calling. This is done with a rule which uses a few say() commands. All devices will raise their voice sequentially (one after another). Is there any way to let them say() something at the same time? like running each command in it’s own thread?

Stefan

Hi @StefanH,
Can you post your rule, please?

1 Like
rule "Phone is ringing"
when
    // fboxRinging is a switch item which switches to ON if call is detected
    Item fboxRinging changed from OFF to ON
then
    // fboxIncoming call receives numbers/name of incoming call
    var incCall = fboxIncomingCall.state as StringListType
    var Account = incCall.getValue(0)
    var callerNumber = incCall.getValue(1)
    var incCallResolved = fboxIncomingCallResolved.state as StringListType
    var callerName = incCallResolved.getValue(1)

    logInfo("Incoming call", Account + " " + callerNumber + " / " + callerName)

    //var GoogleHomeVol_WZ = GoogleHome_Volume_WZ.state
    //var GoogleHomeVol_HK = GoogleHome_Volume_HK.state

    sendCommand(GoogleHome_Volume_WZ, 100)
    sendCommand(GoogleHome_Volume_HK, 100)

    if(callerName.contains("not found"))
    {
        logInfo("Incoming call", "Unbekannt")
        say("Unbekannter Anrufer","voicerss:deDE","chromecast:chromecast:1a878c7e0bad179784fd70253a8d80d9")
        say("Unbekannter Anrufer","voicerss:deDE","chromecast:chromecast:d56cc2812251c4df993b0b59f4953128")
    }else{
        var callerNameStripped = callerName.split("\\(").get(0)
        logInfo("Incoming call", callerNameStripped)
        say(callerNameStripped,"voicerss:deDE","chromecast:chromecast:1a878c7e0bad179784fd70253a8d80d9")
        say(callerNameStripped,"voicerss:deDE","chromecast:chromecast:d56cc2812251c4df993b0b59f4953128")
    }
    Thread::sleep(7000)
    sendCommand(GoogleHome_Volume_WZ, 40)
    sendCommand(GoogleHome_Volume_HK, 40)
end

Watch the two (say) commands.

You are saying that the two say command are executed sequentially one after another or does the second one starts before the first one has stopped, like there is a delay between the two?

I have an idea but it is a bit of a workaround
The idea is to create two rules in which you say something in each
Each rule will run it’s own thread

Create two switch items

Switch sayChromeCast1
Switch sayChromeCast2

Move the callerNameStripped variable at the top of your rule file so it become global in the file:

var callerNameStripped = ""

rule "Phone is ringing"
when
    // fboxRinging is a switch item which switches to ON if call is detected
    Item fboxRinging changed from OFF to ON
then
    // fboxIncoming call receives numbers/name of incoming call
    var incCall = fboxIncomingCall.state as StringListType
    var Account = incCall.getValue(0)
    var callerNumber = incCall.getValue(1)
    var incCallResolved = fboxIncomingCallResolved.state as StringListType
    var callerName = incCallResolved.getValue(1)

    logInfo("Incoming call", Account + " " + callerNumber + " / " + callerName)

    //var GoogleHomeVol_WZ = GoogleHome_Volume_WZ.state
    //var GoogleHomeVol_HK = GoogleHome_Volume_HK.state

    sendCommand(GoogleHome_Volume_WZ, 100)
    sendCommand(GoogleHome_Volume_HK, 100)

    if(callerName.contains("not found"))
    {
        callerNameStripped = "Unbekannter Anrufer"
        logInfo("Incoming call", "Unbekannt")
        sayChromeCast1.sendCommand(ON)
        sayChromeCast2.sendCommand(ON)
        //say("Unbekannter Anrufer","voicerss:deDE","chromecast:chromecast:1a878c7e0bad179784fd70253a8d80d9")
        //say("Unbekannter Anrufer","voicerss:deDE","chromecast:chromecast:d56cc2812251c4df993b0b59f4953128")
    }else{
        callerNameStripped = callerName.split("\\(").get(0)
        logInfo("Incoming call", callerNameStripped)
        sayChromeCast1.sendCommand(ON)
        sayChromeCast2.sendCommand(ON)
        //say(callerNameStripped,"voicerss:deDE","chromecast:chromecast:1a878c7e0bad179784fd70253a8d80d9")
        //say(callerNameStripped,"voicerss:deDE","chromecast:chromecast:d56cc2812251c4df993b0b59f4953128")
    }
    Thread::sleep(7000)
    sendCommand(GoogleHome_Volume_WZ, 40)
    sendCommand(GoogleHome_Volume_HK, 40)
end

rule "say ChromeCast 1"
when
    Item sayChromeCast1 received command ON
then
    say(callerNameStripped,"voicerss:deDE","chromecast:chromecast:1a878c7e0bad179784fd70253a8d80d9")
end

rule "say ChromeCast 2"
when
    Item sayChromeCast2 received command ON
then
    say(callerNameStripped,"voicerss:deDE","chromecast:chromecast:d56cc2812251c4df993b0b59f4953128")
end

Let me know if that works

This is really bad in Terms of perfomance and non intended side effects. In short: use the thread::sleep only shorter than a second, max should never reach more than a few seconds - because it simply blocks the few threads, openHAB has reserved. 7seconds is really long in terms of a CPU.

my approach would be something like a state machine there’s two tutorials on this one:

simple put: they will monitor a state and react on that. In your case I would add a proxy item, which indicates, that you didn’t pick up the phone yet and it has two or three states like the first 5 seconds, the second 5 seconds and the last 5 seconds. for each state change you just add more volume and can also have a different say-command.

1 Like

Vincent, thanks, that worked like a charm.

Thomas, maybe I can remove the sleep completely. I added it because I don’t know how long the say() command will run. Once it is finished I lower the volume of both chromecast devices. Now I noticed that it seems this won’t run in another thread, so the execution of the rule will wait anyway for say() to come to an end.

1 Like

weird, just tested this. unfortunately the volume is lowered “immediately” so I can hardly hear who is calling.

@binderth, I noticed, I was going to address that next

@StefanH,
Install the expire binding
Create another item

Switch ChromeCastTimer { expire="7s, command=OFF" }

Rules:

rule "Phone is ringing"
when
    // fboxRinging is a switch item which switches to ON if call is detected
    Item fboxRinging changed from OFF to ON
then
    // fboxIncoming call receives numbers/name of incoming call
    var incCall = fboxIncomingCall.state as StringListType
    var Account = incCall.getValue(0)
    var callerNumber = incCall.getValue(1)
    var incCallResolved = fboxIncomingCallResolved.state as StringListType
    var callerName = incCallResolved.getValue(1)

    logInfo("Incoming call", Account + " " + callerNumber + " / " + callerName)

    //var GoogleHomeVol_WZ = GoogleHome_Volume_WZ.state
    //var GoogleHomeVol_HK = GoogleHome_Volume_HK.state

    GoogleHome_Volume_WZ.sendCommand(100)
    GoogleHome_Volume_HK.sendCommand(100)
    ChromeCastTimer.sendCommand(ON)

    if(callerName.contains("not found"))
    {
        callerNameStripped = "Unbekannter Anrufer"
        logInfo("Incoming call", "Unbekannt")
        sayChromeCast1.sendCommand(ON)
        sayChromeCast2.sendCommand(ON)
    }else{
        callerNameStripped = callerName.split("\\(").get(0)
        logInfo("Incoming call", callerNameStripped)
        sayChromeCast1.sendCommand(ON)
        sayChromeCast2.sendCommand(ON)
    }
end

rule "ChromeCast volume down after call"
when
    Item ChromeCastTimer received command OFF
then
    GoogleHome_Volume_WZ.sendCommand(40)
    GoogleHome_Volume_HK.sendCommand(40)
end
2 Likes

Simpler rule:

rule "Phone is ringing"
when
    // fboxRinging is a switch item which switches to ON if call is detected
    Item fboxRinging changed from OFF to ON
then
    // fboxIncoming call receives numbers/name of incoming call
    var incCall = fboxIncomingCall.state as StringListType
    var Account = incCall.getValue(0)
    var callerNumber = incCall.getValue(1)
    var incCallResolved = fboxIncomingCallResolved.state as StringListType
    var callerName = incCallResolved.getValue(1)

    logInfo("Incoming call", Account + " " + callerNumber + " / " + callerName)

    //var GoogleHomeVol_WZ = GoogleHome_Volume_WZ.state
    //var GoogleHomeVol_HK = GoogleHome_Volume_HK.state

    GoogleHome_Volume_WZ.sendCommand(100)
    GoogleHome_Volume_HK.sendCommand(100)
    ChromeCastTimer.sendCommand(ON)

    if (callerName.contains("not found")) {
        callerNameStripped = "Unbekannter Anrufer"
        logInfo("Incoming call", "Unbekannt")
    } else {
        callerNameStripped = callerName.split("\\(").get(0)
        logInfo("Incoming call", callerNameStripped)
    }
    sayChromeCast1.sendCommand(ON)
    sayChromeCast2.sendCommand(ON)
end

Please note the use on the item method item.sendCommand instead of the action `sendCommand(String, String). It is recommended to use the item method when the item is known (Which is the case here). The Action can be used occasionally for example when the item name is generated in a rule.

2 Likes

Nice.

One idea to make the say ChromeCast rules generic.

Put the mapping between the Item name sayChromeCast1 and sayChromeCast2 and the address String in a .map file. Then the rules become

rule "Say ChromeCast"
when
    Item sayChromeCast1 received command ON or
    Item sayChromeCast2 received command ON
then
    val address = transform("MAP", "chromecast.map", triggeringItem.name)
    say(callerNameStripped, "voicerss:deDE", address)
end

When 2.3 is released we can replace the triggers with a Member of ChromeCastGroup.

@rlkoshak
The 2 different rule were to create 2 threads.
Will your mapping solution or even Member of also create 2 threads for “simultaneous” sound output?

Makes no difference, because the rule is triggered twice, with different triggeringItem.
But you have to keep in mind, that openHAB will per default only execute up to 5 rules in parallel threads. So, this will work for 2 players, but not for more than 4 players, (original rule plus 4 “say” threads)

Thanks, I thought so, just wondering