Usage of Image Item

Hi,

is it possible to save an image from url directly to an image item in a rule?

I tried something linke that:

var String ImageTest = sendHttpGetRequest("http://192.168.222.140/mjpeg/snap.cgi?chn=0")
IPCameraSendPictureCam01.postUpdate(ImageTest.toString)

But with this i get nothing like errors…

The Image Item requires a Base64 encoded file. Does that URL return that or does it return raw binary?

I’ve seen no examples of how to do this from Rules. Everything that uses the Image Item seems to be bindings.

If you can figure out how to get the image data and encode the raw binary into Base64 you can then put the Base64 into the Image Item.

Thanks for the hint rikoshak but I can’t figure out how to REST PUT a jpeg or png file to an Image item.

import requests
url = 'http://127.0.0.1:8080/rest/items/ESP_Cam_Image'
data = base64.b64encode(open('/etc/openhab2/html/test.jpg', 'rb').read())
res = requests.post(url,
                data=data,
                headers={'Content-Type': 'application/octet-stream'})
print (res.history)
print (res.headers)
print (res.text)
print (res.status_code)

Return code is 415 unsupported media type:

[]
{'Content-Type': 'application/json', 'Content-Length': '227', 'Server': 'Jetty(9.4.11.v20180605)'}
{"error":{"message":"HTTP 415 Unsupported Media Type","http-code":415,"exception": 
{"class":"javax.ws.rs.NotSupportedException","message":"HTTP 415 Unsupported Media Type","localized- 
message":"HTTP 415 Unsupported Media Type"}}}
415

I’ve tried application/octet-stream, image/jpeg, binary/raw etc. but to no avail.
I’ve looked up the Image Type and traced back to RawType in ESH https://github.com/eclipse/smarthome/blob/3e41080bff62d84e073b573e8398b54939289018/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/library/types/RawType.java

Does anybody know if RawType is not completely supported by the REST API or am I doing something stupid here?

Best,
Sascha

Ok, so I got it this way, that I’m using FTP Upload Binding to fill my Image item. Rather cumbersome but successful :grinning:

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:

2018-12-25 21:24:16.822 [WARN ] [g.apache.ftpserver.impl.PassivePorts] - Releasing unreserved passive port: 39241

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.

Best regards & seasons greetings,
Sascha

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)

7 Likes