How do I parse battery percentage from a HTTP GET response?

The log.info just gives the memory address now
data: java.io.StringReader@1f33518

Which one was working to give the full response in the log? I thought it was log.info "data: ${response.getData()}", no?

I'm gonna check out tonight. If you want to keep on going tomorrow I'll check back here for progress.

1 Like

Will do, and thank you again!!!

If it helps:
Source of arduino's url as seen from chrome:

<!DOCTYPE HTML>
<html>
Blind ID: 1<br>Group: Null<br>Level: 0<br>Battery: 97<br>Voltage: 4.19<br>Charger: N<br>Success.<br></html>

Arduino snippit that generates it:

//Prepare response
String s = "HTTP/1.1 200 OK\r\n";
	s += "Content-Type: text/html\r\n\r\n";
	s += "<!DOCTYPE HTML>\r\n<html>\r\n";
	s += "Blind ID: ";
	s += blindID;
	s += "<br>";
	s += "Group: Null<br>";
	s += "Level: ";
    s += level;
    s += "<br>";
	s += "Battery: ";
	s += myPacket->percent;
	s += "<br>Voltage: ";            
	s += voltage;
	s += "<br>Charger: ";
	s += myPacket->charger;
	s += "<br>Success.<br>";

Okay, change out "\r" for "<br>" and that should do it.

No go, screenshot below... also I don't know if it's of any consequence but I screenshot'ed to show that there's a blank line at the first of the data...

And here's a full copy of this driver now , just to confirm:

metadata {
  definition(
    name: "Test",
    namespace: "Test",
    author: "Test")
    {
      capability "Refresh"
    }
}

def refresh() { (sendSyncCmd()) }

private sendSyncCmd() {
  def host = "http://192.168.1.157"
  def command = "/Blind/ID=2/level=0"

  def responseMap = [:]
  httpGet([uri: "${host}${command}",
    contentType: "text/plain",
    textParser: true])
    { response ->
      log.info "data: ${response.getData()}"  //shows the full response in the log

      //if response.data didn't print the entire response then change this to getData() or something that did.
      //
      data.split("<br>").each {
        if (it.contains(":")) {
          def parts = it.split(":")
          responseMap[parts[0].trim()] = parts[1].trim()
        }
      }
    }

  //assuming everything went right up to this point you should be able to do...
  //if it didn't go correctly we probably know that the new line character is something other than "\r" and we'll try "\n" or "\r\n"
  log.debug "value of Battery is ${responseMap.Battery}"
  log.debug "value of Voltage is ${responseMap.Voltage}"
  
}

Is it possible to clean it up with splits? Something like

split( '<html>' )
split( '<br>' )
value1=split( ': ' )
value2=split( ': ' )
value3=split( ': ' )
etc...

Or am I on the wrong path?

Too manual for my tastes. Let's output some debugging to see what's in our variables.

metadata {
  definition(
    name: "Test",
    namespace: "Test",
    author: "Test")
    {
      capability "Refresh"
    }
}

def refresh() { (sendSyncCmd()) }

private sendSyncCmd() {
  def host = "http://192.168.1.157"
  def command = "/Blind/ID=2/level=0"

  def responseMap = [:]
  httpGet([uri: "${host}${command}",
    contentType: "text/plain",
    textParser: true])
    { response ->
      log.info "data: ${response.getData()}"  //shows the full response in the log

      //if response.data didn't print the entire response then change this to getData() or something that did.

      log.debug "if we split on <br> there are ${data.split("<br>").length} elements"
      log.debug "the first is ${data.split("<br>")[0]}"

      data.split("<br>").each {
        //tmp output
        log.debug "working on \"${it}\""
        if (it.contains(":")) {
          def parts = it.split(":")
          responseMap[parts[0].trim()] = parts[1].trim()
        }
      }
    }

  //assuming everything went right up to this point you should be able to do...
  //if it didn't go correctly we probably know that the new line character is something other than "\r" and we'll try "\n" or "\r\n"
  log.debug "value of Battery is ${responseMap.Battery}"
  log.debug "value of Voltage is ${responseMap.Voltage}"

}

*edit: Oh, maybe I see it. At some point you removed def data = response.getData().toString()

Try this:

metadata {
  definition(
    name: "Test",
    namespace: "Test",
    author: "Test")
    {
      capability "Refresh"
    }
}

def refresh() { (sendSyncCmd()) }

