Did isEmpty(deviceVariable) change?


#1

I have been using this in a piston for a while, I just noticed that I’m not getting alerts for open contacts/unlocked doors recently. My log shows the following:

|+278ms|║║Comparison (error) Invalid parameters. Expecting isEmpty(value) is (boolean) false = false (1ms)|
|---|---|
|+279ms|║║Condition #27 evaluated false (10ms)|
|+284ms|║║Comparison (error) Invalid parameters. Expecting isEmpty(value) is (boolean) false = false (2ms)|

Here’s the part of the piston that it relates to…

image

Edit: Here’s a snippet of a log from the 8th where it evaluated correctly.

|+331ms|║║Comparison (boolean) false is (boolean) false = true (2ms)|
|---|---|
|+333ms|║║Condition #27 evaluated true (7ms)|
|+334ms|║║Condition group #26 evaluated true (state did not change) (9ms)|
|+336ms|║║Cancelling statement #21's schedules...|
|+365ms|║║Executed virtual command sendPushNotification (21ms)|
|+374ms|║║Calculating (string) Alert Zones: + (string) Lorenz Window >> (string) Alert Zones: Lorenz Window|
|+377ms|║║Calculating (string) Alert Zones: Lorenz Window + (string) >> (string) Alert Zones: Lorenz Window|
|+379ms|║║Executed virtual command setState (1ms)|
|+381ms|║║Condition #4 evaluated true (145ms)|
|+382ms|║║Cancelling condition #1's schedules...|
|+383ms|║║Condition group #1 evaluated true (state changed) (148ms)|
|+386ms|║╚Execution stage complete. (157ms)|

#2

Log it to console and see what it is outputting as


#3

What is the value of those alertZone variables? Still the window sensor device? Does the referenced device still exist? I’m curious why it thinks the arguments to isEmpty are invalid


#4

This is what it contains during the last run…

image


#5

It worked without an error this morning. Strange, the only difference is Micheal Window is closed today.

|+331ms|║║Comparison (boolean) false is (boolean) false = true (2ms)|
|---|---|
|+333ms|║║Cancelling condition #27's schedules...|
|+333ms|║║Condition #27 evaluated true (14ms)|
|+334ms|║║Cancelling condition #26's schedules...|
|+335ms|║║Condition group #26 evaluated true (state changed) (17ms)|
|+338ms|║║Cancelling statement #21's schedules...|
|+363ms|║║Executed virtual command sendPushNotification (18ms)|
|+372ms|║║Calculating (string) Alert Zones: + (string) Lorenz Window >> (string) Alert Zones: Lorenz Window|
|+375ms|║║Calculating (string) Alert Zones: Lorenz Window + (string) >> (string) Alert Zones: Lorenz Window|
|+378ms|║║Executed virtual command setState (0ms)|

#6

So this is a weird one. I can’t really speak to how it worked in the past other than to say that this particular function hasn’t been changed. If there were any changes to how params are passed into functions that could be related.

The parameters for isEmpty and all other functions are supposed to be wrapped up into an array, so isEmpty(1, 2, 3) should have something like params = [1, 2, 3]. The functions validate based on the value of params: in this case it must be an array and it must have exactly 1 item meaning that isEmpty(1) is valid but isEmpty(1, 2) is not. The item is supposed to be an expression that isEmpty then evaluates.

The problem is that isEmpty(alertZone1) is using alertZone1 instead of that params value, where instead we would expect alertZone1 to get wrapped in an array just like the 1, 2, 3 case. Since the device list is used as if it were individual parameters to the function, isEmpty errors out if you have any more or fewer than one device in the list.

As an ugly workaround you can use an argument that has to be evaluated as an expression to avoid it mapping to the list. The following works as expected for me:

(expression) isEmpty(null ?: alertZone1) »»» (boolean) false
(expression) isEmpty(null ?: alertZone2) »»» (boolean) true

The null ?: does not serve any purpose other than getting that argument passed in as an expression.

This is not something that I would be comfortable fixing without unit tests in place for the functions to ensure that supporting this does not break other functions that take devices. That’s coming, but down the road still.


#7

Thanks for looking into it. It is indeed a weird one but it worked today so I’d leave it at that for now or just resort to using count(alertZoneX) as an alternative.


#8

Ouch yeah it seems that everything relies on this behavior for arguments. Array arguments are spread into the other arguments, so given someArray = [1, 2, 3] if you call someFunc(someArray, 10) it is indistinguishable from calling someFunc(1, 2, 3, 10). That’s definitely going to cause problems down the road with array functions considering something like intersection(listA, listB) not being able to know when listA stops and listB starts…

Fortunately that suggests that there is probably a valid short-term fix for the bug you reported in allowing isEmpty to take any number of arguments.


#9

Fortunately this seems to only affect device lists; other lists like string[] are passed with the entire list as a single argument as expected. I should not have extrapolated that behavior out from device lists to arrays without testing!


#10

Wow, you really went in deep into figuring that one out :slight_smile: Just out of curiosity and without trying it first, would isEmpty(listA, listB) work?


#11

Currently, only if both were device lists and between them there was exactly one device since this function validates that there is exactly one param :slight_smile: Otherwise if they’re something else like string lists it would not pass validation since that will come through as two separate params