Ok, so I got it this way, that I’m using FTP Upload Binding to fill my Image item. Rather cumbersome but successful
I’ve written a small python script which connects to my experimental esp32-cam over http to a MJPEG stream and uploads in regular intervals to the Image item defined with a Channel on the FTP Upload Thing.
Here is my script:
#!/usr/bin/python
import requests
import urllib.request
import urllib.parse
import schedule, time
from ftplib import FTP
import io
# Attach to stream and update image every other second
i=io.BytesIO()
ftp = FTP()
def update_item():
print ("Publish Image")
ftp.storbinary('STOR %s' % 'image.jpg', i)
# Optionally, write to file
#f = open('/etc/openhab2/html/cam.jpg', 'w+b')
#binary_format = bytearray(i)
#f.write(binary_format)
#f.write(i.getbuffer())
#f.close()
schedule.every(2).seconds.do(update_item)
stream = urllib.request.urlopen('http://esp-cam/jpg_stream')
bytes = bytes()
ftp.connect('127.0.0.1', 2121)
ftp.login('user', 'password')
while True:
bytes += stream.read(1024)
a = bytes.find(b'\xff\xd8')
b = bytes.find(b'\xff\xd9')
if a != -1 and b != -1:
jpg = bytes[a:b+2]
bytes = bytes[b+2:]
#print(i.getbuffer().nbytes)
i = io.BytesIO(jpg)
#print(i.getbuffer().nbytes)
print ("Got Image")
schedule.run_pending()
ftp.quit()
But now there is a new problem. Every time the file upload happens, I got the following line in my openhab.log:
Is there a way to stop the binding from logging this? I know, I can adjust my logback.xml but I’d like to stop the binding from logging in the first place.
You can adjust the log4j2 config to exclude lines that match the one you want to not have printed. See openHAB - Filtering event logs. Or you can edit the source code to remove the line that logs that.
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)