I’m super sorry to “beat a dead horse” but after digging a bit through the source code of the RawType - I’ve managed to get a rule that updates an Image
type.
Here’s the details:
In my people.items
file, I’ve got an Image
declared:
Image BrelandImage
For my use case, I want to periodically refresh images from instagram - but the ideas would work for any url. I’m using this to update images on a “who’s home” presence board.
Here’s my people_images.rules
:
import java.io.ByteArrayOutputStream
import java.net.URL
when
// run hourly or at system start
Time cron "0 0 0/1 1/1 * ? *" or
System started
then
logWarn("Picture.rules", "Trying to get profile images")
// create a map of our "people" to their image item names
// my home has mutliple people, i am just listing me here
val peopleToItems = newHashMap(
"mindstorms6" -> BrelandImage
)
// iterate over everyone in my map - and get their profile image
for (entry : peopleToItems.entrySet()) {
// start by getting their "json" user data (public) from IG (the ig username is the key in the map above)
val userJson = sendHttpGetRequest("https://www.instagram.com/" + entry.getKey() + "/?__a=1")
// using the JSONPATH transform, extract the actual profile picture url
val userImageUrl = transform("JSONPATH", "$.graphql.user.profile_pic_url_hd", userJson)
// fetch the raw image data. Start by declaring an empty byte array.
var userImageDataBytes = newByteArrayOfSize(0)
try {
// use the built in java URL class - pass it the url to download
val url = new URL(userImageUrl)
// create an output stream - we'll use it for building up our downloaded bytes
val byteStreamOutput = new ByteArrayOutputStream()
// open the url as a stream - aka - start getting stuff
val inputStream = url.openStream()
// n is a variable for tracking how much data we have read off the inputStream per loop (and how much we write to the output stream)
var n = 0
// buffer is another byte array. basically we're using it as a fixed size copy byte array
var buffer = newByteArrayOfSize(1024)
do {
// read from input stream (the data at the url) into buffer - and place how many bytes were read into n
n = inputStream.read(buffer)
if (n > 0) {
// if we read more than 0 bytes - copy them from buffer into our output stream
byteStreamOutput.write(buffer, 0, n)
}
} while (n > 0) // keep doing this until we don't have anything to read.
userImageDataBytes = byteStreamOutput.toByteArray() // assemble all the bytes we wrote into an actual byte array
} catch(Throwable t) {
logError("Picture.rules", "Some bad stuff happened in my rule: " + t.toString)
}
logWarn("Picture.rules", "Got image bytes")
// make a new RawType with the byte array and the mime type - the open hab type Image needs a raw type
// my images are all jpeg - so this mime type is right for me
val rawType = new RawType(userImageDataBytes, "image/jpeg")
// entry.getValue is the "ImageItem" from the map. In my case - this is BrelandImage
if (rawType.toString != entry.getValue().state.toString) { // if this raw type isn't the same as what's already there (the toString is a kind of accurate way to test - the raw type to string is bascially mime + size - which is a bad approximation for equality - but good enough for me
entry.getValue().postUpdate(rawType) // update it!
logWarn("Picture.rules", "Value updated")
} else {
logWarn("Picture.rules", "Images were same")
}
}
logWarn("Picture.rules", "Finished rule update")
end
And just like that - you can update an Image
from a rule. Sadly, the fetch was more complex than I wanted it to be (I tried to coerce the response from sendHttpGetRequest
into the right byte array, but never could get it to work quite right. I also couldn’t seem to get HttpUtils
directly usable in a rule, despite a lot of trying to. YMMV)