How to dynamically change image at URL (rename github file?)

I'm trying to figure out if there's a way to dynamically change the background image of my Sharptools dashboard, depending on a Hubitat device's state, location mode, weather, holidays, etc. Sharptools accepts a URL for the background image to display, so if I can program an app to dynamically change the image at a certain URL, that would work.

One way could be to have a file in github called something like "backgroundImage.jpg" which would have a fixed URL, and then use a Hubitat app to rename whatever github-stored image I want to have the name of "backgroundImage.jpg". But I don't know if there's a way for Hubitat to rename a github file... Or if there's a better way to do this.

Any ideas?

Note: I know I can use Fully Kiosk Browser to dynamically change the displayed Sharptools dashboard, but that requires me to design and maintain multiple dashboards, something that could prove tedious.

Boy doing that with Github would be a LOT of overkill. It would work, but you'd need to build an app that implements the Github API. Do you need this image to work remotely? Or are you OK if it only works within your network?

My immediate use case just requires local. So that'd be fine if there's not a way that would work remotely.

Well there's a way to do anything, it depends on how much work you want to make for yourself! If you need it to be remote accessible you need somewhere to host the files. If you need it local., this can all be done with the HE. What are all the criteria for deciding what image to use? You could basically write an app with a webservice endpoint that returns the correct image based on the criteria. I kind of like where you're going with this, it sounds interesting. Sounds like you want something like show rain on a rainy day, show a nice snowy Christmas tree on Christmas, things like that? I think that's cool! The hardest part comes down to figuring out all the criteria. Like for example, weather vs. holiday -- it's hot and rainy on Christmas... show snowy pretty Christmas scene? Or rainy miserable day?

Yup, that's the idea. My rainy day dashboard at the moment would be something like this:


The criteria really could be anything. I would probably prioritize holiday over weather, and choose holiday images that are weather agnostic. The whole idea is to exploit aesthetically pleasing backgrounds for informational purposes, not just for looks.

So how do you do this? I know how to create the endpoint and mappings, but all I have experience with so far is returning an SVG. Don't know how to return an actual image file like a JPG

It's actually something new in 2.2.1. If you use render() and return a byte[] it will work as long as you set the right content type header. Got me thinking about doing something like this for my dashboards too. I think it's a cool idea.

Ok, I guess I also need to figure out where to store the images, and how to pull them into HE so that I can then return them at the endpoint...Any example apps that you know of that do something similar?

Maybe this is what I need?

I tried this but it returns a null response. Not sure what's different here than in the example from that thread.

void getImage() {
    def params = [
        uri: "https://raw.githubusercontent.com/lnjustin/wallpaper/master/cropped%20dreamstime_l_155161055.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()}"
        render contentType: "image/jpeg", data: imageBytes, status: 200
    }
    else log.debug "Null response"
}

I’ll have to test but that’s not going to work anyway. You can’t call render in an asynchronous all back like that

Yeah, I was afraid of that...

Use a synchronous call

    def params = [
        uri: "https://raw.githubusercontent.com/lnjustin/wallpaper/master/cropped%20dreamstime_l_155161055.jpg",
        headers: ["Accept": "image/jpeg"]
    ]
    httpGet(params) { resp ->
        if(resp?.data != null) {
            byte[] imageBytes = resp?.data.decodeBase64()
            render contentType: "image/jpeg", data: imageBytes, status: 200
        }
        else {
            log.error "Null Response"
        }
    }

Gives this error:
groovy.lang.MissingMethodException: No signature of method: java.io.ByteArrayInputStream.decodeBase64() is applicable for argument types: () values: [] on line 66 (getImage)

Not sure what to do about that. Does that suggest the image I've got isn't appropriate for decodeBase64? I'm pretty clueless on this file decoding stuff.

The synchronous one just returns a ByteArrayInputStream, not base64. So you just need to convert that stream to bytes. You'd need to google, I don't know the method off hand.

    def params = [
        uri: "https://raw.githubusercontent.com/lnjustin/wallpaper/master/cropped%20dreamstime_l_155161055.jpg",
        headers: ["Accept": "image/jpeg"]
    ]
    httpGet(params) { resp ->
        if(resp?.data != null) {
            byte[] imageBytes = new byte[resp?.data.available()]
            resp?.data.read(imageBytes)
            render contentType: "image/jpeg", data: imageBytes, status: 200
        }
        else {
            log.error "Null Response"
        }
    }

I think this is the way to do it (https://www.baeldung.com/convert-input-stream-to-array-of-bytes)

But I get nothing. No error in the log. No image output. Ugh.

You still can't call render within a closure like that. You need to assign the imageBytes to a variable available outside of the httpGet and then call render on that.

Same result tho. No error, no image output. Any other ideas?

    byte[] imageBytes = null
    def params = [
        uri: "https://raw.githubusercontent.com/lnjustin/wallpaper/master/cropped%20dreamstime_l_155161055.jpg",
        headers: ["Accept": "image/jpeg"]
    ]
    httpGet(params) { resp ->
        if(resp?.data != null) {
            imageBytes = new byte[resp?.data.available()]
            resp?.data.read(imageBytes)
        }
        else {
            log.error "Null Response"
        }
    }
    render contentType: "image/jpeg", data: imageBytes, status: 200

And you're testing using a local endpoint? If so I'll have to test when I'm at a pc

Yup, local endpoint.

The code below works to get the byte data for the image, but does not appear to render the image. Logging shows the right byte count. So must be an issue with rendering those bytes...

    byte[] imageBytes = null
    def params = [
        uri: "https://raw.githubusercontent.com/lnjustin/wallpaper/master/cropped%20dreamstime_l_155161055.jpg",
        headers: ["Accept": "image/jpeg"]
    ]
    httpGet(params) { resp ->
        if(resp?.data != null) {
            imageBytes = resp?.data.getBytes()
            log.debug "bytes size = ${imageBytes.size()}"
        }
        else {
            log.error "Null Response"
        }
    }
    render contentType: "image/jpeg", data: imageBytes, status: 200

I haven't tested it out yet, I was actually going to play with it later today, but this is what @chuck.schwer sent me as an example:

def getImage() {
    String imgBase64 = "iVBORw0..."
    
    render data: imgBase64.decodeBase64(), 
        contentType: "image/png"
} 

So I think what you have should work as long as it's a local endpoint. He told me cloud will NOT work

Hmm. Ok, well let me know if you come up with anything!