private sendSyncCmd() {
  def host = "http://192.168.1.157"
  def command = "/Blind/ID=2/level=0"

  def responseMap = [:]
  httpGet([uri: "${host}${command}",
    contentType: "text/plain",
    textParser: true])
    { response ->
      log.info "data: ${response.getData()}"  //shows the full response in the log

      def data = response.getData().toString()
      log.info "data: ${data}"  //should still show full response

      log.debug "if we split on <br> there are ${data.split("<br>").length} elements"
      log.debug "the first is ${data.split("<br>")[0]}"

      data.split("<br>").each {
        //tmp output
        log.debug "working on \"${it}\""
        if (it.contains(":")) {
          def parts = it.split(":")
          responseMap[parts[0].trim()] = parts[1].trim()
        }
      }
    }

  //assuming everything went right up to this point you should be able to do...
  //if it didn't go correctly we probably know that the new line character is something other than "\r" and we'll try "\n" or "\r\n"
  log.debug "value of Battery is ${responseMap.Battery}"
  log.debug "value of Voltage is ${responseMap.Voltage}"

}

You were right about the def data missing, sorry I was afraid I missed something with all the edits, but here's where it is now:

I'll be in and out all day so responses will be scattered.

So, that's interesting. Apparently GStringImpl doesn't convert objects to String always with the toString method. It must be looking at the StringReader and converting it without toString().

Try this:

metadata {
  definition(
    name: "Test",
    namespace: "Test",
    author: "Test")
    {
      capability "Refresh"
    }
}

def refresh() { (sendSyncCmd()) }

private sendSyncCmd() {
  def host = "http://192.168.1.157"
  def command = "/Blind/ID=2/level=0"

  def responseMap = [:]
  httpGet([uri: "${host}${command}",
    contentType: "text/plain",
    textParser: true])
    { response ->
      log.info "data: ${response.getData()}"  //shows the full response in the log

      def data = "${response.getData()}"
      log.info "data: ${data}"  //should still show full response

      log.debug "if we split on <br> there are ${data.split("<br>").length} elements"
      log.debug "the first is ${data.split("<br>")[0]}"

      data.split("<br>").each {
        //tmp output
        log.debug "working on \"${it}\""
        if (it.contains(":")) {
          def parts = it.split(":")
          responseMap[parts[0].trim()] = parts[1].trim()
        }
      }
    }

  //assuming everything went right up to this point you should be able to do...
  //if it didn't go correctly we probably know that the new line character is something other than "\r" and we'll try "\n" or "\r\n"
  log.debug "value of Battery is ${responseMap.Battery}"
  log.debug "value of Voltage is ${responseMap.Voltage}"

}

Log says:
groovy.lang.StringWriterIOException: java.io.IOException: Stream closed on line 25 (refresh)

I'm going to PM you from here on out. We'll join back here once we have working code. Debugging can be messy when you're doing it on Hubitat through another person.

By the way, here was the result of the PMs. I'm putting it here in case it has any historical significance or helps anybody down the road.

A few key notes:

  • java.io.StringReader is a stream from the actual server response. You can read from it once. Second time is exception. I should have known that as it is in java.io afterall.
  • By passing different content types to the httpGet you are changing how the response is parsed. If you pass no content type or plain text the HTML can be stripped from the response. In this case, we didn't want it because we needed the
    line breaks to split on.
  • GStringImpl doesn't always call toString apparently on objects inside escape brackets. It knew to use the StringReader appropriately. However, it didn't know to store the result for reuse. The first time "data: ${response.getData()}" was logged it work. The second time it failed. The final solution was to force the GStringImpl to a String and then work with that.

So, it turned out to be a little trickier than anticipated because of the format of the response.

metadata {
  definition(
    name: "Test",
    namespace: "Test",
    author: "Test")
    {
      capability "Refresh"
      capability "Battery"

      attribute "voltage", "number"
    }
}

def refresh() { (sendSyncCmd()) }

