Device State Variables Not Updating in UI

@patrick @bravenel is there a limit on the amount/length of data stored in a device state variable? I ask because I am creating an app and driver to monitor a UPS that is connected to my NAS. I am storing all the data received from the UPS in a state variable map so that I can compare new values and only create events if a value changes for things like battery, powerSource, alarms, etc. This is important given I am polling the hub every 15-30 seconds via telnet.

The issue I am finding that the UI isn't reflecting updates to the state variables, even with refreshing the page. I see changes to the state variable values via debug statements in logging but those same values aren't reflected in the UI.



See above where there have been many updates to the state.lastPollTime as reflected by the log statements but the UI still shows a much older time. I really don't know where to begin troubleshooting this. Here is what I have done for troubleshooting:

  • Commented out all the code for state.upsData thinking it may be an issue with that large value.
  • Removed device and added it back and observed no state variables displayed in the UI except for upsName- lastPollTime and batteryDate are empty/null.
    image
  • Removed again just to be sure and same issue
  • Rebooted hub to see if there was some sort of memory issue and still no state data displayed
    • Confirmed via logging that the values are being stored
  • Factory Reset my hub (this is my dev hub) and added the code back and the lastPollTime and batteryDate variables started updating again. Once I add the upsData back the issue with no state variable updates in the UI started happening again.
  • Deleting the installed app and child device doesn't solve the problem.

I am happy to trim back the upsData elements if the answer is that state variables can only only be so big but this is just super odd that I can post the value of the state variable via debug yet its not showing up in the UI. Thank you in advance for any help!

State variables are only updated on page refresh.

Thanks but that doesn't work either. As mentioned I even rebooted hub and the updates still weren't reflected.

Time for some debugging in your code then , I've stored more data than that in state before...

Thank you for confirming there isn't a limit on the size of data. I have rewritten the code to be as efficient as possible. Like many telnet based connections a stream of data comes in and originally I was creating events as the stream was coming in, I am now storing the delta/differences in yet another state variable so the event creation processing doesn't effect this. Once the stream is complete, I then process the events. Between those changes and a few others I have the lastPollTime updating again and the upsData being set.

However something strange is still happening and the UI isn't accurately reflecting changes to the state variables... I have noticed the upsData having incorrect data. And I am refreshing the page too to ensure it is not updating.

Is it possible that the state variable you are trying to update is in fact, not updating and that the problem is your code and not the Hubitat UI? If you don't see the state variable updating on the device page, how do you know that it is updating correctly? Just because it logs that the state changed in a debug message, doesn't actually mean that the state did change. Just that the code logged that it did. Since logging and actually doing a thing are separate lines of code, it's possible that the line changing the state, is in fact, not changing that state.

@bravenel @mike.maxwell there is a bug in HE with the use of leftShift. According to this documentation, I should be able to use leftShift to update a value. This is what I was using in my NAS driver that prompted the OP question.
Since you cannot simulate the updates from my NAS UPS, I created the driver below so you can observe the issue.

Steps to reproduce:

  • Install the driver and create a new virtual switch leveraging this custom "Simulated Dimmer Switch" driver
  • Open up Logs in another tab to observe the debug output
  • Initially I set the state.switchData to {level=100, switch=off} and with an off, on, or setLevel change, I duplicate the level and switch multiple times to demonstrate multiple items within the Map and the fact that all items aren't updated as they should.
  • There is a preference to toggle the use of leftShift instead of a direct update, upon install leftShift is not turned on.
  • With each on, off, or setLevel, the driver will log the value of switchData and after each state change, I press the Print State Variable button to log the value of switchData
  • After installing, I press On and print the variable, Set Level to 50 and print the variable, and press off and print the variable. Without leftShift, all is good and all the values update correctly:

[dev:69] 2019-04-14 03:42:41.365 pm [debug] printStateVariable - state.switchData = [y.switch:off, z.switch:off, x.level:50, level:50, z.level:50, x.switch:off, y.level:50, switch:
off]
[dev:69] 2019-04-14 03:42:39.854 pm [debug] eventHelper (post update) - state.switchData = [y.switch:off, z.switch:off, x.level:50, level:50, z.level:50, x.switch:off, y.level:50, switch:off]
[dev:69] 2019-04-14 03:42:31.483 pm [debug] printStateVariable - state.switchData = [y.switch:on, z.switch:on, x.level:50, level:50, z.level:50, x.switch:on, y.level:50, switch:on]
[dev:69] 2019-04-14 03:42:29.769 pm [debug] eventHelper (post update) - state.switchData = [y.switch:on, z.switch:on, x.level:50, level:50, z.level:50, x.switch:on, y.level:50, switch:on]
[dev:69] 2019-04-14 03:42:17.313 pm [debug] printStateVariable - state.switchData = [y.switch:on, z.switch:on, x.level:99, level:99, z.level:99, x.switch:on, y.level:99, switch:on]
[dev:69] 2019-04-14 03:42:15.673 pm [debug] eventHelper (post update) - state.switchData = [y.switch:on, z.switch:on, x.level:99, level:99, z.level:99, x.switch:on, y.level:99, switch:on]

  • Now toggle the leftShift preference and save preferences. This will reset the switchData back to the install state.
  • Now perform the same actions again, turn on and print the variable, set level and print the variable, and turn off and print out the variable. Notice below the leftShift is duplicating values within the map prior to saving to disk but then when you print it, those duplicates go away but not all the values update correctly.

[dev:69] 2019-04-14 03:52:40.386 pm [debug] printStateVariable - state.switchData = [z.switch:off, y.switch:off, x.level:50, level:50, z.level:50, x.switch:on, y.level:50, switch:off]
[dev:69] 2019-04-14 03:52:38.899 pm [debug] eventHelper (post update) - state.switchData = [z.switch:on, y.switch:on, x.level:50, switch:off, z.switch:off, level:50, y.switch:off, z.level:50, x.switch:off, y.level:50, x.switch:on, switch:off]
[dev:69] 2019-04-14 03:52:36.495 pm [debug] printStateVariable - state.switchData = [z.switch:on, y.switch:on, x.level:50, level:50, z.level:50, y.level:50, x.switch:on, switch:off]
[dev:69] 2019-04-14 03:52:34.713 pm [debug] eventHelper (post update) - state.switchData = [z.switch:on, y.switch:on, level:100, y.level:50, level:50, x.switch:on, z.level:50, switch:off, x.level:50]
[dev:69] 2019-04-14 03:52:19.074 pm [debug] printStateVariable - state.switchData = [z.switch:on, y.switch:on, level:100, x.switch:on, switch:off]
[dev:69] 2019-04-14 03:52:17.678 pm [debug] eventHelper (post update) - state.switchData = [switch:on, z.switch:on, level:100, y.switch:on, x.switch:on, switch:off]

