httpGet JSON result, just one value

Hi, n00b question here.

I'm polling an url with httpget, and get the result:

{"speed-status":3.5,"internal-fault":0,"frost-cycle":0,"filter-dirty":0,"airquality_perc":"not
available","airq-based-on":0,"co2level_ppm":"not available","indoor-humidity_perc":"not
available","outdoor-humidity_perc":"not available","exhausttemp_c":"not available","supply-temp_c":"not
available","indoor-temp_c":"not available","outdoor-temp_c":"not available","speed-cap":63488,"bypass-pos_perc":"not
available","fan-info":"away","exh-fan-speed_perc":3,"in-fan-speed_perc":0,"remaining-time_min":0,"post-heat_perc":"not
available","pre-heat_perc":"not available","in-flow-l_sec":"not available","exh-flow-l_sec":"not
available","ventilation-setpoint_perc":-1,"fan-setpoint_rpm":450,"fan-speed_rpm":587,"error":0,"selection":1,"startup-counter":301,"total-operation_hours":58716,"absence_min":32,"highest-co2-concentration_ppm":"not
available","highest-rh-concentration_perc":"not
available","co2-velocity":2.121995792e-314,"valve":2,"current-period":0,"period-timer":0}

Nice, but of this array, I just want the value of fan-info. ("away" in this case)
How do I select only that value, and bind it to an attribute?

I believe the below will work:

Add the below import to your code (usually near the top).

import groovy.json.JsonSlurper

Assume your pull result is the variable result.

def data = new JsonSlurper().parseText(result)

def fanInfo = data.fan-info

or

def fanInfo = data["fan-info"]

1 Like

Cannot seem to get that working.
Like sayed, I'm a n00b in groovy (programmed in Common Lisp 15 years ago, and that was it)

What i have so far:

metadata {
    definition(name: "Test", namespace: "Fan", author: "Fanman76", importUrl: "") {
        capability "Polling"
    }
}

preferences {

    }

import groovy.json.JsonSlurper

void poll()
{
    String DeviceInfoURI = "http://192.168.1.158/api.html?get=ithostatus" 
    httpGet([uri:DeviceInfoURI, contentType: "application/json"])

	{ resp->
			def data = new JsonSlurper().parseText(result)
        if (enableDebug) { log.debug "Raw value: ${resp.data}" }
        if (resp.status == 200) {
            def fanInfo = data["fan-info"]
        }
   }
}

I was going from the point where the response was what you posted in post 1. I believe that here is resp.data. So resultant would be something like:

{
    String DeviceInfoURI = "http://192.168.1.158/api.html?get=ithostatus" 
    httpGet([uri:DeviceInfoURI, contentType: "application/json"])

	{ resp->
        if (enableDebug) { log.debug "Raw value: ${resp.data}" }
        if (resp.status == 200) {
		def data = new JsonSlurper().parseText(resp.data)
            def fanInfo = data["fan-info"]
        }
   }

That gives json errors:
dev:65452024-01-29 21:56:14.714errorjava.lang.IllegalArgumentException: Text must not be null or empty on line 26 (method poll)

dev:65452024-01-29 21:55:17.026errorjava.lang.IllegalArgumentException: Text must not be null or empty on line 29 (method poll)

dev:65452024-01-29 21:54:37.201errorgroovy.lang.MissingMethodException: No signature of method: groovy.json.JsonSlurper.parseText() is applicable for argument types: (groovyx.net.http.HttpResponseDecorator) values: [groovyx.net.http.HttpResponseDecorator@16957b5]
Possible solutions: parseText(java.lang.String), parse([B), parse([C), parse(java.io.File), parse(java.io.InputStream), parse(java.io.Reader) on line 29 (method poll)

dev:65452024-01-29 21:50:54.953errorjava.lang.NumberFormatException: For input string: "[absence_min:0, airq-based-on:0, airquality_perc:not available, bypass-pos_perc:not available, co2-velocity:2.121995792E-314, co2level_ppm:not available, current-period:0, error:0, exh-fan-speed_perc:2, exh-flow-l_sec:not available, exhausttemp_c:not available, fan-info:low, fan-setpoint_rpm:543, fan-speed_rpm:572, filter-dirty:0, frost-cycle:0, highest-co2-concentration_ppm:not available, highest-rh-concentration_perc:not available, in-fan-speed_perc:0, in-flow-l_sec:not available, indoor-humidity_perc:not available, indoor-temp_c:not available, internal-fault:0, outdoor-humidity_perc:not available, outdoor-temp_c:not available, period-timer:0, post-heat_perc:not available, pre-heat_perc:not available, remaining-time_min:0, selection:2, speed-cap:63488, speed-status:2.5, startup-counter:301, supply-temp_c:not available, total-operation_hours:58718, valve:2, ventilation-setpoint_perc:0]" on line 31 (method poll)

dev:65452024-01-29 21:48:00.753errorgroovy.lang.MissingMethodException: No signature of method: user_driver_Fan_ITO_FANBOX_1316.httpGet() is applicable for argument types: (java.util.LinkedHashMap) values: [[uri:http://192.168.1.158/api.html?get=ithostatus, contentType:application/json]] on line 23 (method poll)

dev:65452024-01-29 21:47:26.051errorjava.lang.IllegalArgumentException: Text must not be null or empty on line 25 (method poll)

dev:65452024-01-29 21:46:25.009errorjava.lang.IllegalArgumentException: Text must not be null or empty on line 27 (method poll)

dev:65452024-01-29 20:58:46.067errorgroovy.lang.MissingMethodException: No signature of method: groovy.json.JsonSlurper.parseText() is applicable for argument types: (groovy.json.internal.LazyMap) values: [[absence_min:46, airq-based-on:0, airquality_perc:not available, ...]]
Possible solutions: parseText(java.lang.String), parse([B), parse([C), parse(java.io.File), parse(java.io.InputStream), parse(java.io.Reader) on line 27 (method poll)

void poll()
{
    String DeviceInfoURI = "http://192.168.1.158/api.html?get=ithostatus" 
    httpGet([uri:DeviceInfoURI, contentType: "application/json"])

	{ resp->
        if (enableDebug) { log.debug "Raw value: ${resp.data}" }
        if (resp.status == 200) {
		def data = new JsonSlurper().parseText(resp.data)
            def fanInfo = data["fan-info"]
        }
   }
}

This means that resp.data does not exist. Provide the full resp so I can see what is going on.

Code below (modified from your earlier post). Note data is changed to body (my memory error).

{
    String DeviceInfoURI = "http://192.168.1.158/api.html?get=ithostatus" 
    httpGet([uri:DeviceInfoURI, contentType: "application/json"])

	{ resp->
        if (enableDebug) { log.debug "Properties: ${resp.properties}" }
        if (enableDebug) { log.debug "Raw value: ${resp.body}" }
   }

Code example. I used an old driver (I no longer have the device installed but some other users are still using) and created a short program that uses sendHubCommand (HubAction) vice syncGet. The device returned a Json string (as in Post1) and I parsed out the data to update attributes (as you are trying).

This was to get us started.

The code I used to get this can be found at: https://raw.githubusercontent.com/DaveGut/HubitatActive/master/bleBoxDevices/Drivers/tempSensor.groovy

I have a lot of wifi (LAN) developments all on GitHub. top link is:
'DaveGut (David Gutheinz) ยท GitHub' The later the development, the cleaner the code (I started as a total noobee as you).

import groovy.json.JsonSlurper

def poll() {
	log.debug "refresh."
	sendGetCmd("/api/tempsensor/state", "commandParse")
}

private sendGetCmd(command, action){
	log.debug "sendGetCmd: ${command} / ${action} / ${getDataValue("deviceIP")}"
	//	in this case, the deviceIP is that for your device ("192.168.1.158")
	def deviceIp = "192.168.1.158"
	sendHubCommand(new hubitat.device.HubAction("GET ${command} HTTP/1.1\r\nHost: ${deviceIP}\r\n\r\n",
				   hubitat.device.Protocol.LAN, null,[callback: action]))
}

def commandParse(response) {
	try {
		if(response.body == null) { 
			logWarn("parseInput: body is null")
			return 
		}
		def jsonSlurper = new groovy.json.JsonSlurper()
		def respData = jsonSlurper.parseText(response.body)
		log.debug respData
		log.debug respData["fan-info"]
		//	 or respData.fan-info may work
	} catch (error) {
		logWarn("Error attempting to parse: ${error}.")
	}
}


@ djgutheinz

[dev:6545](https://remoteaccess.aws.hubitat.com/logs#)2024-01-30 15:35:45.723[error](https://remoteaccess.aws.hubitat.com/logs#)groovy.lang.MissingPropertyException: No such property: body for class: groovyx.net.http.HttpResponseDecorator on line 28 (method poll)

[dev:6545](https://remoteaccess.aws.hubitat.com/logs#)2024-01-30 15:35:45.680[debug](https://remoteaccess.aws.hubitat.com/logs#)Properties: [headers:groovyx.net.http.HttpResponseDecorator$HeadersDecorator@f9ba2f, class:class groovyx.net.http.HttpResponseDecorator, entity:ResponseEntityProxy{[Content-Type: text/html,Content-Length: 969,Chunked: false]}, status:200, contentType:text/html, locale:en_US, allHeaders:[Content-Length: 969, Content-Type: text/html, Connection: close, Accept-Ranges: none], params:[parameters={}], data:[absence_min:0, airq-based-on:0, airquality_perc:not available, bypass-pos_perc:not available, co2-velocity:2.121995792E-314, co2level_ppm:not available, current-period:0, error:0, exh-fan-speed_perc:3, exh-flow-l_sec:not available, exhausttemp_c:not available, fan-info:low, fan-setpoint_rpm:543, fan-speed_rpm:582, filter-dirty:0, frost-cycle:0, highest-co2-concentration_ppm:not available, highest-rh-concentration_perc:not available, in-fan-speed_perc:0, in-flow-l_sec:not available, indoor-humidity_perc:not available, indoor-temp_c:not available, internal-fault:0, outdoor-humidity_perc:not available, outdoor-temp_c:not available, period-timer:0, post-heat_perc:not available, pre-heat_perc:not available, remaining-time_min:0, selection:2, speed-cap:63488, speed-status:3.5, startup-counter:301, supply-temp_c:not available, total-operation_hours:58735, valve:2, ventilation-setpoint_perc:0], context:groovyx.net.http.HttpContextDecorator@1a7149a, success:true, statusLine:HTTP/1.1 200 OK, protocolVersion:HTTP/1.1]

NB, your second peace of code gives null back.

This seems to partialy work:

def poll() {
    String DeviceInfoURI = "http://192.168.1.158/api.html?get=ithostatus" 
    httpGet([uri:DeviceInfoURI, contentType: "application/json"])

	{ resp->
        if (enableDebug) { log.debug "Properties: ${resp.properties}" }
        if (enableDebug) { log.debug "values: ${resp.data}" }
        if (enableDebug) { log.debug "values: ${resp.data.absence_min}" }
   }
}

In this example I get the value of resp.data.absence_min
When however I try this for resp.data.fan-info, I'm getting a java error java.lang.NullPointerException: Cannot invoke method minus() on null object on line 29 (method poll)

So how to set the minus sign as a string, instead of a operator?

That is when you use the format resp.data["fan-info"]. (so the easy method will not work.

I forgot about the minus sign messing up the json parse.

First, unless you have a very good reason for using httpGet (which is synchronous and wastes CPU time while it's waiting on packets to travel across the internet), you should really be using asyncHttpGet.

import hubitat.scheduling.AsyncResponse
void poll() {
  Map params = [uri: "http://192.168.1.158/api.html?get=ithostatus"]
  params.contentType = 'application/json'
  params.requestContentType = 'application/json'
  asynchttpGet('pollResponse', params)
}

void pollResponse(AsyncResponse response, Map data = null) {
  if (response?.status == 200) {
    Map json = response.getJson()
    String fanInfo
    if(json.containsKey('fan-info')) {
      fanInfo = json['fan-info']
    }
    // Do something with fanInfo here
  }
}

That should work for what you're wanting here.

1 Like

This will not work: groovy.lang.MissingPropertyException: No such property: contentType for class: java.lang.String on line 24 (method poll)

I have no "very good reason" for httpget as you state it, but on the other hand I'm not pulling data of the internet. The target is on my local LAN.

See above edit. Should be Map not String.

Even so, there's no reason not to use async. That device could still take a significant amount to time to respond, during which you've tied up one of the 4 threads on Hubitat needlessly. Pretty much the only reason to use sync is if you absolutely need to have the code wait for the reply. The only time I've ran into 'needing' to use sync was during things like doing a request as part of building a dynamicPage on an app. And even that can probably now be done with async since they added the ability to dynamically change app page text a couple updates ago.

If I change the String params = to Map params = , I'm also getting an error:

java.lang.NullPointerException: Cannot get property 'data' on null object on line 27 (method pollResponse)

Just remove that line, or change it to

if (enableDebug) { log.debug "Raw value: ${json}" }

1 Like

That seems to work better:
image

1 Like

Depending on how much control you have over whatever this device is that you're polling, you could instead have it POST the JSON to port 39501 on your Hubitat. Then in this driver instead of doing a poll, you write a parse(String message) method and then your driver only uses up CPU time on Hubitat when there's a new event from the device, rather than polling it potentially thousands of times a day when nothing has changed.

I wish it was that easy.. I have no control apart from an hardcoded RF device, and an a http interface to fire some direct commands to the interface.
It can fire events, but only to a MQTT server.

Ok, still struggling with the whole httpget thing.
Next problem on my list, I want to give a command to an api. When return value is 'OK' then the command is accepted by the device.

void SetSpeed( String mode ) {
    def setspeeduri = 'http://${ipAddress}/api.html?username=${username}&password=${password}&vremotecmd='
    switch (mode) {
        case 'low' :
        httpGet (uri: "${setspeeduri}low")
           { resp ->
               if (response?.status == 200) {
               log.debug "SetSpeed to low"
               }
           }
            break
    }
}

I know i'm not doing anything with the 'OK' part, just with the http responce code.
yet above code gives me: java.net.URISyntaxException: Illegal character in authority at index 7: http://${ipAddress}/api.html?username=${username}&password=${password}&vremotecmd=low on line 89 (method SetSpeed)

In what way am i goofing up now?