Expressions and LuaXP Functions
-
MSR RECIPES: Compact object-based message composer
Several of my Rules send out SMTP emails, and I'm always looking for a compact method of composing these outbound messages, using the fewest expressions along with a simple "mail merge" template. Here's a two-variable approach you might consider:
msgBody ► "This is my message." // leave blank if msgBody will be set by other Rules msgTemplate ► tmp = {hdr:"Header here" , msg:msgBody , ftr:"Footer here" , crlf:"\n"}, send = {message:tmp.hdr+tmp.crlf+tmp.msg+tmp.crlf+tmp.ftr} , send.message
I hope this can be adapted to your workflow somehow!
-
This post is deleted!
-
Can someone please explain how you would create an array within a Rule that does the following:
(a) begins with an expression that is an empty array;
(b) has another expression containing the .dim_level of a given light;
(c) each time the Rule sets (activates/triggers), the value of (b) is pushed into (a)Working example:
First time light gets turned on, the rule runs. Dim level of 50 is pushed onto end of [ ], making it [50].
Next time light turns on, the rule again runs and pushes the dim level of 75 onto array, making it [50,75].
Etc.I can't quite grok where to put my
push ( )
. In a[Set Variable]
using${{ }}
substitution? In an auxiliary Expression? (I've attempted this approach and can only get one iteration along, meaning the array stops growing after 1 element.) And how does one declare an empty array inside an Expression that will otherwise be modified by the Rule, which normally requires it to be blank?Thanks!
@toggledbits , to put this more succinctly, I find myself unable to implement in MSR the construct you laid out here for creating a time series.
-
LibraSunwrote on Apr 18, 2021, 2:13 PM last edited by LibraSun Apr 18, 2021, 11:18 AM
SOLUTION: This works for creating a timed data series in MSR...
test1
:= if go=="1" then push( test1 , getEntity( "vera>device_110" ).attributes.dimming.level , 5 ) else test1 endif...BUT...you must first pre-populate thetest1
variable with an empty array[ ]
,SAVE
, then paste in the above definition. Otherwise, MSR will throw a "no such 'push' function on (null)" error! (I call this the "Chicken and the Egg" conundrum, whereas Reactor for Luup doesn't complain about doingpush
to a non-existent array; it just invisibly creates one for you.)In my
set
reaction, I do a[Set Variable] [ go ] = 1
, and in thereset
reaction[Set Variable] [ go ] = 0
, with anInterval
imposed on the Rule's sole Trigger condition, making it goTRUE
for 2 seconds every 60 seconds.NOTE: Interestingly, with
Interval
limited to 3 repeats, the variablego
changes to"1"
only 3 times (expected), yet somehow the array gets fully populated with 5 values (unexpected), so I must not fully understand the leading- and trailing-edge nature ofInterval
as it applies to Expression re-evaluation. I'm sure @toggledbits will elucidate? Or is this truly unexpected behavior?ANSWER: Unchecking "Force re-evaluation of expressions and conditions" in both the
set
andreset
reactions cured the problem of "too many data points". There was my problem. -
@librasun said in Expressions and LuaXP Functions:
you must first pre-populate the test1 variable with an empty array
You don't actually. This is just a bug in lexpjs. I've fixed it in my local copy, will be in the next build. It's just this simple:
-
LibraSunwrote on Apr 24, 2021, 2:15 PM last edited by LibraSun Apr 24, 2021, 10:16 AM
@toggledbits Do you want to include conditional key references like this in lexpjs?
obj={'122':['a',5, true]}
obj?['123'][0] // null or throws error
obj?['122'][0] // (string) "a"By this I mean do you want to have square bracketed key references treated the way potentially non-existent dot references are handled using question marks.
-
Confused. Are you asking for this to be added?
-
I think so, if it's possible. Just wanted to (a) make sure that ?[ doesn't exist yet (my testing seems inconclusive), and (b) you consider it necessary. I've already bumped into a potential use case.
So, I suppose "Yes, add it" at least for consistency with existing ?. usage.
-
toggledbitsreplied to LibraSun on Apr 24, 2021, 5:41 PM last edited by toggledbits Apr 24, 2021, 1:41 PM
@librasun OK... so I think I'm seeing through the confusion now...
The
?[
and?.
operators already exist; they were added together with??
a long time ago. They are left-associative, so they refer to the operand on the left. In your examples, they will not work as you are trying to get them to becauseobj
is non-null in every case (it's the first thing defined in the example). I think what you mean to say is:obj={'122':['a',5, true]} obj['123']?[0] // which will return null and not throw an error obj['122']?[0] // which will return (string) "a"
-
Agree with everything you just wrote. I was mis-applying the question marks, as far as placement in my expression (treating it as a prepend instead of append). Thanks for straightening me out yet again.
I had mistakenly thought this construct only worked on
objects.?with.?arbitrary.?keys
and arrays[with_arbitary_indexes]? and notobject
keys (with non-standard names) referred to via brackets. My bad. -
CODE SAMPLE: Creating an Array of N Whole Numbers [0, 1, ... , N]
Sometimes you need an array of Counting Numbers from zero to some arbitrary integer N. But it's hard to write this as an Expression in MSR, because you don't know in advance how long to make the array, and the value of N may change later.
This somewhat byzantine construct fills the bill, and works for N=
0
up to N=56
:arrCount := N=56,each s in keys(substr((s='s'+1/9)+s+s,0,N+1)): int(s)
This example evaluates to the array:
arrCount == [0,1,2, … ,55,56]
Just change the number after
N=
(it could also be another variable name, of course!) to suit your needs.BONUS: Need more numbers? Just add another
+s
in the middle for an additional 19 integers (giving max. N=75). Repeat indefinitely!P.S. @toggledbits I may need to enter this in the Obfuscated Lexpjs Code contest!
-
toggledbitswrote on May 1, 2021, 3:11 PM last edited by toggledbits May 1, 2021, 11:29 AM
I think it's a grand prize winner for the "Solutions for Problems Nobody Has Ever Had Using Reactor" contest, for sure, but I'll admit it's not a straightforward problem to solve without a counting loop structure, so the question is, what's the best approach:
- Implement a counting loop statement (
for
in many languages); - Take the Python-ish approach and have either a
range()
function or astart..end
range operator, which you could use as the operand ineach
; - Other?
Choice 2 is better suited to an expression environment, but we already ride the fine edge with
each
,first
anddo
statements in the current language. But in keeping with the flavor, each of these statements still produces an expression result, so still arguably as much operator as statement; it's less clear what a sensible return result from "for" might be. On the other hand, #2 requires an array be built in memory over which to iterate, and an error could easily build a large array that consumes dangerously large amounts of (or all) available memory and have a negative impact on stability (so perhaps more safeguards in order, but how big is too big?).EDIT: I just prototyped the two options given above:
# Example for to build array of values 1-25 arr=[]; for i =1,25: push(arr, i) # That's unnecessarily clunky and could be streamlined by making "for" return an array # of result values like "each": arr=for i=1,25: i # But then... that means "for" is about the same as "each", just different operands. # In my mind, that kind of argues for option 2: arr=each i in range(1,25): i # or arr=each i in 1..25: i # and even that's unnecessary, because you could just do arr=range(1,25) arr=1..25 # because both of those naturally return an array. # Another example, to sum all the values from 1 to 25 it would be: sum=0; for i in 1,25: sum=sum+i sum=0; each i in range(1,25): sum=sum+i sum=0; each i in 1..25: sum=sum+i
I think the range operator
..
is the cleanest all around, and relieves the ambiguity of having bothfor
andeach
so similar. I'm not sure..
is any easier or harder to learn/remember than having a functionrange(start,end)
. So I'm leaning toward option 2 with range operator..
. - Implement a counting loop statement (
-
100% agree that
sum=0; each i in 1..25: sum=sum+i
makes the most sense in terms of conciseness without introducing another function name. I don't foresee any usability issues with anm..n
operator generally.I'd expect
..
to accept any integer,–
or+
, for its argumentsm
andn
, regardless ifm
>n
orn
>m
. Use cases include step functions or monotonic sequences for things like dimming levels over time.Sure looks better than what I came up with, eh?
Glad I brought it up, because I had recently been hand-coding my own arrays for use with
each
. -
@toggledbits For today's checkup on how Expressions handle assigning values by reference, I constructed:
a=b=c=[1,2,3], pop(a), shift(b), [a==b,b==c,a==c,c]
// result is [true,true,true,[2]]
A. Pls confirm this is as expected. (I'm guessing 'Yes')
B. Does result comport with typical user's expectations? (I'm guessing 50/50)
C. I know you don't want me mashing that "Try" ► button (in lieu of actual, useful, runtime evals).Just something that's been on my mind, wondering if it belongs in your Test Suite.
-
toggledbitswrote on May 2, 2021, 6:50 PM last edited by toggledbits May 2, 2021, 2:54 PM
You can't compare arrays (or objects) with ==. What it's telling you when you write
a==b
is that neither a nor b are null, 0, or false, and they are identical references. So yes, this is all expected.As for it comporting with user expectations, if the user is a programmer, I think this will be no surprise. Non-programmers have the learning curve. Again, another reason I really don't like guiding users to expressions. New users, IMO, should be blissfully unaware of their existence.
-
Wise words!
-
toggledbitswrote on May 2, 2021, 10:13 PM last edited by toggledbits May 3, 2021, 7:23 AM
By the way, I forgot to mention, as is often necessary in many languages because of the reference issue, there is a
clone()
function that makes a new copy of an existing array or object. Soa=[1,2,3],b=clone(a),push(b,99)
will result ina
having[1,2,3]
andb
having[1,2,3,99]
. -
It's as if you read my mind. I suppose my numerous "cloning arrays" examples gave it away.
Without
clone()
, I was never (easily) able to manipulate a copied array without affecting the original, and that was driving me bonkers.Thanks!
-
@toggledbits , am digging how the new
a..b
operator behaves predictably, fixing the decimal ofa
and spitting out integer steps up to≤b
when botha
,b
> 0 anda
<b
. For example:a = 1 .. 3.3 // result (array) [1,2,3] a = 1.1 .. 2.3 // result (array) (array) [1.1,2.1]
But sometimes it tries too hard, particularly in this case where
a
< 0:a = -2.3 .. 1.1 // result (array) [-2.3,-1.2999999999999998,-0.2999999999999998,0.7000000000000002]
or here, where
a
>b
:a = 2.3 .. 1.1 // result (array) [2.3,1.2999999999999998]
All testing thus far using only INTEGERS has avoided any rounding issues, so that will likely become my mantra on usage.
rev. 21123 -
LibraSunwrote on May 14, 2021, 2:42 PM last edited by LibraSun May 14, 2021, 2:30 PM
KEEP TURNING OFF LIGHTS!
I thought it might be fun to have a Rule that periodically watches for lights (any device, really) left on, but I wanted the entire "action" part of the Rule to live inside a single expression. Here's what I came up with:
devs = each dev in getEntity( "vera>controller_all" ).members: match( dev , ".*device_\\d+" ), devices = each dev in devs: indexOf( getEntity( dev ).actions, "dimming.set" ) > -1 ? dev : null, performAction( f = first dev in devices with getEntity( dev ).attributes.power_switch.state, "power_switch.off", { } ), getEntity( f ).name + " turned off."
This example only checks dimmable lights paired with Vera, but could easily be modified to include other classes of device connected to other controllers, etc.
The way it works is it (Line 1) pulls all of the member devices associated with Vera; (Line 2) filters the list to disregard devices not of the formdevice_NNN
; (Lines 3-4) checks to see whether each device handles dimming; and, (Line 5) passes along only those forperformAction()
to turn off individually (one "On" device per cycle); finally, (Line 6) the name of the most recently turned-off device is logged.I concocted this behemoth because my Vera routinely chokes on my "Lights Out" rule, which attempts to switch 20 devices OFF all at once. I felt it might be more prudent to handle the switching at a slower pace, with the added benefit that if anyone turns ON one of the listed devices during "Lights Out" time, this rule would spot it and turn it back off automatically.
Comments?
- Libra