Establish Reference/Pointers to a $settings.switch object

To the best of my knowledge, you are indeed “missing something”.

The Device Objects (ie, along with their Events, Attributes and Commands) can only be accessed by their explicit input variables or input lists (ie, “Relay1”, “Relay2”, etc.).

There is (in my recollection / experience) no way to assign this reference nor access it indirectly through the “settings” map or similar references.

Repeat after me: Only the input variable can access the Object.

Why? - Because only the customer can set and change the selected inputs, and this ensures that the customer is in full control of which of his/her Devices that your SmartApp can access.

Make sense???

1 Like

I get all that, and all I’m trying to do is reference the item that is in the settings/preferences{} which was input by the user…

i.e a pointer to Relay1
Switch = Relay1
Switch.on()

if I am not mistaken, that can be done…

TurnOnSwitch(Relay1)

def TurnOnSwitch(Switch) {
Switch.on()
}

all of these things "REFERENCE the original user input “Setting.Relay1”

All we’re really trying to do here is avoid creating CRAPPY code and harcoding those object NAMES… i.e. Relay1, Relay2, … etc…

There must be some way…
Since I cant passit through a map via runIn, somehow I have to get my bearings back…
I’ve also considered and tried things like a state.map but haven’t had much luck there either…
Would be nice to just old the reference in state.activeSwitch and recall it on the callback… somehow

This passes the Switch Object by reference and, if you find it is working, it is permitted.


What’s not permitted is anything that could possibly bypass the security mechanism (and, yes, some perfectly “safe” scenarios are crippled as a side-effect).

For example, the state[] map is global (to the SmartApp) and also persistent between invocations.

So: If you could say state.usersSwitch = Relay1 then this is a security risk because if the customer subsequently updates the SmartApp’s preferences and points Relay1 to a different Device Object, and then state.usersSwitch will still point the old input which references an object the customer no longer wishes to permit you to use.

Wouldn’t this be safe if state.usersSwitch = Relay1 was just a pointer to a pointer to the Object? Sure… but that’s not SmartThings’s choice of implementation, due to their design of serializing (to JSON) all persistent data. Furthermore, ensuring that a dereferenced pointer was not illegally manipulated would require extra effort in the architecture.

It may be that you are either overestimating the functionality available in a SmartApp (remember, SmartApps are sandboxed), or are just temporarily too absorbed in your particular implementation choice to come up with a creative alternative.

For arbitrary example, instead of having 1 SmartApp, consider the model of the official Smart Lighting SmartApp and use a Parent-Child configuration. Each Irrigation Channel Child SmartApp manages one Relay (it’s own relay and the specific schedule and settings for that relay). The Parent is where any over-arching functionality can still live.

There are likely good examples out there (though, sadly, Smart Lighting has not been open sourced by SmartThings).

hmmm, not entirely true, you can do some interesting stuff with the settings map and in may ways there are many more efficient ways to capture inputs when you have large number of inputs. This is where groovy is at it’s best for run time references like settings."relay{i}". Mind you there are a few quirks and bugs in ST when using run time references like this one in an input variable (reported these in the past to ST somewhere on the forum and also provided a workaround for them)

You are however correct that the state map cannot store device objects because it’s serialized, the best way to handle it is to save the object id and correlated it back to the object. You’re also correct that you cannot access an object not selected by a user, but the story doesn’t end there.

You however don’t need to go rummaging through the entire settings map, which BTW is a VERY VERY expensive operation in ST to touch the settings map, so definitely not a good idea to iterate through it (and yes I’ve seen timeouts from the platform when there are too many accesses to the settings map), rather use more efficient ways to track your object id’s and references.

3 Likes

Well… I was sort of paraphrasing; and I’m focusing on the problem @klockk is experiencing … i.e., the inability to merge an input list of devices into a single list.

But please confirm for me:

  • Device Objects (i.e., the variable in an Input Statement) cannot be assigned to anything that gets serialized (such as state.* and the parameter to runIn() and perhaps a few other cases). Corrent?

  • Device Objects cannot be assigned to temporary variables (i.e., variables within the scope of a method); e.g., def mySwitch = input1. Or can they?

I know that various properties of the Device Object can be read and combined, which certainly can lead to useful code; but such properties do not include the ability to subscribe to Events nor issue Commands:

e.g.,

def data = []
switches?.each{data << getDeviceData(it, "switch")}
dimmers?.each{data << getDeviceData(it, "dimmer")}

Thanks for pointing this out. I was thinking that the fallback to the settings[] map was a goose-chase.

Correct, since they are serialized and stored to the DB objects are lost barring special cirumstances

Yes they can, the run time environment can manipulate any object and use most of groovy’s features barring a few special restrictions from ST (like reflections or using static objects)

