Using `format()` to build notification and other strings
-
A few new functions have worked their way into Reactor's expression language over the last couple of months, and while they are documented in the change logs and documentation, it seems some of you are not aware of them, so I'm going to pick one out and highlight it, maybe do others later...
A lot of you build strings in expressions by simple concatenation, and there's nothing wrong with that, but there's a new function in the expression language that can help you clean things up, in particular when handling numeric values.
I've noticed many of you will round your results either on the calculation, or in some cases, in a separate variable. For example, say we have a pair of expressions that retrieves the indoor and outdoor temperature from a sensor and the weather service:
indoor = getEntity( 'hubitat>144' ).attributes.temperature_sensor.value' ) outdoor = getEntity( 'weather>home' ).attributes.wx.temperature
The weather service, since it converts from degrees K in its reporting to degrees C, may have an irrational or repeating value (e.g. 8.233333333...). As a result, when you compute the difference, it, too, may have an irrational or repeating result, so you decide to round it to one decimal:
tempDiff = round( outdoor - indoor, 1 )
Then, perhaps you have a rule that if the differential is greater than 10 degrees, you send a notification, and the notification uses all three variables in the message string:
${{ 'The outdoor temperature is ' + outdoor + 'C and the indoor temperature is ' + indoor + 'C; the difference is ' + tempDiff + 'C' }}
This is fine, except that the outdoor temperature isn't rounded, so you decide to fix that. There are at least three ways: add a
round()
to theoutdoor
variable expression, or create a third variable likeoutdoorRounded = round( outdoor, 1 )
, or, use theround()
function into the substitution expression${{ 'The outdoor temperature is ' + round( outdoor, 1 ) + 'C and the indoor temperature is ' + indoor + 'C; the difference is ' + tempDiff + 'C' }}
It's all getting very messy... Let's use
format()
instead of string concatenation, and not worry about rounding the difference, so we keep the maximum precision possible for any calcs or tests that use it. We go back to:indoor = getEntity( 'hubitat>144' ).attributes.temperature_sensor.value' ) outdoor = getEntity( 'weather>home' ).attributes.wx.temperature tempDiff = outdoor - indoor
Now our string substitution:
${{ format( "The outdoor temperature is {0:.1f}C and the indoor temperature is {1:.1f}C; the difference is {2:.1f}C", outdoor, indoor, tempDiff ) }}
Here,
format()
is building the message string. Each{}
(curly-brace) pair denotes the substitution of a parameter to follow. The number first inside the opening curly brace is the parameter number, starting from 0 as the first parameter. So parameter 0 in this example refers to the variableoutdoor
, and 1 isindoor
, and 2 istempDiff
. Using the numbers, parameters can be inserted in any order.Each of these substitutions in the example includes an optional format specification. If no format specification is given (e.g.
{0}
), then the parameter is converted to a string with no special formatting, but since we can get numbers like 8.233333... it's desirable to use some formatting to make it pretty. The format is separated from the parameter number by the:
(colon) in the substitution. The format we're using for all three parameters is.1f
, which is<width>.<precision>f
. Any number before the dot is a fixed field width, but since we want no extra spaces, we just don't specify a width (it's blank for our example), and the value will take as much space as it needs. Thef
at the end tellsformat()
that the value is going to be formatted as a floating point number, and the.1
before thef
tells the formatter to display a single decimal digit. So our 8.23333333 value would be displayed as 8.2, without using extra functions or variables, or dropping precision in a calculation. For clarity, 10.875 would be displayed/inserted as 10.9 in this example, because the formatter will round the displayed result to the specified number of decimal digits.Tip: for simple string insertions that require no special formatting,
format()
will let you skip the parameter numbers if the parameters are in the same order as the substitutions, and the default format is (generic) string (s
), soformat( "{} x {} = {}", "alpha", "beta", "gamma" )
produces the stringalpha x beta = gamma
.Docs for format are in the Reactor documentation under the heading Special Reactor Functions.
-
A few new functions have worked their way into Reactor's expression language over the last couple of months, and while they are documented in the change logs and documentation, it seems some of you are not aware of them, so I'm going to pick one out and highlight it, maybe do others later...
A lot of you build strings in expressions by simple concatenation, and there's nothing wrong with that, but there's a new function in the expression language that can help you clean things up, in particular when handling numeric values.
I've noticed many of you will round your results either on the calculation, or in some cases, in a separate variable. For example, say we have a pair of expressions that retrieves the indoor and outdoor temperature from a sensor and the weather service:
indoor = getEntity( 'hubitat>144' ).attributes.temperature_sensor.value' ) outdoor = getEntity( 'weather>home' ).attributes.wx.temperature
The weather service, since it converts from degrees K in its reporting to degrees C, may have an irrational or repeating value (e.g. 8.233333333...). As a result, when you compute the difference, it, too, may have an irrational or repeating result, so you decide to round it to one decimal:
tempDiff = round( outdoor - indoor, 1 )
Then, perhaps you have a rule that if the differential is greater than 10 degrees, you send a notification, and the notification uses all three variables in the message string:
${{ 'The outdoor temperature is ' + outdoor + 'C and the indoor temperature is ' + indoor + 'C; the difference is ' + tempDiff + 'C' }}
This is fine, except that the outdoor temperature isn't rounded, so you decide to fix that. There are at least three ways: add a
round()
to theoutdoor
variable expression, or create a third variable likeoutdoorRounded = round( outdoor, 1 )
, or, use theround()
function into the substitution expression${{ 'The outdoor temperature is ' + round( outdoor, 1 ) + 'C and the indoor temperature is ' + indoor + 'C; the difference is ' + tempDiff + 'C' }}
It's all getting very messy... Let's use
format()
instead of string concatenation, and not worry about rounding the difference, so we keep the maximum precision possible for any calcs or tests that use it. We go back to:indoor = getEntity( 'hubitat>144' ).attributes.temperature_sensor.value' ) outdoor = getEntity( 'weather>home' ).attributes.wx.temperature tempDiff = outdoor - indoor
Now our string substitution:
${{ format( "The outdoor temperature is {0:.1f}C and the indoor temperature is {1:.1f}C; the difference is {2:.1f}C", outdoor, indoor, tempDiff ) }}
Here,
format()
is building the message string. Each{}
(curly-brace) pair denotes the substitution of a parameter to follow. The number first inside the opening curly brace is the parameter number, starting from 0 as the first parameter. So parameter 0 in this example refers to the variableoutdoor
, and 1 isindoor
, and 2 istempDiff
. Using the numbers, parameters can be inserted in any order.Each of these substitutions in the example includes an optional format specification. If no format specification is given (e.g.
{0}
), then the parameter is converted to a string with no special formatting, but since we can get numbers like 8.233333... it's desirable to use some formatting to make it pretty. The format is separated from the parameter number by the:
(colon) in the substitution. The format we're using for all three parameters is.1f
, which is<width>.<precision>f
. Any number before the dot is a fixed field width, but since we want no extra spaces, we just don't specify a width (it's blank for our example), and the value will take as much space as it needs. Thef
at the end tellsformat()
that the value is going to be formatted as a floating point number, and the.1
before thef
tells the formatter to display a single decimal digit. So our 8.23333333 value would be displayed as 8.2, without using extra functions or variables, or dropping precision in a calculation. For clarity, 10.875 would be displayed/inserted as 10.9 in this example, because the formatter will round the displayed result to the specified number of decimal digits.Tip: for simple string insertions that require no special formatting,
format()
will let you skip the parameter numbers if the parameters are in the same order as the substitutions, and the default format is (generic) string (s
), soformat( "{} x {} = {}", "alpha", "beta", "gamma" )
produces the stringalpha x beta = gamma
.Docs for format are in the Reactor documentation under the heading Special Reactor Functions.
-
Love it! Two things just so you know I was paying attention:
- Numbers like 8.2333... are not "irrational" (as 'pi' would be), just plain rational-but-repeating.
- Yay for new functions like this; only reason I never commented or utilized is that my focus has been forced elsewhere lately.
Definitely planning to revisit things like my dozen or so "Messaging" rules that insert expressions into boilerplate text, à la mail merge.
THANKS for your ongoing dedication to making MSR the snizznel!! -
A few new functions have worked their way into Reactor's expression language over the last couple of months, and while they are documented in the change logs and documentation, it seems some of you are not aware of them, so I'm going to pick one out and highlight it, maybe do others later...
A lot of you build strings in expressions by simple concatenation, and there's nothing wrong with that, but there's a new function in the expression language that can help you clean things up, in particular when handling numeric values.
I've noticed many of you will round your results either on the calculation, or in some cases, in a separate variable. For example, say we have a pair of expressions that retrieves the indoor and outdoor temperature from a sensor and the weather service:
indoor = getEntity( 'hubitat>144' ).attributes.temperature_sensor.value' ) outdoor = getEntity( 'weather>home' ).attributes.wx.temperature
The weather service, since it converts from degrees K in its reporting to degrees C, may have an irrational or repeating value (e.g. 8.233333333...). As a result, when you compute the difference, it, too, may have an irrational or repeating result, so you decide to round it to one decimal:
tempDiff = round( outdoor - indoor, 1 )
Then, perhaps you have a rule that if the differential is greater than 10 degrees, you send a notification, and the notification uses all three variables in the message string:
${{ 'The outdoor temperature is ' + outdoor + 'C and the indoor temperature is ' + indoor + 'C; the difference is ' + tempDiff + 'C' }}
This is fine, except that the outdoor temperature isn't rounded, so you decide to fix that. There are at least three ways: add a
round()
to theoutdoor
variable expression, or create a third variable likeoutdoorRounded = round( outdoor, 1 )
, or, use theround()
function into the substitution expression${{ 'The outdoor temperature is ' + round( outdoor, 1 ) + 'C and the indoor temperature is ' + indoor + 'C; the difference is ' + tempDiff + 'C' }}
It's all getting very messy... Let's use
format()
instead of string concatenation, and not worry about rounding the difference, so we keep the maximum precision possible for any calcs or tests that use it. We go back to:indoor = getEntity( 'hubitat>144' ).attributes.temperature_sensor.value' ) outdoor = getEntity( 'weather>home' ).attributes.wx.temperature tempDiff = outdoor - indoor
Now our string substitution:
${{ format( "The outdoor temperature is {0:.1f}C and the indoor temperature is {1:.1f}C; the difference is {2:.1f}C", outdoor, indoor, tempDiff ) }}
Here,
format()
is building the message string. Each{}
(curly-brace) pair denotes the substitution of a parameter to follow. The number first inside the opening curly brace is the parameter number, starting from 0 as the first parameter. So parameter 0 in this example refers to the variableoutdoor
, and 1 isindoor
, and 2 istempDiff
. Using the numbers, parameters can be inserted in any order.Each of these substitutions in the example includes an optional format specification. If no format specification is given (e.g.
{0}
), then the parameter is converted to a string with no special formatting, but since we can get numbers like 8.233333... it's desirable to use some formatting to make it pretty. The format is separated from the parameter number by the:
(colon) in the substitution. The format we're using for all three parameters is.1f
, which is<width>.<precision>f
. Any number before the dot is a fixed field width, but since we want no extra spaces, we just don't specify a width (it's blank for our example), and the value will take as much space as it needs. Thef
at the end tellsformat()
that the value is going to be formatted as a floating point number, and the.1
before thef
tells the formatter to display a single decimal digit. So our 8.23333333 value would be displayed as 8.2, without using extra functions or variables, or dropping precision in a calculation. For clarity, 10.875 would be displayed/inserted as 10.9 in this example, because the formatter will round the displayed result to the specified number of decimal digits.Tip: for simple string insertions that require no special formatting,
format()
will let you skip the parameter numbers if the parameters are in the same order as the substitutions, and the default format is (generic) string (s
), soformat( "{} x {} = {}", "alpha", "beta", "gamma" )
produces the stringalpha x beta = gamma
.Docs for format are in the Reactor documentation under the heading Special Reactor Functions.
I have the following array > BatteryLow = ["Door Auxiliar Room 66%","Door Lock Kitchen 55%","Motion Closed 33%","Motion Kitchen 65%"]
Using the format() function > ${{ format( "List of low battery: {}.", BatteryLow ) }}
Generates the result in a notification > List of low battery: Door Auxiliar Room 66%,Door Lock Kitchen 55%,Motion Closed 33%,Motion Kitchen 65%.
All separated by a comma ",".
Examples (spaces shown as "⋅" for clarity only):
Questions:
- how is it possible to format the separator to be for example comma space ",⋅"
List of low battery: Door Auxiliar Room 66%,⋅Door Lock Kitchen 55%,⋅Motion Closed 33%,⋅Motion Kitchen 65%.
- Or if I want to change for example, space bar space "⋅/⋅"
List of low battery: Door Auxiliar Room 66%⋅/⋅Door Lock Kitchen 55%⋅/⋅Motion Closed 33%⋅/⋅Motion Kitchen 65%.
-
This has been mentioned and used in other threads on the more specific topic of creating and handling lists of failed or otherwise "in need of notification" devices...
You are passing an array into
format()
directly, and asking it to format it as a string using default formatting. The default formatting isn't very interesting/pretty for arrays. But thejoin()
function specifically converts an array to a string using a provided separator (another string). -
This has been mentioned and used in other threads on the more specific topic of creating and handling lists of failed or otherwise "in need of notification" devices...
You are passing an array into
format()
directly, and asking it to format it as a string using default formatting. The default formatting isn't very interesting/pretty for arrays. But thejoin()
function specifically converts an array to a string using a provided separator (another string).@toggledbits Ok got it, I was using joing() before, and it generated the third variable with the final text.
As we discussed in the other threads when using sort() that already generates the final name, I'm adding the space (" " + e.name), so it solved the presentation.
The format() function is very interesting, and worked great with array, eliminated one step and one variable.
Thanks
-
@toggledbits Ok got it, I was using joing() before, and it generated the third variable with the final text.
As we discussed in the other threads when using sort() that already generates the final name, I'm adding the space (" " + e.name), so it solved the presentation.
The format() function is very interesting, and worked great with array, eliminated one step and one variable.
Thanks
@wmarcolin said in Using `format()` to build notification and other strings:
I was using joing() before, and it generated the third variable with the final text.
Don't understand. Can you elaborate?
-
@wmarcolin said in Using `format()` to build notification and other strings:
I was using joing() before, and it generated the third variable with the final text.
Don't understand. Can you elaborate?
@toggledbits as we said, we are good at creating problems, not explaining
this is also valid when we are creative
Ok, before I first created an array with the list of devices with problem (OldFaultOn) and then in a second step without sorting the names, I generated into a new variable the string with the names (OldFaultMSG), separated by comma and space.
With the brilliant sort() function I am already directly generating the list of device names, sorted.
This eliminated having to have other variable to store the information to be displayed. As commented before, when using format() ahead, the function displays the whole array separated only by a comma. To improve the display it would be good to have a comma and space before the next name. So I decided to put space in the sort (" " + e.name).
I previously set up the notification like this.
And now using sort() and format().
I believe we have many ways of doing things, I am looking to improve the use of functions.
-
Since the result of
sort()
is an array, you can wrap thesort()
withjoin()
, no need for an extra variable.FaultOn = join( sort( each id of FaultSensor: do e=getEntity(id), e?.attributes?.x_vera_device?.failed == true ? e.name : null done ), ", " )
This would, of course, change the type of
FaultOn
to a string.If you wanted to preserve the array for other uses, you could also apply the
join()
to theformat()
substitution on the array:${{ format( "Failed devices: {}", join(FaultOn, ", ") ) }}
-
Since the result of
sort()
is an array, you can wrap thesort()
withjoin()
, no need for an extra variable.FaultOn = join( sort( each id of FaultSensor: do e=getEntity(id), e?.attributes?.x_vera_device?.failed == true ? e.name : null done ), ", " )
This would, of course, change the type of
FaultOn
to a string.If you wanted to preserve the array for other uses, you could also apply the
join()
to theformat()
substitution on the array:${{ format( "Failed devices: {}", join(FaultOn, ", ") ) }}
@toggledbits nothing like talking to a Jedi master.
I will go with the second option, to preserve the array, I think it may have a better future use.
Thanks!
-
Since the result of
sort()
is an array, you can wrap thesort()
withjoin()
, no need for an extra variable.FaultOn = join( sort( each id of FaultSensor: do e=getEntity(id), e?.attributes?.x_vera_device?.failed == true ? e.name : null done ), ", " )
This would, of course, change the type of
FaultOn
to a string.If you wanted to preserve the array for other uses, you could also apply the
join()
to theformat()
substitution on the array:${{ format( "Failed devices: {}", join(FaultOn, ", ") ) }}
With the new DynamicGroupController option, is it now possible to override all this instruction to use format() directly?
Or is it necessary to first transform the IDs that appear in the object carrying the name and format, and only then be able to send a text message?
Can you help Patrick, I am not really sure now how to go about this.
Thanks.
-
I will leave this for you to think about, or perhaps some other users can chime in with some assistance. Try some things, observe, fail, and learn.
-
I will leave this for you to think about, or perhaps some other users can chime in with some assistance. Try some things, observe, fail, and learn.
@toggledbits done
sort( each id in getEntity('groups>low_battery').attributes.sys_group.members: getEntity(id).name + " " + int(getEntity(id).attributes.battery_power.level *100) + "%")
-
T toggledbits locked this topic on