Device Attributes in Expressions - Brainstorming and Discussion

Migrating away from WebCORE (Smarthings / Samsung). Just checking that there is no way to directly use device values in expressions? I believe the answer is “no” and while I have achieved the same effects by setting up variables - it’s not a terribly elegant solution. Does that capability exist? If not, any plans to include it? Doing so would vastly simplify routines. Thanks!

You can use context variables that will spawn during the execution of a rule. They can pick up device values to be acted upon.

1 Like

Welcome to the community and thanks for posting!

As Bry noted, if the device triggers the rule, you can access the value using Context Variables (eg. $context.event.value).

Otherwise, you can not access Device State directly in an expression. Temporarily storing the device state in a variable is a reasonable approach.

I don’t believe I’ve seen any feature requests for this, but feel free to create one!

Can you share more feedback around what you find inelegant about using variables? I agree with you that the experience could be improved and have some ideas, but I’d love to hear which parts of the experience you see as pain points, so we can take them into consideration with future enhancements.

1 Like

Hmmm, thanks! I will read up on contect variables, I was only tracking the variables I created at the main console, which I presume to be global variables. I have a lot to learn here. :slight_smile:

@josh - I’m not a skilled enough programmer to really know “elegant” from “inelegant”. :slight_smile: But what I’m trying to say is that in the ecosystem I came from (WebCore), a basic routine for one of my most used automations involves running an air distribution system when different parts of the house are significantly colder than the living room (where the wood stove runs) was very simple to accomplish. On WebCore that would be a single line of code:

Trigger from temperature change at Temp Sensor #1
(1) If $TemperatureSensor1.Temperature > ($TemperatureSensor2@.Temperature + 5) Then VentilationFan.On Else VentilationFan.Off*

On Sharptools my set-up jumps from 1 to 7 lines of code (first world problems!) if I include the
three variables that I need to declare:

(1) Declare Variable TemperatureSensor1Temperature as Number
(2) Declare Variable TemperatureSensor2Temperature as Number
(3) Declare Variable Temp1Temp2Delta as Number (I’m still not sure why I needed to do this, but I did…?)

Trigger from temperature change at Temp Sensor #1
(4) Set Variable TemperatureSensor1Temperature as $TemperatureSensor1.Temperature
(5) Set Variable TemperatureSensor2Temperature as $TemperatureSensor2.Temperature
(6) Set Variable Temp1Temp2Delta as TemperatureSensor1Temperature - ( TemperatureSensor2Temperature)

(7) If ($Temp2Delta > 4) Then VentilationFan.On Else VentilationFan.Off

I was also not able to do an expression with my actual variables to eliminate Line 6 above. It seems like that should have worked, but my code just failed to run. I realize that may have been some other error on my part.

Bottom line, I really do feel like it would be orders of magnitude simpler to just reference the device attributes directly in expressions. If I’m just completely off base here - I’d love some education if you are willing to share your expertise :slight_smile:

Up next - can one build arrays on Sharptools? Not a question as I haven’t researched it yet, but my Smart Thermostat routines relied on arrays to smoothly handle temperature changes. . . .I replicated with a series of nested if statements for now, but again, from my uneducated point of view - that’s not an elegant solution.

1 Like

Is the ‘Declare Variable XXX’ part to indicate that you have to explicitly create those variables before you can use them in a rule?

And if I’m reading between the lines, it sounds like you wanted to have the condition be able to directly use an expression rather than requiring the use of a true/false variable. I don’t think there’s a feature request for that yet, but it’s certainly something that passed my mind.

Combining all this feedback, it sounds like if you could shorten the steps for being able to use device state within expressions somehow without having to declare global variables and could use that expression directly in an IF Condition, it would significantly improve the elegance factor that you’re looking for?

While this is great feedback, I would reiterate that feature requests are really the best way to get enhancements in place. This kind of discussion is super helpful for clarifying what the pain points are, but the feature requests (and the associated voting system) help us better understand what’s most important to the community as a whole and what we should work on next. :slight_smile:

Check out the following recent thread for some discussion on arrays and some examples:

Expression 'IF' statement or looping through an Array? - #2 by josh

Once you’ve had a chance to review things, if you’re still looking for some help creating/updating your rule, feel free to create a new thread and share some more details.

Yea, that’s exactly right. I’d much rather just directly access the data I need, and make decisions with it vs. declaring variables. It feels like an unnecessary step and adds complexity to the flow.

Also, thanks for the nudge on a feature request. Normally when I encounter an “ugh” with a programming system, it’s because I’m not doing it right - so that was my going in assertion here. That someone would say, “You CAN do that you dummy, you just aren’t doing it right.” :slight_smile:

2 Likes

Hey @Jeff_L - since you and @kevinpbe both asked about this recently, I figured I would provide a bit of context as to how expressions work and why they can’t access device state directly.

Expressions execute ‘synchronously’ which means that when an expression is evaluated (eg. the calculation performed), it can’t stop to wait for data from external sources.

Variables