The simple solution to what @klockk is trying to do (if I understood it correctly) is turn off a relay after x seconds/minute, the easiest way to do this would be to pass the relay number in the data parameter to runIn and then access that relay dynamically at runtime like settings."Relay{data.no}"?.off(), simple and efficient.

3 Likes

I agree.

Not coding nearly as frequently as you has me forgetting basic “groovyisms” like the evaluation of strings into variables. Regardless of the gyrations it took to get here, I think you’ve found and recommended an elegant, simple and applicable solution.

3 Likes

BINGO!
As I was trying to do in the first place!Grab the devise Id using getId methods, then later I can make reference to the original “Setting” by name using the format:

    log.debug "settingName is ${switchName}"
    log.debug "Relay is " + settings."${switchName}"
    log.debug "Relay state is " + settings."${switchName}".currentSwitch

This works Beautifully! Thank You for the simple suggestion in your text!

Now…

if things were all dynamic, and all II have is some device identifier, how would one get back to the “Setting” nae in the app without walking the settings map?

I’ve come up with this, but you suggest it is costly to do this…

def getSwitchById(id) {
  String keyName = null
  settings.each { 
      try {
          if ( it.value.hasCapability("Switch") ) {
              //if ( ${it.value.getId()}.is(${id}) ) {
              if ( "${it.value.getId()}" == "${id}" ) {
                log.debug "FOUND $it.key "
                keyName = "$it.key"
              }
          }
      }
      catch(Exception e) {
        //log.error "$it.value -->> $e"
      }
  }
  log.debug "getSwitchById: Returning ${keyName}"
  return keyName
}

One thought is to actually use that function, but build a deviceMap during installed() or updated() and store it in the stateMap, then I should only have to do a simply key lookup most of the time…

BINGO Again! Thanx… YOU, Sir, see what I’m trying to do…
But in the end, I don’t want a SET # of relays/switches… I’d like it to be variable… so the user can hit [+] and add another if they so choose to …

I finally got the one thing working… WooHoo! Thanx to you…

Much more elegant… :slight_smile:

    Integer minDelay = 1  // 'Cuz we want every call to behave the same with the MAP data...    
	//if (Relay1) {
    //  runIn(minDelay,turnOnSwitch, [overwrite: false, data: [Zone: Relay1.getId(), runTimeMin: Timer1.toInteger()]] )
    //  minDelay = minDelay+Timer1.toInteger()
    //}
	//if (Relay2) {
    //  runIn(60 * minDelay ,turnOnSwitch, [overwrite: false, data: [Zone: Relay2.getId(), runTimeMin: Timer2.toInteger()]])
    //  minDelay = minDelay+Timer2.toInteger()
    //}    
	//if (Relay3) {
    //  runIn(60 * minDelay ,turnOnSwitch, [overwrite: false, data: [Zone: Relay3.getId(), runTimeMin: Timer3.toInteger()]])
    //  minDelay = minDelay+Timer3.toInteger()
    //}        
	//if (Relay4) {
    //  runIn(60 * minDelay ,turnOnSwitch, [overwrite: false, data: [Zone: Relay4.getId(), runTimeMin: Timer4.toInteger()]])
    //  minDelay = minDelay+Timer4.toInteger()
    //}        
	//if (Relay5) {
    //  runIn(60 * minDelay ,turnOnSwitch, [overwrite: false, data: [Zone: Relay5.getId(), runTimeMin: Timer5.toInteger()]])
    //  minDelay = minDelay+Timer5.toInteger()
    //}            
	//if (Relay6) {
    //  runIn(60 * minDelay ,turnOnSwitch, [overwrite: false, data: [Zone: Relay6.getId(), runTimeMin: Timer6.toInteger()]])
    //  minDelay = minDelay+Timer6.toInteger()
    //} 
    // Much Better/smaller peice of code here.. In the future, we'll figure out what 1..n really is based on preference settings..
    Integer minMultiplier = 1 // If this was 0, the first call to runIn passes the Map different, so we wait 1 second
    for (int i in 1..6) {
	  if (settings."Relay${i}") {
        log.debug "Scheduling " + "Relay${i}" + " in $minDelay minutes"
        runIn(minMultiplier * minDelay,turnOnSwitch, [overwrite: false, data: [Zone: settings."Relay${i}".getId(), runTimeMin: settings."Timer${i}".toInteger()]] )
        minMultiplier = 60 // AFter the 1st execution, we change to 60 minutes
        minDelay = minDelay + settings."Timer${i}".toInteger() // Make sure we convert the whole thing to Minutes!!
      }
    }
1 Like

For simple automations I tend to find CoRE a suitable option to use and you can create as many as you’d like by adding more pistons (upto about 500)

I thought about using WebCoRE originally…
But figured this was a good time to learn to work with SmartAps and Groovy :slight_smile:
And since I might just share this with the rest of the Konnected community when I’m done… it seems better suited to be done here…