Lambda Functions or similar: Create an array / map to iterate through to send 'timed' commands

Tags: #<Tag:0x00007f2faffce078> #<Tag:0x00007f2faffcdf10> #<Tag:0x00007f2faffcdda8>

Hello,

Generally, I am struggling to find some exhaustive documentation or IDE for rules and lambda functions and the xtend language. I find it a lot of guess-work concerning what types are defined, how the syntax should be, etc.
I am thinking about using the Exec bridge and have the stuff carried out by shell scripts, c-code or similar, but this would kind of undermine the whole idea of openhab, I guess…

Anyway:
I am using openhab2 on a cubietruck which is an armbian based board similar to RPi etc.
The board has IR-LED, RF transmitters etc. connected to it, basically using GPIO functions to control IR receivers (stereo, a transmitter to a projector and the projector itself). I am using the LIRC bridge for this.
Now I am using rules. E.g.: if I turn on the projector, turn on the amplifier as well, roll down the screen and turn on the transmitter (sending the video content to the projector which is under the ceiling).
Timing is very crucial here, as the ir-diode is put on and of in us delays to ‘mimic’ remote controls.

The issue is, that if I use several LIRC commands in a rule consecutively, it is not working, There needs to be a delay, otherwise the consecutive signals interfere or only part of the signals are caught or transmitted properly. In the syslog I sometimes find errors “busy repeating” meaning that the sending of the previous signal has not finished when the next one is sent to lirc.
I tried to delay the signals with Thread::sleep, but this is not working at all. I guess when the CPU is sleeping, it messes up the timing for the lirc, for good.

The idea is that I give each of the devices a time slot (e.g. the amplifier power, the amplifier input selector, the transmitter, screen and projector) and after 200ms the amplifier power is receiving a command, if it needs to, after 400ms the input selector etc.
The rule can then build a a hashmap or something similar and when everything is clear that needs to be done, a reusable lambda function can be called that iterates through this map and fires the necessary lirc commands by creating a set of timers at the dedicated time slots

I tried to create two maps and a Lambda (just for testing, for the time being, not doing anything, yet):

val functionMap =<Integer, String>newHashMap()
val commandMap =<Integer, String>newHashMap()
val Functions execCommands = [Map<Integer, String> fm, Map<Integer, String> cm |
    fm.forEach[tim, func |
            logInfo("execCommands: time",tim.toString)
            logInfo("execCommands: func",func)
            logInfo("execCommands: comm",cm.get(tim))
    ]
]

in the rule, I add “stuff to these maps” and then call the lambda (or I try)

    functionMap.put(200,"amplifier")
    commandMap.put(200,"on")
    ....
    execCommands.apply(functionMap,commandMap)

I tried to define the lambda as “Runnable” or leave the type definition empty, but I am not getting anywhere, here.
I get the following errors in the log:

There is no context to infer the closure’s argument types from. Consider typing the arguments or put the closures into a typed context.

2019-08-21 00:59:03.181 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'entertainment.rules', using it anyway:
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
2019-08-21 00:59:03.471 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'entertainment.rules'
2019-08-21 00:59:16.250 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'amplifier on': 'apply' is not a member of 'org.eclipse.xtext.xbase.lib.Functions'; line 22, column 2, length 42

Any idea, either where to find a full documentation of types like Runnable, Functions or this example here: Reusable Functions: A simple lambda example with copious notes ?
Or of course, even better, a suggestion on how to solve this problem?

What do you find lacking in that example?
A browse through all the “design patterns” is a good grounding in capabilities of rules DSL.

This may be the wheel already invented -

I think you may need
val HashMap<String, Object> functionMap = new HashMap()

