oH 4.1.1 - Download an image and save it into an item

Hi there,

i would like to get a new look on this topic.

Some while ago i searched for a rule that could download an image from the internet and save it into an item.
I found a solution from @mindstorms6 that worked for quite some time now.
With oH 4 something has changed and this rule is throwing an out of memory error.

The rule in question:

rule "Speedtest Image Update"

when

    Item spdURL changed

then

    val speedImage = spdURL.state + ".png"
    var userImageDataBytes = newByteArrayOfSize(0)

    try {
        // use the built in java URL class - pass it the url to download
        val url = new URL(speedImage)
        // 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(ruleId, "Es ist ein Problem beim Download aufgetreten: " + t.toString)
    }
    logDebug(ruleId, "Bild Daten erhalten")

    // 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 != spdImage.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
        spdImage.postUpdate(rawType) // update it!
        logDebug(ruleId, "Bild aktualisiert")
    } else {
        logDebug(ruleId, "Bilder waren identisch")
    }

end

The error:
An error occurred while calling method 'NetworkAddressChangeListener.onChanged()' on 'org.openhab.core.io.transport.mdns.internal.MDNSClientImpl@69ce48c5': unable to create native thread: possibly out of memory or process/resource limits reached

ItĀ“s totally random when this error appears.
Sometimes itĀ“s only once a day and sometimes itĀ“s multiple times in a row.
I know itĀ“s coming from this rule as the logError for catch(Throwable t) is only used in this rule.

Is there something in oH 4 (until 4.1.1) that changed and could have an impact on this rule?
I switched to the raspberry pi 5 with openhabian v1.8c and an Sandisk Endurance card.
So atleast the rpi and sd card are brand new and canĀ“t have an impact on this.

kind regards
Michael

Are you sure, that it is caused by the rule or does the rule just catch the symptoms ?
What is the memory usage over time ?

I didnĀ“t change anything else except upgrading openHAB.
No new rules, bindings or any other changes.

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     ZEIT+ BEFEHL
   1018 openhab   20   0   22,4g   1,9g 136768 S   2,0  23,9 228:12.56 java
###############################################################################
###############  openhab  #####################################################
###############################################################################
##   Release = Debian GNU/Linux 12 (bookworm)
##    Kernel = Linux 6.1.0-rpi7-rpi-2712
##  Platform = Failed to start bthelper@hci0.service - Raspberry Pi bluetooth helper.
##    Uptime = 3 day(s). 01:44:36
## CPU Usage = 1.41% avg over 4 cpu(s) ( core(s) x - socket(s))
##  CPU Load = 1m: 0.06, 5m: 0.07, 15m: 0.02
##    Memory = Free: 4.17GB (53%), Used: 3.79GB (47%), Total: 7.86GB
##      Swap = Free: 2.99GB (100%), Used: 0.00GB (0%), Total: 2.99GB
##      Root = Free: 20.82GB (76%), Used: 6.40GB (24%), Total: 28.70GB
##   Updates = 8 apt updates available.
##  Sessions = 1 session(s)
## Processes = 148 running processes of 4194304 maximum processes
###############################################################################

These 3 days are with the rpi 5.

OH4 runs out of memory may give some hints for debugging.

Remember to call close once stream is consumed.

Thanks for the hint.
What would be the correct place to close the stream?

Edit: So this does not work and results in an errorā€¦

try {
        ...        
    } catch(Throwable t) {
        ...
    }finally{
        inputStream.close();
    }

2024-03-07 14:18:48.356 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'speedtest-2' failed: The name 'inputStream' cannot be resolved to an item or type; line 177, column 9, length 11 in speedtest

Using JRuby, your rule looks like this:

rule "Speedtest Image Update" do
  changed spdURL
  run do
    speed_image_url = "#{spdURL.state}.png"
    spdImage.update_from_url(speed_image_url)
  end
end

Or you can do it in one line

changed(spdUrl) { spdImage.update_from_url("#{spdURL.state}.png") }
val inputStream;
try {
  inputStream = open(ā€¦);
  ā€¦
} finally {
  if (inputStream != null) inputStream.close();
}

I assume this only works with rules from the UI and not in .rules files?


IĀ“m honest and donĀ“t know how to get this working with my rule :slight_smile:

I just put the inputStream.close(); after userImageDataBytes = byteStreamOutput.toByteArray() inside the try { ... } phase.
Seems to work until nowā€¦ iĀ“ll wait and see if itĀ“s still throwing an OutOfMemoryError.

That was for a file based rule but in jruby, so you need to install the jruby automation addon and create the file insideconf/automation/ruby/ folder, and call it something.rb. See Installation

You can do something similar in UI rule too, but the rule / trigger creation is slightly different. The rule body is the exact same.

1 Like

No worries, since your rule is written in DSL it might differ a bit in certain aspects. More recent Java version allow to use try (InputStream str = open(...)) { ... } syntax which automatically close stream after leaving inner code block.
Whole point of sample I pasted is:

  1. Declare input stream variable before try { part
  2. Open stream inside contents of try block
  3. Within finally part close stream, only if it was successfully opened before

It might happen that url.openStream() throws an error, then finally section has nothing to do since stream was not even created. Thatā€™s why its necessary to have a null check.

1 Like

Perfect :slight_smile:
Looks like itĀ“s working.


IĀ“ll use the JRuby approach as it is much easier and uses some standard methods instead of building them inside a rule.

Thanks for you help Jim and Lukasz!

1 Like