Storing image as an attribute on a custom device and displaying in dashboard

I am working on a custom device which will display a snapshot graffic from my Grafana server.
I get the image data and encode it with base64
I can store the image in an attribute of the device.
And when I check device on the device list I can see the image on device properties page in the list of attributes.

But I can't get it to display on my dashboard.
On the dashboard I select the device and then use "attribute" parameter and select the correct attribute name. But it shows "please select an attribute"

I am not sure if I define the device attribute correctly.
I use following code:

attribute "lineqimg", "string"
def displayImage = '<img src="data:image/png;base64,' + encodedImage + '">'
sendEvent(name:"lineqimg", value:displayImage) 

I also tried using "image" as attribute type but it did not differ.

attribute "lineqimg", "image"
def displayImage = '<img src="data:image/png;base64,' + encodedImage + '">'
sendEvent(name:"lineqimg", value:displayImage)

What is the size of the resultant image? Attribute size over 1024 can’t display on the dashboard.

When I display graphs on my dashboard I use an iFrame tile device and the path to the graph, This one let’s me define 3 tile attributes on one virtual device:
https://raw.githubusercontent.com/thebearmay/hubitat/main/tileIframe.groovy

it is about 15K
but OpenWeatherMap-Alerts Weather Driver can display an image of about 35K
so how ?

The dashboard will not load an attribute that is over 1024 characters (which this almost certainly is). I faced a similar issue in developing my Nest integration, Google SDM API.

As a workaround, I split this into two attributes -- one, the rawImg, has the base64-encoded data, and the second, image, has the html code to render it

sendEvent(data.device, [name: 'rawImg', value: img])
sendEvent(data.device, [name: 'image', value: "<img src=/apps/api/${app.id}/img/${data.device.getDeviceNetworkId()}?access_token=${state.accessToken}&ts=${now()} />", isStateChange: true])

-- using a "mapping" in the App to provide the src url in the html, read the rawImg attribute, and return the data.

mappings {
    path("/img/:deviceId") {
        action: [
            GET: "getDashboardImg"
        ]
    }
}

def getDashboardImg() {
    def deviceId = params.deviceId
    def device = getChildDevice(deviceId)
    logDebug("Rendering image from raw data for device: ${device}")
    def img = device.currentValue('rawImg')
    render contentType: 'image/jpeg', data: img.decodeBase64(), status: 200
}

This works for local display, but there seems to be a size cap on the App API mapping response, that it doesn't render on a cloud dashboard.

1 Like

that is a good solution for me.
I just want to see the image locally.
But, mine is not an app , it is a device driver.
Can I still use this method ? Or should I have to write an app ?

does the "mappings {" directive work with a device driver ?

no, "mappings" does not work in a Driver, only Apps can provide APIs. In my case I had the App and Drivers already in place to provide the full solution...

ok I tried and see that mapping is not possible with a device driver.
So I will use a simple app to handle it.
But I am not sure if these variables are populated automatically.
${data.device.getDeviceNetworkId()}
${state.accessToken}

should I define these ?

data.device.getDeviceNetworkID() -- I was working with a previous API response, and passing the device object along through the method params. In your case, you might just need to call getChildDevices(), or if there will only be one child, you could cache the DNI as a state value when it is created.

state.accessToken is populated when you call createAccessToken() -- recommend doing this in the def installed() method of your App.

thanks.
I wrote a simple app to do this.
But there is no child parent relation between my app and device since the device was manually created.
I pass the device id of the device manually to the app:

        sendEvent(name:'image', value: "<img src=/apps/api/900/img/d211d4c0-744c-415c-9398-f740d67b58b4?access_token=396dcf8f-bdf6-431e-9d91-6a003cdd1bef&ts=${now()} />")

can I get the device without a child relation ?
I mean instead of using:

def device = getChildDevice(deviceId)

how can I get a device with its deviceId ?

In the app, you can define an input based on a device type or capability...

device type:

input(name: "imgDevice", type: "device.customDeviceType", title: "Select Image Device", description: "", multiple: true, required: true, submitOnChange: true)

device capability:

input(name: "imgDevice", type: "capability.imageCapture", title: "Select Image Device", description: "", multiple: true, required: true, submitOnChange: true)

can obviously change multiple: true to false if you only want to select one -- this selection would then be available as the settings.imgDevice var in your App (when multiple: true it is a List to iterate)

Not sure I've ever tried sending events to a device with this model, but I don't see a reason why it wouldn't work...

Otherwise, you can simply have the device be created by your app, using addChildDevice

Works if the app is on the same hub as the device, doesn't work across Hub Mesh on a shared device though.

1 Like

ok. I wrote the following:

    if (it.id.equals(deviceId)) {
        log.debug "Found device: ID: ${it.id}, Label: ${it.label}, Name: ${it.name}"
        def img = it.currentValue('lineqimgraw')
        log.debug img
        render contentType: 'image/jpeg', data: img.decodeBase64(), status: 200
    }

now it finds the device , match the device Id. But render command returns a json output starting:

[{"currentStates":[{"value":"<img src="data:image/png;base64,iVBORw0KGgoAAAA

what is wrong ?

got it. acts like that when it is in the loop.
if I put the render command out of loop its output is the image.
I really don't understand groovy.

1 Like