I'm in groovy math / type quicksand. Can't get simple math eqn to not throw an error

I've been working on a simple app (still) to calculate DEW point from temperature and humidity.

I have most of it working but the math functions are giving me a hard time. I received a number of java method exceptions but can't figure how to resolve them.

I had tried this code for the calculation line with no success:

def dewPointL = Double.parseDouble(state.lastTEMP) - (9 / 25) * (100-Double.parseDouble(state.lastHUMID))

Any suggestions or where to look would be appreciated. I read through the groovy language on the Apache site and have a "groovy in action" book but neither gives me a hint (at least that I can find)

Latest Code:

def initialize() {

  	def averageDev = getChildDevice("DEWPoint_${app.id}")
	if(!averageDev) averageDev = addChildDevice("hubitat", "Virtual Temperature Sensor", "DEWPoint_${app.id}", null, [label: thisName,     name: thisName])
	averageDev.setTemperature(0)
	subscribe(tempSensor, "temperature", handlerTEMP)
	subscribe(humidSensor, "humidity", handlerHUMID)

	state.lastHUMID = 50		// these are in the app and will not display in the child
	state.lastTEMP = 50		//  50/50 DEWPoint = 32
}

def calcDEW() {
	def averageDev = getChildDevice("DEWPoint_${app.id}")
    log.debug "  56 state.lastTEMP ${state.lastTEMP}"
    log.debug "  57 state.lastHUMID ${state.lastHUMID}"
	def dewPointL = ((state.lastTEMP) - (9 / 25) * (100-state.lastHUMID)).toDouble().round(1)
	averageDev.setTemperature(dewPoint)
	//return
}

def handlerHUMID(evt) {
	state.lastHUMID = evt.value
    log.debug " 65 last Humidity = ${evt.value}"
   calcDEW()
}

def handlerTEMP(evt) {
	state.lastTEMP = evt.value
    log.debug " 71 last Temperature = ${evt.value}"
   calcDEW()
}
//  --- eof ---