Here is the test driver:

 metadata {
     definition (name: "Simulated Dimmer Switch", namespace: "mlritchie", author: "Michael Ritchie") {
         capability "Actuator"
         capability "Sensor"
 
         capability "Switch"
         capability "Switch Level"
 		
 		command "printStateVariable"
 		command "initialize"
     }
 
     preferences {
 		input("useLeftShift", "bool", title: "Use leftShift?", defaultValue: false)
     }
 }
 
 def installed() {
     initialize()
 }
 
 def updated() {
     initialize()
 }
 
 private initialize() {
     sendEvent(name: "switch", value: "off")
     sendEvent(name: "level", value: 100)
 	
 	state.switchData = ["switch":"off", "level": 100]
 }
 
 def on() {
 	eventHelper("switch", "on", null)
 }
 
 def off() {
 	eventHelper("switch", "off", null)
 }
 
 def setLevel(value) {
 	def intValue = value as Integer
     def newLevel = Math.max(Math.min(intValue, 99), 0)
 	
     if (newLevel == 0) {
         off()
     } else {
         if (device.currentValue("switch") != "on") {
 			on()
 		}
 		eventHelper("level", newLevel, "%")
     }
 }
 
 def setLevel(value, duration) {
     setLevel(value)
 }
 
 def eventHelper(name, value, unit) {
 	sendEvent(name: name, value: value, unit: unit)
 	
 	if (useLeftShift) {
 		def switchValue = ["${name}" : value]
 		state.switchData << switchValue
 		switchValue = ["x.${name}" : value]
 		state.switchData << switchValue
 		switchValue = ["y.${name}" : value]
 		state.switchData << switchValue
 		switchValue = ["z.${name}" : value]
 		state.switchData << switchValue
 	} else {
 		state.switchData["${name}"] = value
 		state.switchData["x.${name}"] = value
 		state.switchData["y.${name}"] = value
 		state.switchData["z.${name}"] = value
 	}
 	
 	log.debug "eventHelper (post update) - state.switchData = ${state.switchData}"
 }
 
 def printStateVariable() {
 	log.debug "printStateVariable - state.switchData = ${state.switchData}"
 }

Sorry, but we're not going to be able to dig into this. We have encountered no issues with the << operator, and use it extensively. I suspect you are not using it correctly.

Thank you for the reply, especially on a Sunday. I ended up switching my code to state.VariableName["${key}"] = value and all is good now.

I am definitely using it correctly as I have used this operator in many of my drivers. I even loaded this exact test driver into SmartThings and it behaves correctly. If you haven't encountered an issue all good, but I am just alerting you there is a problem.

The problem is actually the difference between String and GString. The hashcode of a String and a GString are different, so Groovy considers it 2 different keys even though when you print it out it looks the same. It actually works the same on ST, however I think the order of saving is different so it acts differently in your test case, although I did write a new test case to show the same actions and it does act the same for me. On the plus side while I was working on the comparison I found the bug in drivers that does not allow you to do a state.clear() so that will be fixed in the next release.

Here is the code:

	 def testMap = [switch:"on"]
	 log.debug "testMap: $testMap"
	 def name = "switch"
	 testMap << ["${name}": "off"]
	 log.debug "testMap: $testMap"

	 def attribName = "switch"
	 def stringHashCode = attribName.hashCode()
	 log.debug "switch String hashCode: ${stringHashCode}"
	 def gStringImplHashCode = "${attribName}".hashCode()
	 log.debug "switch GStringImpl hashCode: ${gStringImplHashCode}"
     
     log.debug "state0: ${state}"
     state.myMap = [switch:"on"]
     log.debug "state1: ${state}"
     state.myMap << ["${name}": "off"]
     log.debug "state2: ${state}"

Here is the output in ST:
image

Here is the output in Hubitat:
image

You can see that the hashcodes are different between the 2 types of string and so it inserts what appears to be duplicate values into the Map

The fix in your case would be to make sure you are using Strings as the keys in your map,, so something like this:

	if (useLeftShift) {
 		def switchValue = [("${name}".toString()) : value]
 		state.switchData << switchValue
 		switchValue = [("x.${name}".toString) : value]
 		state.switchData << switchValue
 		switchValue = [("y.${name}".toString()) : value]
 		state.switchData << switchValue
 		switchValue = [("z.${name}".toString()) : value]
 		state.switchData << switchValue
 	}

although I don't know why you are assigning it to a variable before inserting it into the map, maybe its just an artifact of how you cut down your code to show us the problem, but I would think you would just do it this way:

	if (useLeftShift) {
 		state.switchData << [("${name}".toString()) : value]
 		state.switchData << [("x.${name}".toString) : value]
 		state.switchData << [("y.${name}".toString()) : value]
 		state.switchData << [("z.${name}".toString()) : value]
 	}
2 Likes

Thank you very much for the detailed explanation. I really appreciate it. This makes total sense.

1 Like