I think you need either
val Functions$Function2 execCommands = [ ...
or
val execCommands = [ ...

Thanks for the prompt answer. I will definitely look into the Design Patterns and revisit the other hints this evening.
The Lambda example is clear and well documented, but I am missing some comprehensive documentation on available classes, available fields and methods for the class etc.
E.g. I did not know if a Map is possible with Integers instead of Strings as index parameter, what types can be used in a map, if the size needs to be allocated etc. I find the Xtend documentation on Expressions very confusing with that respect and feel I am missing something somewhere.

Or, in the example you are using the class GenericItem. For me, it is not obvious that this must be GenericItem and not Item, Object etc.

I tried the “val execCommands” approach (not specifying a type), but got very similar error messages, here and something related to the Map<Integer, String> not being defined. I do not remember the exact wording. But then, this may be because the index of the Map must be of type String…

Me too.
There are years of practical experience in these forums, however, open to searching.

The only reason there isn’t a comprehensive guide is that no-one has written one. Open source by volunteers.
I wouldn’t recommend anyone starting one now either, with the abandonment of rules DSL in sight.

VSCode with the openHAB extension. https://www.openhab.org/docs/developer/ide/vscode.html#vscode. It has syntax highlighting, error checking, and code completion.

https://www.openhab.org/docs/configuration/rules-dsl.html, https://www.eclipse.org/xtend/documentation/203_xtend_expressions.html, https://docs.oracle.com/javase/8/docs/api/, https://github.com/openhab/openhab-core/tree/master/bundles, and https://www.joda.org/joda-time/apidocs/index.html should pretty much cover all the documentation that exists.

For the most part, VSCode should help a lot though. Just type the name of the entity and a . and a pop up will appear telling you all the members and methods on that Object.

One important thing to realize is that all of Java is available to you. Most everything you are working with in Rules is a Java Object. Rules DSL is a way to script the Java and it has some unique expressions but underneath it’s Java.

If you already know how to program, you would probably be happier using one of the JSR223 Rules Languages: Jython, JavaScript, or Groovy.

Not necessarily. The Exec binding and executeCommandLine exist because we know that OH Rules can never fully cover all use cases. But your particular use case is achievable using Rules.

A Thread::sleep only causes the thread the Rule is running in to pause for that amount of time. It does not impact the CPU over all and it should not cause the external lirc to fail. But you don’t provide enough info to see what might be going on.

Regardless, a Thread::sleep may not be the best idea, see Why have my Rules stopped running? Why Thread::sleep is a bad idea for details.

rossko57 has already posted one better approach to do this instead of using lambdas. Another would be to use Timers.

Amplifier.sendCommand(ON)
createTimer(now.plusMillis(200), [ | Screen.sendCommand(ON) ]
createTimer(now.plusMillis(200+400), [ | Projector.sendCommand(ON) ]
createTimer(now.plusMillis(200+400+300), [ | Source.sendCommand(ON) ]

and so on. Timers schedule an action to take place in the future without blocking the Rule.

Building a Map and using lambdas is way more complicated than is necessary for this. Given that a HashMap and lambda were your first thoughts here, I believe that is further evidence that you would be happier coding in a JSR223 language.

The first error is complaining about the lambda which is not properly defined. The second error is complaining because you are trying to use the improperly defined lambda. Functions doesn’t have an apply method. Functions$Function2 does.

But as the example in the link you provided indicates, you do not (and I’ll add should not) need to specify the type. You will be much better off in Rules DSL if you do not specify the type unless you really have to (e.g. you are defining a variable and initializing it to null, in all other cases the type will be inferred based on what you are assigning to it). One reason this is the case is because a Functions$FunctionX must return a value and you don’t have a return in your lambda. The value of the last line executed will be used as the return (you don’t need to actually use return but logInfo is void which cannot be returned by a Functions$FunctionX. What you want is a Procedure$ProcedureX. But when you don’t try to specify the type of the lambda ahead of time, the parser sees there is no return value and uses the correct type automatically.

So

val execCommands = [ Map<Integer, String> fm, Map<Integer, String> cm |

I also notice that you are defining your Maps in a weird way that may or may not work. I don’t think I’ve seen it done that way. I typically see

val Map<Integer, String> functionMap = newHashMap

But all things considered, I see both the use of global lambdas and the use of Map in this way to be a code smell for Rules DSL. They usually indicate you are trying to force the Rules DSL to work in the way you want it to rather than adjusting your approach to how Rules DSL likes. That’s one of the major reasons I wrote the Design Patterns and its why I don’t recommend Rules DSL to people who already know how to code or are unable to bend their approach to the language. There is almost always a better way to achieve what you are trying to do with them, in this case there are at least two better ways.

You are probably missing that it’s a java.util.Map. They are core Java classes.

You have to be aware of and familiar with the types of certain things when you are dealing with lambdas (another mark against their use). In that case I have to use GenericItem because Item is a reserved word in Rules DSL and I can’t use Item. But I know that the parent class of all Items is GenericItem so I used that. It could have been Object which is the parent class for ALL objects but there is no .state method on Object.

A lot of this can be very simply discovered using VSCode while others need a bit of research.

If you are using the key as a simple index, a Map is not the correct data structure to use in the first place. A Map is intended to be used for random access, not in order access. In fact, a Map doesn’t even store the elements in order. You want a List/ArrayList which is an ordered list of elements.

It’s more than that. A truly comprehensive guide would be hundreds and hundreds of pages long with duplication of documentation that already exists in at least three other projects we have no control over. It would be a huge effort up front and become a huge long term maintenance nightmare.

Even commonly used languages like C++ and Python lack the sort of thorough documentation that most users seem to want OH to have.

Wow, this is a great summary for everything. Thank you so much.
The idea with the map was an overkill and I am using the timers, directly, now.
However, I run into the next problem :thinking:

2019-08-21 22:02:31.079 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'amplifier on': 'plusMillies' is not a member of 'org.eclipse.smarthome.core.library.items.DateTimeItem'; line 21, column 14, length 31

I tried now.plusSeconds, etc., but it is not taking this at all. I was searching all over for similar problems. Apparently, no includes are needed. I installed a persistence service rrd4j and used it as default, but the problem persists.
The full code for the rule is here:
https://pastebin.com/bVPD3NWp

DateTime type Items don’t have plusMillis etc. methods.

I had seen that, but what I am confused about it that it states that the Joda DateTime is the default used in rules (it even says somewhere ‘most notably now’) and the Joda DateTime has the plusMillis, plusSeconds etc. methods.
The “millies” is a typo or a leftover of my random and desperate attempts to fix it ;), I am using plusMillis without ‘e’.
What also confuses me is that I have in the meantime installed VisStudio with the openhab extension, as recommended by @rlkoshak , and it is not making any suggestions after ‘now.’

ok, I just found it. It is now().plusMillis(someInt)
It is a static method of DateTime

Did you configure the extension with the hostname/ip? Do you see any errors in either openhab.log or the bottom panel of VSCode? To do the syntax checking and completion VSCode needs to be able to connect to a running openHAB instance.

I didn’t. I fixed it now and it has more code sense/suggestions, but it still is not handling the “now” field or “now()” method.
now().plusMillis works as expected, but still a bit confusing since I found many examples that mention now.plusSomething. May that changed in a recent update.
Thanks for your help!

now.plusSeconds(5) or whatever should work just like that. That is the way it has always been since before OH 1.6. The last time I used VSCode it would show all the methods on now too. Are you typing now. and waiting for the pop-up, or forgetting the .?