Bindenv and dynamic callbacks

Hello,

I am dynamically setting a pin callback based on a firebase table, and am having problems with bindenv. Here is a code snippet:

`
input1 <- Button(hardware.pinL, DIGITAL_IN_PULLUP, Button.NORMALLY_HIGH);

…else if (value.type == “input”) {
server.log("in value.type input : " + value.name)
if (value.event == “high”) {
this[“input”+value.option].onRelease(function() {
server.log(“in input trigger release”);
server.log(value.name)
executeActions(value.actions)
}.bindenv(this))
} else if (value.event == “low”) {
this[“input”+value.option].onPress(function() {
server.log(“in input trigger press”);
server.log(value.name)
executeActions(value.actions)
}.bindenv(this))
}
}`

I am using the button class. The callbacks are set and execute the executeActions function, but they are the same for either high or low cases, and the value.name returns the same. I am sure this has to do with the bindenv. I tried bindenv(value) [throws error not table | instance | userdata etc]. Is there a way to pass this array of data into the bindenv? I read acall() but not sure if this is the correct route.

Thanks in advance…

Looks like “this” is the global context here. You could try:

`....else if (value.type == "input") {
            local context = this["input"+value.option]
            server.log("in value.type input : " + value.name)
            if (value.event == "high") {
                context.onRelease(function() {
                    server.log("in input trigger release");
                    server.log(value.name)
                    executeActions(value.actions)
                }.bindenv(context))
            } else if (value.event == "low") {...`

Hmm… thought that would work… Here is some code to demonstrate the issue [or more appropriately my lack of understanding :slight_smile: ] :

`#require “Button.class.nut:1.2.0”

trigger <- {
“first” : {
“option” : “1”,
“event” : “low”
“type” : “input” ,
“name” : “test”,
“actions” : {
“0” : {
“type” : “test”,
“data” : “hello”
}
}
}
“second” : {
“option” : “1”,
“event” : “high”
“type” : “input” ,
“name” : “test2”,
“actions” : {
“0” : {
“type” : “test2”,
“data” : “hello2”
}
}
}
}

function executeAction(actions) {
foreach (i,v in actions) {
server.log("data = " + v.data + ", type = " + v.type)
}
}

input1 <- Button(hardware.pinL, DIGITAL_IN_PULLUP, Button.NORMALLY_HIGH);

function processTrigger() {
foreach (key, value in trigger) {
if (value.type == “input”) {
local context = this[“input”+value.option]
server.log("in value.type input : " + value.name)
if (value.event == “high”) {
context.onRelease(function() {
server.log(“in input trigger release”);
server.log(value.name)
executeAction(value.actions)
}.bindenv(context))
} else if (value.event == “low”) {
local context = this[“input”+value.option]
context.onPress(function() {
server.log(“in input trigger press”);
server.log(value.name)
executeAction(value.actions)
}.bindenv(context))
}
}
}
}

processTrigger()

`

logs:

2016-08-07 20:29:15 UTC-4 [Device] in value.type input : test 2016-08-07 20:29:15 UTC-4 [Device] in value.type input : test2 2016-08-07 20:29:33 UTC-4 [Device] in input trigger press 2016-08-07 20:29:33 UTC-4 [Device] test2 2016-08-07 20:29:33 UTC-4 [Device] data = hello2, type = test2 2016-08-07 20:29:33 UTC-4 [Device] in input trigger release 2016-08-07 20:29:33 UTC-4 [Device] test2 2016-08-07 20:29:33 UTC-4 [Device] data = hello2, type = test2

Are you looking to dynamically assign button behaviour to your input pins? If not, you can just add the Release and Press handlers at the moment the button is instantiated.

I guess I mean dynamic at runtime. On boot I request a table from firebase, pass to device, then process triggers. I need to be able to assign callbacks based on this table, and the variables associated with (spefically the actions).

I have been working with angular for a few weeks now, and I thought I may be inappropriately assigning this with my reference of $scope. Peter?

This isn’t a bindenv problem; this is a closure problem.

Whenever you write a function in Squirrel, it has access to local variables of (lexically) outer functions. So in your callbacks that you pass to onRelease and onPress, you have access to local variables of the processTrigger function. (And in all those functions you have access to local variables of the top-level main function.) This access is known as “capturing” or “closing over” those outer variables, and is done by keeping a reference to those local variables – not by taking a copy.

So your onRelease handler, for instance, uses the name “value”, which is a local variable of the surrounding processTrigger function. When the closure is constructed – when context.onRelease is called for the first trigger – the closure incorporates a reference to processTrigger’s local variable “value”. And then what happens? the foreach loop goes round again, which assigns the next value to the local variable “value”. And the already-built closure also sees that assignment. That’s why all the callbacks end up with the same value in “value” – it’s because they’re all sharing a reference to a single variable.

And the way to fix it, is just to arrange that they don’t all share the reference. One way is to declare new, temporary local variables that go out of scope again without being changed:

`
            if (value.event == "high") {
                local temp = value;
                context.onRelease(function() {
                    server.log("in input trigger release");
                    server.log(temp.name)
                    executeAction(temp.actions)
                }.bindenv(context))
            } 
`

but in fact the more idiomatic (if slightly sneaky) way is to pass the data to your callback not in the closure (by reference) but as a function parameter, like this:

`
           if (value.event == "high") {
                context.onRelease(function(value=value) {
                    server.log("in input trigger release");
                    server.log(value.name)
                    executeAction(value.actions)
                }.bindenv(context))
            }
`

where, in “function(value=value)”, the first “value” is the name of a function parameter (so all references to “value” within the function are scoped to refer to the parameter, and the second “value” refers to the local variable of processTrigger, which is assigned (copied) as the default value of the parameter.

Peter

That was it. I assumed that was what was happening, but also assumed that bindenv would counter that. I was doubtful that I could pass a parameter into the callback function, as normally this cant be done with imp.wakeup. I sense the button class handles this. Thank you!