Blockly: how to use 'return;'

hello, I’m running openhab 3.4.4 at the moment. Is there an elegant way to implement something that represents the ‘return;’ statement in blockly? I’m currently working around the problem like this:

image

it does the job, but maybe I could do better. I tried using the “inline script” block, but I’m not sure how to getting that to work in this case.

The Script Action isn’t a function so you cannot use a return statement unless you put the whole body of the Script Action into a function. In the function blocks menu you will find an “if [ ] return” block for that case. So the blocks would look something like:

function runme() {
  if some condition == true return
  do stuff
}
call function runme

But you don’t need to do even that much. Swap your condition so it reads != or tests for false and the code will be nearly as elegant as can be supported.

if some condition != true
  do stuff

Fewer blocks, fewer indentations.

A yet even better approach could be to use a Script Condition

some condition

and then your Script Action won’t even run unless some condition is true so it doesn’t need to test for anything at all.

do stuff

But that only works if it’s an either do something or don’t do anything situation.

Hi @rlkoshak, use cases like these contain situations where certain rule triggers are not interesting or would cause infinite loops. Code like this effectively deals with that while preserving a good overall readability:

if(source == "Proxy") {
        logWarn("light", "Received command on " + triggeringItem.name + ", this should not occur, ignoring.")
        return;
}

I also have been experimenting with the “if [ ] return” block, but I never managed to exit the rule with it. only the function

Right, and that’s exactly what the rule conditions are best used for. Baes on that example, keeping the code as Rules DSL, add a Script Condition to the rule with

val source = triggeringItem.name.split("_").get(1)
// The Proxy Item should never receive a command
if(source == "Proxy") {
    logWarn("light", "Received command on " + triggeringItem.name + ", this should not occur, ignoring.")
    return;
}

becomes the following in a Script Condition:

val source = triggeringItem.name.split("_").get(1)

// The Proxy Item should never receive a command
if(source == "Proxy") {
    logWarn("light", "Received command on " + triggeringItem.name + ", this should not occur, ignoring.")
 }
source != "Proxy"

and now we don’t have to worry about that case at all in the Script Action because the condition prevents the action from running in the first place if the condition doesn’t match. And the only reason it’s as complicated as that is so we preserve the log statement. Without that it becomes a one liner:

triggeringItem.name.split("_").get(1) != "Proxy"

The Script Action becomes simpler not only because it has less code but it also has fewer branches/paths through the code.

// Get access to all the relevant Items
val lightName = triggeringItem.name.split("_").get(0)
val source = triggeringItem.name.split("_").get(1)

val proxy = LightControls.members.findFirst[ l | l.name == lightName + "_Proxy" ]
val device = LightControls.members.findFirst[ l | l.name == lightName + "_Device" ]
val ui = LightControls.members.findFirst[ l | l.name == lightName + "_UI" ]
val rules = LightControls.members.findFirst[ l | l.name == lightName + "_Rules" ]

// When a command comes in from any source not the Device, a command gets sent to the Device
// This let's us skip this command so we don't end up in an infinite loop.
if(source == "Device") {
    Thread::sleep(50) // experiment, may not be needed, may need to be longer, give the Item registry time to update the proxy
    if(receivedCommand == proxy.state) return;
}

// Detect whether the light was triggered manually or automatically and do what needs to be done
if(source == "Device" || source == "UI") {
    // manually controlled
}
else {
    // automatically controlled
}

// Forward the new state to those Items that are not already in the new state. Use sendCommand 
// for the device so the light actually turns on/off.
if(proxy.state != receivedCommand) proxy.postUpdate(receivedCommand)
if(ui.state != receivedCommand) ui.postUpdate(receivedCommand)
if(rules.state != receivedCommand) rules.postUpdate(receivedCommand)
if(device.state != recevivedCommand) device.sendCommand(receivedCommand)

For this example at least, it’s far better to implement it as a Condition than a return anyway.

But again, the script action and script condition isn’t running in a function so you can’t return from it. So once again your options are (in order of preference):

  1. implement the condition as a script condition
  2. swap around your if conditions
  3. put the whole body of the script action/condition into a function

What you tried to do with the repeat block is just a less optimal way to do 3.

Let’s take a more complicated but related example. Here’s my current implementation of manual trigger detection using the timing based approach (note this is JS Scripting).

I’ve two separate conditions:

conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: TimeOfDay
      state: DAY
      operator: =
    type: core.ItemStateCondition
  - inputs: {}
    id: "4"
    label: Manual trigger?
    description: TimeOfDay changed more than 10 seconds ago and vIsCloudy more than
      5 seconds ago
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >-
        var {timeUtils, testUtils} = require('openhab_rules_tools');

        console.debug('Checking to see if enough time has past since a transition');

        testUtils.sleep(300); // give the Items a chance to update, should probably become a timer


        var isBefore = function(item, secs) {
          const lastUpdate = item.history.lastUpdate('mapdb');
          const secsAgo = time.toZDT('PT-'+secs+'S');
          return lastUpdate.isBefore(secsAgo);
        }


        var tod = isBefore(items.getItem('TimeOfDay'), 10);

        var cloudy = isBefore(items.getItem('vIsCloudy'), 5);


        tod && cloudy;
    type: script.ScriptCondition

Only if the TimeOfDay is “DAY” and it has been at least 10 seconds since TimeOfDay changed and five seconds since vIsCloudy changed does the condition pass. That’s some relatively complicated code over all.

But now I don’t have to worry about any of that in my script action. I know the conditions are correct so I can just do what needs to be done which is just a two liner (one of which is a log statement.

actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >
        console.info('Manual light trigger detected, overriding the light for {}', event.itemName);
        items[event.itemName].replaceMetadata('LightsOverride', 'true', []);
    type: script.ScriptAction

Use conditions and both the code for the conditions and the actions will become much simpler and readable.