private sendSyncCmd() {
  def host = "http://192.168.1.157"
  def command = "/Blind/ID=2/level=0"

  def responseMap = [:]
  httpGet([
    uri: "${host}${command}",
    contentType: "text/plain"
  ])
    { response ->
      def data = "${response.getData()}".toString()

      data.split("<br>").each {
        if (it.contains(":")) {
          def parts = it.split(":")
          responseMap[parts[0].trim()] = parts[1].trim()
        }
      }
    }

  //assuming everything went right up to this point you should be able to do...
  //if it didn't go correctly we probably know that the new line character is something other than "\r" and we'll try "\n" or "\r\n"
  log.debug "value of Battery is ${responseMap.Battery}"
  log.debug "value of Voltage is ${responseMap.Voltage}"

  def battery = responseMap.Battery?.toInteger() ?: 0
  battery = battery > 100 ? 100 : battery
  battery = battery < 0 ? 0 : battery
  sendEvent([name: "battery", value: battery])
  sendEvent([name: "voltage", value: new Double(responseMap.Voltage.trim() ?: "0")])

}
2 Likes

Stumbled upon this one while searching for a similar “fix”

I’m wondering if I can get some help parsing:

data: {
"Blinds1": [
{
"battery": 64,
"light": 0.0,
"macaddr": "xx:xx:xx:xx:xx:xx",
"position": 100
}
],
"status": "OK"
}

The output is JSON with mine and i have 0 clue on how to parse it into capturing the values…

Once you out it in a variable it should be data.Blinds1.battery

Ie

{
httpGet(params) {
response ->
logDebug "Request was successful, $response.status"
//logInfo "data = $response.data"
logDebug "ld = $response.data.latestData"

        def curTemp = response.data.latestData.uiData.DispTemperature
        def fanMode = response.data.latestData.fanData.fanMode
        def switchPos = response.data.latestData.uiData.SystemSwitchPosition
        def coolSetPoint = response.data.latestData.uiData.CoolSetpoint
        def heatSetPoint = response.data

Yeh… you lost me.

I was piggy backing on the code above :blush: trying to adapt it but that didn’t work as it was/is splitting (obviously at <br>) and the output of the split logic does not match my data.

try making sure you data type is json.. if that doesnt work you will have to use split on a certain charcter and use debugging to figure out whice elements of the array contain the data you nmeed

def params = [
uri: "https://mytotalconnectcomfort.com/portal/Device/CheckDataSession/${settings.honeywelldevice}",
headers: [
'Accept': '/',
'DNT': '1',
'Cache': 'false',
'dataType': 'json',
'Accept-Encoding': 'plain',
'Cache-Control': 'max-age=0',
'Accept-Language': 'en-US,en,q=0.8',
'Connection': 'keep-alive',
'Referer': 'Honeywell Home - My Total Connect Comfort',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
'Cookie': device.data.cookiess
],
using split i would split on the colon and then agaion maybe on blank to isolate you stuff

the first issue if using split is get rid of the double quotes as it is ffing up the result.. see example here

metadata {
definition (name: "split test", namespace: "lgkapps", author: "larry kahn kahn@lgk.com") {

capability "Actuator"
capability "Configuration"

}

}

def installed() {
initialize()
}

def updated() {
initialize()
}

def configure()
{
def String input = 'data: {' +
'"Blinds1": [' +
'{' +
'"battery": 64,' +
'"light": 0.0,' +
'macaddr": "1:1:2:1",' +
'"position": 100' +
'}' +
'],' +
'"status": "OK"' +
'}'

def input2 = input.replace("\"", "");

log.debug "stripped string = $input2"

def split1 = input2.split("battery:")

def p1 = split1[0]
def p2 = split1[1]
//def p3 = split1[2]

log.debug "1= $p1"
log.debug "2 = $p2"
//log.debug "3 = $p3"

def split2 = p2.split(",")

def p1a = split2[0]
def p2a = split2[1]

log.debug "1a = $p1a"
log.debug "2a = $p2a"

def Integer batteryval = p1a.toInteger()

log.debug "battery val = $batteryval"

}

1 Like

if you are getting text / a string you can do:

    def data = """
        {
        "Blinds1": [
            {
            "battery": 64,
            "light": 0.0,
            "macaddr": "xx:xx:xx:xx:xx:xx",
            "position": 100
            }
        ],
        "status": "OK"
        }"""

    def json_data = new groovy.json.JsonSlurper().parseText(data)
    assert json_data["Blinds1"][0]["battery"] == 64
}

in case you are performing a http request, make sure you set
contentType: "application/json". Then resp.data already contains the "json object" with data.

(Parsing text screams regular expressions to me)

1 Like

My hub is pretty busy. I gave up on hub level automation and used bash pushing to maker API.

1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.