Latest Errors: (the last error is for the code above:

I would assume you need to cast the state variable(s) from a string to integer or double...

I'm sure your are right but I thought I tried that with:

def dewPointL = Double.parseDouble(state.lastTEMP) - (9 / 25) * (100-Double.parseDouble(state.lastHUMID))

I'll go back and look at type casting again...thanks

Well, I could be wrong... :slight_smile:

I can't tell what line is actually throwing the error.

Other errors have shown the line to be the one I singled out above

def dewPointL = Double.parseDouble(state.lastTEMP) - (9 / 25) * (100-Double.parseDouble(state.lastHUMID))

At least that is what I'm focusing on.

John

gotcha.

So this line:

def dewPointL = Double.parseDouble(state.lastTEMP) - (9 / 25) * (100-Double.parseDouble(state.lastHUMID))

is the one that throws that error? If so that isn't obvious to me as why - as there is clearly a string usage error in the debug line.

1 Like

Are any of your humidity sensor devices returning strings instead of numeric types as the humidity value? (Or temperature or whatever value you're trying to use on the line where this error occurs.) This is hard to tell--I'm not sure anywhere that would tell the user what the data type is--and probably won't happen with any built-in drivers but might be something to look out for in a community driver.

Something else to note is that [EDIT: see posts from Bruce below with better explanations about data types in state; they are objects, but not necessarily Strings, though apparently event values are Strings regardless of what the event sends]

Also, if it's of any help, I'm pretty sure something like this would at least help figure out the data type your code things any specific object is:

log.debug "x is String? = ${x instanceof String}"

I think getClass() (which would otherwise just tell you what it is) is blocked by Hubitat's security model, so good guesses with instanceof are the only thing I can think of that we have left. :slight_smile:

Another easy way to find out is to intentionally throw an error. Pass the object to a method that isn't defined. The logged error message will tell you the type of the object passed.

Quick and dirty, but it works.

3 Likes

state will always return a string. So if you know what is there, you can just use state.x.toDouble() or state.x.toInteger() upon pulling it from state.

Thanks for the insight. Question is not:

state.lastTEMP.toDouble()

the same as

Double.parseDouble(state.lastTEMP)

John

For what you're doing, either will work. I'm just used to .toDouble() -- fewer characters than Double.parseDouble(), and you can avoid some errors with the safe navigation operator:

myNumber = state.x?.toDouble()

state.x.toDouble or Double.parseDouble(state.x) will both throw an error is state.x is null. Whereas state.x?.toDouble() will return a null without an error.

2 Likes

As it turns out there is something in the groovy engine that doesn't like my conversion from string to double performed withing the calculating line.

This code works:

def calcDEW() {
	def averageDev = getChildDevice("DEWPoint_${app.id}")
    log.debug "  56 state.lastTEMP ${state.lastTEMP}"
    log.debug "  57 state.lastHUMID ${state.lastHUMID}"

    operandHUMID = state.lastHUMID.toDouble() //  <<------ conversion performed here
    operandTEMP = state.lastTEMP.toDouble()

    def dewPointI = (operandTEMP - (9 / 25) * (100 - operandHUMID))    // <<---------------------------------------works 
    log.debug "   62  dewPointI =     ${dewPointI}"
	averageDev.setTemperature(dewPointI)
	//return
}

def handlerHUMID(evt) {
	state.lastHUMID = evt.value
    log.debug " 65 last Humidity = ${evt.value}"
   calcDEW()
}

def handlerTEMP(evt) {
	state.lastTEMP = evt.value
    log.debug " 71 last Temperature = ${evt.value}"
   calcDEW()
}

This code gives an error (see logs in above post)

def calcDEW() {
    def averageDev = getChildDevice("DEWPoint_${app.id}")
    log.debug "  56 state.lastTEMP ${state.lastTEMP}"
    log.debug "  57 state.lastHUMID ${state.lastHUMID}"

    def dewPointI = state.lastHUMID.toDouble() - (9 / 25) * (100 - state.lastHUMID.toDouble())   // <<---------------------------------------------here causes error
    log.debug "   62  dewPointI =     ${dewPointI}"
    averageDev.setTemperature(dewPointI)
    //return
}

def handlerHUMID(evt) {
    state.lastHUMID = evt.value
    log.debug " 65 last Humidity = ${evt.value}"
   calcDEW()
}

def handlerTEMP(evt) {
    state.lastTEMP = evt.value
    log.debug " 71 last Temperature = ${evt.value}"
   calcDEW()
}

I'm not sure that could be the problem. I just created a minimal app and used these lines (cheating a bit to set the state as it may have been for you), and it worked:

state.lastHUMID = 57
log.info "${state.lastHUMID.toDouble() - (9 / 25) * (100 - state.lastHUMID.toDouble())}"

What is the error you're getting? Does "above" mean the first post? I'm not sure how the above could possibly get a string, but you'd definitely get problems if state.lastHUMID is null or something that isn't actually able to be converted (is this a custom driver? did the author inadvertently include the units in the value?).

Finally, it's worth noting that this:

state.lastTEMP = 56
fakeMethod(state.lastTEMP)  

...logs the following for me:


(EDIT: Fixed screenshot to show that argument was Integer...accidentallly copied the wrong thing before)

...which suggests that it isn't written to state as a String (nor was the result different if I removed the first line and just read the value on a second execution, eliminating any effect that the app writing out state upon finishing execution might have) but rather an Integer like ST would, so perhaps I'm misinterpreting something Bruce said (otherwise, this behavior looks a lot like the behavior ST documents where int is specifically called out as a supported type for state, which Groovy automatically boxes to Integer, resulting in the above).

It all depends on how the value was set, if "state.lastTEMP = 56.1" it would be an instance of BigDecimal.

You could always check what the type is, one way is with instanceof:

state.stringNumber = "60.2"
state.lastHUMID = 60.2
state.lastTEMP = 25.1
log.debug "state.stringNumber String: ${state.stringNumber instanceof String}"  // True
log.debug "state.lastHUMID Integer: ${state.lastHUMID instanceof Integer}"  // False
log.debug "state.lastHUMID BigDecimal: ${state.lastHUMID instanceof BigDecimal}"  // True
log.debug "state.lastHUMID String: ${state.lastHUMID instanceof String}"  // False
log.debug "state.lastTEMP Integer: ${state.lastTEMP instanceof Integer}"  // False
log.debug "state.lastTEMP BigDecimal: ${state.lastTEMP instanceof BigDecimal}"  // True
log.debug "state.lastTEMP String: ${state.lastTEMP instanceof String}"  // False
def operandHUMID = state.lastHUMID.toDouble() //  <<------ conversion performed here
def operandTEMP = state.lastTEMP.toDouble()

def dewPointI = (operandTEMP - (9 / 25) * (100 - operandHUMID))    // works 
log.debug "   62  dewPointI1 =     ${dewPointI}"

def dewPointI2 = state.lastTEMP.toDouble() - (9 / 25) * (100 - state.lastHUMID.toDouble())   // works
log.debug "   62  dewPointI2 =     ${dewPointI2}"

def dewPointI3 = state.lastTEMP - (9 / 25) * (100 - state.lastHUMID)   // works
log.debug "   62  dewPointI3 =     ${dewPointI3}"

For these calculations you probably want BigDecimal anyway, it will leave you without the floating point imprecision of Double.

Thanks to both. Even though it is currently working I will have to look into this issue some more.

bertabcd1234:

Does "above" mean the first post?
Yes but during previous iterations of the calculations, I had other errors all " no signature of method:

To preclude having a null in the state variables, I assigned them a value of 50.

As for the possibility of units in the state data, I would have thought the line:

 log.debug "  57 state.lastHUMID ${state.lastHUMID}"`

would have shown them. BTW the 57 is the line number of this statement (it helps me when I look at the logs).

markus
Thank you for the "what the heck is the variable" code. I will definitely use it if for no other reason than to understand.

It all depends on how the value was set, if "state.lastTEMP = 56.1" it would be an instance of BigDecimal.

The Smartthings docs tell us all state variables are stored as a string and must be JSON friendly. But maybe things have changed.

But you've both given me new information / capability to look and learn.

Thanks
John

You can also check "state.stringNumber.class.name" most other routes to the class name are restricted in the sandbox.

Yes, but that is not what I have seen on HE. Or it may actually be what we see, dumped to and from JSON we would get datatypes like BigDecimal. For a simple test, set a state variable to a number in one command method, access that state variable in another command method and check the class.

Glad to help in learning more! :slight_smile:

1 Like

I had it wrong before what I said about state.

State is turned into a json object when written. So, 14 becomes Integer, 1.4 becomes BigDecimal.

Values in events get turned into String. So when an Integer, BigDecimal or Double value is sent in an event, you're going to get a String that needs to be converted before you can do math on it.

Obviously, if you put the event value into state, as a String, its going to come back as a String.

1 Like