Lambda within lambda

Hi there,

I’m trying to set up a bit of code to control a wifi webcam having some http api.

To factorize code, i created a lambda “moveCamera” that will be called from 4 rules , one for each direction.

For some reasons (basically, i show only snapshots on my panel, not a stream), i need to force a refresh of an Angular variable containing the snapshot url. This is done by either a timer, OR after moving my camera. Because i have those 2 use cases , i also put the code in a lambda called “updateCameraSnapshotUrl”

Now 1st question :
When moveCamera calls updateCameraSnapshotUrl, i see an error in the log :

[ERROR] [.script.engine.ScriptExecutionThread] - Rule 'Camera controlR': cannot invoke method public abstract java.lang.Object org.eclipse.xtext.xbase.lib.Functions$Function0.apply() on null

so i end up duplicating the code rather than using my lambda.

My 2nd question : as you will see in below code, i used the ip+port of the camera between quotes, instead of an available global val. Because if a use the global val, its value is empty in the lambdas. Any idea why ?

Here is the whole rules code :

val String cameraHostPort = "192.168.0.44:80"

val Functions.Function0<void> updateCameraSnapshotUrl = [|
	logInfo("CameraHost", cameraHostPort)
	var url = "http://"+"192.168.0.44:80"+"/snapshot.cgi?user=admin&pwd=888888&_ts="
	url += now.hashCode
	postUpdate(CameraSnapshotUrl, url)
	
	null
]

val Functions.Function1<Integer, void> moveCamera = [
    Integer commandId |

    sendHttpGetRequest("http://"+"192.168.0.44:80"+"/decoder_control.cgi?loginuse=admin&loginpas=888888&command="+(commandId)+"&onestep=0")
        Thread::sleep(10)
        sendHttpGetRequest("http://"+"192.168.0.44:80"+"/decoder_control.cgi?loginuse=admin&loginpas=888888&command="+(commandId+1)+"&onestep=0")

	// Lambda from lambda does NOT work 
	//updateCameraSnapshotUrl.apply()
	// ---> Rule 'Camera controlR': cannot invoke method public abstract java.lang.Object org.eclipse.xtext.xbase.lib.Functions$Function0.apply() on null
	
	var url = "http://"+"192.168.0.44:80"+"/snapshot.cgi?user=admin&pwd=888888&_ts="
	url += now.hashCode
	postUpdate(CameraSnapshotUrl, url)
	
	null
]

rule "Camera update"
    when Time cron "/20 * * * * ?"
then
    updateCameraSnapshotUrl.apply()
end

rule "Camera controlL"
    when Item Webcam_Starcam_Control_Left received command
then
    moveCamera.apply(4)
end

rule "Camera controlR"
    when Item Webcam_Starcam_Control_Right received command
then
    moveCamera.apply(6)
end
 

Tx for your help !

You can refer to lambda X inside lambda Y, but remember that everything external referred to in a lambda (except Items) must be passed into it.
You’ll need to add another parameter and pass in the name of the lambda function.
Examples (ignore the Timer stuff, the interest is passing one function to another)

For your string (IP) you have a choice of either passing it in as another parameter, or put it into a String Item where you can get at it.

I had a similar issue. To fix, I passed the lambda function in as an argument.

In your example:

In the rule, change the call to this:

moveCamera(4, updateCameraSnapshotUrl).apply()

Then define moveCamera to have 2 arguments, like this:

val Functions.Function2<Integer, Functions.Function0<void>, void> moveCamera = [
    Integer commandId,
    Functions.Function0<void> updateCameraSnapshotUrl |

Hopefully, that will work for you. There may be a cleaner way to do it, but I haven’t found any other way.

Thanks guy. Both solutions of passing lambda as parameter , and a static string as an Item work fine.

Now i’d have the tendency to think that code is more heavy with this implementation.

  • It’s not good for encapsulation to have to give any implem details at the caller level. Maintenance effort will be far more costly. (Playing a little bit with Scala, i initially thought lambda could have access to other functions).
  • defining Items + init at startup + “xxx.state.toString” is full of boilerplate, especially if i want to do the same for username, password, and whatever else is needed at global level.

Anyway, it’s like that and it works, that’s the essential for now.

Thanks again.

Yes, everyone knows its clumsy and improvements could be made
http://docs.openhab.org/configuration/rules-ng.html

Meanwhile we got we got, and as you say, it works.

Hi, I also have problems to call my lambda - any idea?

val Functions.Function0<Integer> selectTvChannel = [
    Integer Channel
|
	executeCommandLine("/usr/share/openhab2/bin/philtv_control.sh presskey "+Channel.toString(), 15000)
	logInfo("TVCH", "    keys send.")

null
]

rule "Select TV-Channel"
when
	Item TV_CHANNEL received command
then
	logInfo("TVCH", "Switch TV Channel: " + TV_CHANNEL.state.toString())

	logInfo("TVCH", " switch TV on")
	callScript("tv_on")
	logInfo("TVCH", " switch TV channel")
selectTvChannel.apply(1)
	logInfo("TVCH", " TV channel switched")

openHAB shows

your code goes here

That’s for a function with zero parameters.
Maybe you meant val Functions.Function1

ha - cool. Sometimes syntax is a devil :slight_smile: