asynchttpGet get byte data

@mlciskey Did you ever figure out the answer to this?

@chuck.schwer I'm examining the response after I call out with an async GET. I can see headers, status (which is 200), warning messages (which are empty) and the data is null. Are you not passing content type image/jpg through in the response? I would like to pull in a thumbnail for a camera. After I get the bytes of the image back I plan to base64 encode it and store it in state. For example:

state.thumbnail = "<img src="data:image/png;base64,${response.data.encodeBase64().toString()}" alt="Snapshot" />"

However, I'm not seeing the image data in the response. Also, I don't even know if the above will work. Is there a better way to do this?

No, I was never able to use the async function

You don’t necessarily need to do the fetch using async calls. You could implement a background image refresh using the scheduler and synchronous calls. I’m assuming that the scheduler tasks execute on a different thread and shouldn’t block anything other than possibly other scheduled tasks.

Do you have example code? The async and sync are supposed to work the same but I suppose there could be something missing.

In the syncronous call response.data returns a ByteArrayInputStream. In the Async call the call back function gets has a response object with a status of 200 but response.data and the call back function's data parameter are both null.

def imageTest() {
		httpGet(
			uri: "my url",
			headers: 
			[
				"Accept": "image/jpeg"
			]
		) {response ->
    log.trace "response - ${response.properties.collect{it}.join(',')}"
			def is = response.data
            log.debug "is available = ${is.available()}"
		    ByteArrayOutputStream imageBuffer = new ByteArrayOutputStream();
			int nRead;
			byte[] readData = new byte[1024];
			while ((nRead = is.read(readData, 0, readData.length)) != -1) {
				imageBuffer.write(readData, 0, nRead)
			}

			imageBuffer.flush();
			imageData = imageBuffer.toByteArray()
            log.debug "size = ${imageBuffer.size()}"
		}
    
    	def params = [
		uri: "my url",
		headers: 
			[
				"Accept": "image/jpeg"
			]

	]
	asynchttpGet(sendHttpResponse, params)
}

def sendHttpResponse(response, data)
{
    log.debug "sendHttpResponse"
    try {
        log.debug "data available = ${data.available()}"
    }
    catch (e)
    {
        log.error "unable to find data size"
    }
    def is = response.data
    try {
        log.debug "is available = ${is.available()}"
    }
    catch (e)
    {
        log.error "unable to find 'is' size"
    }
    ByteArrayOutputStream imageBuffer = new ByteArrayOutputStream();
    int nRead;
    byte[] readData = new byte[1024];
    while ((nRead = is.read(readData, 0, readData.length)) != -1) {
        imageBuffer.write(readData, 0, nRead);
    }

    imageBuffer.flush();
    imageData = imageBuffer.toByteArray();
    log.debug "size = ${imageBuffer.size()}"
}

Thanks for the example. We do not parse the response if it is a ByteArrayInputStream. I'm open to suggestions on how to handle this. The response.data returns a String to be backwards compatible. I'm open to encoding the incoming bytes as Base64 or a Hex String and putting that result into data, any preference for type of encoding?

Well, maybe you can tell me what I want. I'm trying to pull a thumbnail and store it on a device as the most recent snapshot from a camera. I had assumed that I would base64 encode the image to a string and show it with an <img> HTML tag. So for that I would want a base64 String. Would there be a better way to do this type of thing?

1 Like

Why not support additional request content types and return a ByteArrayOutputStream for the binary content types. This would be consistent with SmartThings.

1 Like

Have you tried this on ST? Do you have an example that works on their system?

Whats the advantage to returning a stream vs just parsing the response into a byte array or into some other encoded format?

Yes. I wrote a SmartApp called Arlo Pilot.

SmartApp

DeviceType

Depending on the use case there could be added overhead, especially in terms of storage. Of course SmartThings offered a still image storage object which Hubitat does not. That would be the best solution for this so the dev does not have to rely on state.

I'm only seeing sync calls there, not async. sync calls already return the input stream.

Apologies.. Thought you were just looking for an input stream example.. I've not coded anything async with a bytestream. On ST wasn't necessary.. On HE I would be concerned with blocking the main event thread.

I suggested earlier in this thread using the scheduler and synchronous http calls to accomplish this.. But I am not sure if scheduled events will block the main event loop or other hub even processing, except possibly for other scheduled events.

1 Like

I would keep it the same between sync and async calls. If sync calls return an input stream than do the same for async calls. Both calls do the same thing, so why implement a different result? It also makes it easier from a documentation perspective.... just my two cents though....

2 Likes

I agree, but we did not make that decision, we are just trying to remain compatible with existing code. If we change it now (or changed it then) all existing async get calls would have had to change when porting to Hubitat.

I might be wrong but I thought the issue was that the data is not passed through at all when it is an image and he got a null value. What I am saying is that it should be passed through as a byte stream in this case (same as the sync call) and not changing every response. Obviously, I don't know the underlying code so I can't determine what if that is even possible. It really depends on why data is null when he uses the async functions and if the async functions does different things based on content type.... I am just shooting in the dark with very opinionated bullets :slight_smile:

1 Like

@chuck.schwer was there ever any progress on this or is there still no way to get image data back from an async call? I'm running into this same issue on a project now.

1 Like

My original question was never answered.. The existing definition is that getData() returns a String. It was done that way because that other system defined it like that and we wanted to remain compatible. So my proposal would be to return the data as a base 64 encoded string, is that sufficient for your requirements?

Indeed it was not answered! It would work for my needs since I'm actually taking the data and base64'ing it myself. I believe @codahq is doing the same. My gut says, @srwhite wants to do something similar. Codahq and I are working on grabbing screenshots from Ring, he was doing Arlo so I'm guessing b64 works for him as well.

1 Like

Ok, I can move forward with doing it that way, thanks!

1 Like

How does this look?

void imageTest() {
    def params = [
        uri: "https://www.w3.org/MarkUp/Test/xhtml-print/20050519/tests/jpeg444.jpg",
        headers: ["Accept": "image/jpeg"]
	]
	asynchttpGet(sendHttpResponse, params)
}

void sendHttpResponse(response, data) {
    if(response.data != null) {
        log.debug "base 64 data size ${response.data.length()}"
        byte[] imageBytes = response.data.decodeBase64()
        log.debug "bytes size = ${imageBytes.size()}"
    }
}

Output:

[app:4624] 2020-05-11 05:36:26.065 pm [debug] bytes size = 5667
[app:4624] 2020-05-11 05:36:26.064 pm [debug] base 64 data size 7556
2 Likes