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

`

errorgroovy.lang.MissingMethodException: No signature of method: java.io.StringReader.split() is applicable for argument types: (java.lang.String) values: Possible solutions: split(groovy.lang.Closure), wait(), wait(long), skip(long), skip(long), getAt(java.lang.String) on line 26 (refresh)

`

Yes, and shows correct in the log on the log.info calls

1 Like
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 ->

      def data = response.getData().toString()
      log.info "data: ${data}"  //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("\r").each {
        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}"

}

What's this do?

1 Like

`

java.lang.ArrayIndexOutOfBoundsException: 1 on line 30 (refresh)

`

That means that there are lines in the response that don't have a colon.

Keep the rest but modify this part to:

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

Oh, oops. It's the last line. It is "Success." That won't split. I modified the above.

1 Like

Oh Man, that's the first time i haven't seen an error all night lol

dev:842020-03-01 03:47:56.598 am debugvalue of Voltage is null

dev:842020-03-01 03:47:56.594 am debugvalue of Battery is null

dev:842020-03-01 03:47:56.590 am infodata: java.io.StringReader@1f33518

1 Like

Is the full response still printing from that log.info? If so, do you have a link to the project that you used for the Arduino or the snippet of code that built the response? We need to know what the new lines are. And the problem is you can't really copy and paste them because they are probably changed in the log. I'm too lazy to show you how to print out the int character value. We'll do that if you don't have the code that generated that response. This one.

Those new lines could be any number of things.

1 Like

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…