You might be wondering how this works for variables then. Retrieving the current value for a variable, including many context variables, is an asynchronous operation so we work around this by performnig some ‘static analysis’ on the expression wherein we identify $Variable ‘tokens’ within the expression - we then perform the variable lookups just before running the expression so we can swap out the $Variable tokens with their replacement values.

WebCoRE Approach

While I suspect something similar could be done directly in expressions, I can’t think of an elegant and intuitive raw syntax for doing so. Since you both are coming from WebCoRE, I took a brief look at what they’re doing and it appears there’s a special [deviceName : attributeName] ‘syntax’ to support this. Long story short, this is not a scalable solution and has significant issues with syntax collision.

If you're curious why it's not ideal... (tap to expand)

I put ‘syntax’ in quotes when referencing the [deviceName : attributeName] format from WC, because it’s not as simple as it seems on the surface. Behind the scenes, it looks like the expression that’s stored is different than what you’re seeing in the editor. I’ve only very briefly looked at how WC handles this since that’s were both of you are coming from, but it appears to be storing a raw expression which includes the unique device identifier that’s different than what’s displayed in the editor.

This makes sense as “Downstairs Thermostat” isn’t a unique identifier, so a unique identifier needs to be stored somewhere otherwise you’re at the whim of a fresh lookup upon each execution and all the risks and performance issues associated with it.

Furthermore, while this is already something of an issue in a single-location execution environment like WC, it’s even more complicated when a rule can execute across multiple locations and platforms - for example ‘Downstairs Thermostat’ is the name of a device in my SmartThings, Hubitat, and Home Assistant.

In terms of collision, it’s too close to the array [] definition and it’s important to keep syntax distinct to avoid complicating things. One obvious complication being that the ‘token’ (syntax) isn’t actually real and is not a direct identifier, so it obscures the complexity of the aforementioned issues. For broader language features, its sometimes OK to acquiesce in these situations, but this is a very narrowly scoped function that I think would be better handled with a function or alias.

Adding in the async restrictions noted above, using an alias fits really well with the existing static analysis approach.

Brainstorming

That being said, the approach is actually very similar to an ‘alias’ rather than syntax. I would be open to doing something similar with expressions, but perhaps where the alias definition is more explicit. I think a reasonable first-pass enhancement would be to add a ‘Device Aliases’ link or something similar in the Expression block so you could quickly pick a Device + Attribute and create an ‘alias’ for it to use in your expression.

Since expressions already support their own form of expression-scoped variables, we could piggyback on these or perhaps define a special prefix. For simplicity, let’s say it piggy backed on expression scoped variables, that might look something like:

image

It might make sense put the reference list/editor behind a modal or some other design, but I wanted to at least demonstrate the concept.

If there’s enough demand for an even quicker approach for power-users, I could see something like typing an @ character popping up a list of devices where you could pick the device and attribute and be prompted for an alias. From that action, the editor could then create the reference and swap it out at your cursor position. I suspect this extra enhancement would be a stretch feature as there’s lots of other highly requested features and the base functionality would likely be a significant enhancement solving most of the core concerns.

3 Likes

Super helpful write-up @josh. I was aware of the effective “aliasing” in WebCORE because I’ve seen errors manifest there were my direct references to devices in code got decoupled from the actual device somehow. I never really understood why that happened, but effectively I’d wind up with a chunk of code no longer working - and when I went and looked at the code in WC instead of the device name I’d see a somewhat random looking string of characters. The fix was to edit the code, re-select the appropriate device, an then it would reset and work again. For a while at least. I had to make fixes like that several times a year. I suspect that issue is related to the issues you are highlighting.

I like the idea of a device reference created when a routine is triggered. More like a temporary variable declared at run time. That at least keeps all the code / variables, etc. required for a specific funtion all in one place. I’m not a highly skilled programmer and for me it makes things easier and more intuitive to have it all right there in front of me. I was also concerned because I have multiple functions manipulating the same variables, and often at the same time. I don’t see a way to sequence the individual routines easily (it did occut to me that I could create a sequencing variable, and use the (wait) command to effectively do this) - which led me to worry about a race condition / unexpected variable state.

But I realize that’s probably a separate issue and unrelated to this topic. Anyways, I really appreciate your highly constructive engagement here. I’m not sure exactly how to suggest the “device reference” idea you had, do I just post in the Feature Request group?

I’m trying to combine a text variable and a number variable into a single variable. 2 questions…

  1. can I do this using expressions and saving the device attributes into a single variable with local variables as below?
Set text variable 
name = $context.event.deviceName
value = $context.event.value
concat(name," ",value,"%")
  1. or do I need to use 3 separate variables and concat them into one?

Yes, you may need to format the number as a string first. You can use the toFixed(input, digits) method which formats a number as a string with the specified number of decimal digits (function docs):

name = $context.event.deviceName
value = toFixed($context.event.value)
concat(name," ",value,"%")

In my example above, I’m rounding to an integer. If you wanted to keep any decimal places, you would specify the second optional parameter like toFixed(input, 1) or toFixed(input, 2).

1 Like