Help needed: parse JSON in JRuby rule

Hi @jimtng,

may I ask for your help again how to parse and persist a JSON timeseries using a JRuby rule? I already get an error with a very simple rule I’ve created so far.

I’m pulling the JSON through the http binding using the Open-Meteo API.

My rule looks like this:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: OpenMeteo_SSO
    type: core.ItemStateUpdateTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/x-ruby
      script: |
        require "json"

        #time_series = TimeSeries.new
        data = JSON.parse(event.to_s)
        logger.info "parsed data: #{data}"
    type: script.ScriptAction

The Item OpenMeteo_SSO is containing the JSON string from this URL.

When executing that rule I get the following error:

2024-12-27 10:26:39.298 [ERROR] [.handler.AbstractScriptModuleHandler] - Script execution of rule with UID '10be2fb372' failed: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1

My goal is to extract the timestamps and values into a timeseries Item.

Thanks!

There’s a problem in jrubyscripting addon for UI rules in openhab 4.3.0 that will be fixed in 4.3.1. This doesn’t affect file-based rules.

In the mean time please uninstall the jrubyscripting addon from the addons store, and drop this jar in your conf/addons folder:

In the above code, change event.to_s to: event.state.to_s

To persist the timeseries to an item, let’s see what the (parsed) json looks like first.

… ahh, ok. I replaced the add-on and now that error is gone. Yeah, I’m using 4.3 release version.

Appreciate your help on parsing the JSON. It has the timestamp and values in separate sections, not as pairs. Yet I fail to get access to the different sections and fields.

Thanks!

Paste it here please

PS. I don’t know your reasons to use HTTP binding, but if it’s just to retrieve this JSON, you could do that in Ruby. IMO it would be a lot less complex than dealing with HTTP binding + separate thing + item just for the sake of getting the JSON data.

In JRuby it would just be

json = HTTP.get("https://xxxxxxxx") # using the HTTP action from openhab

# or

require "net/http"
json = Net::HTTP.get(URI("https://xxxxx")) # Using Ruby's Net::HTTP

… it’s pretty large. I put a link to it here (again).

Sorry I missed that.

Here’s the Ruby code:

data = JSON.parse(event.state.to_s)

time_zone = ZoneId.of(data["timezone"])
unit = data["minutely_15_units"]["global_tilted_irradiance_instant"]

data = data["minutely_15"]["time"].map.with_index do |time, index|
  [
    ZonedDateTime.of(java.time.LocalDateTime.parse(time), time_zone),
    data["minutely_15"]["global_tilted_irradiance_instant"][index] | unit
  ]
end

time_series = TimeSeries.new

data.each do |entry|
  time_series << entry
end

# logger.info time_series.map { |entry| "#{entry.timestamp}: #{entry.state}" }.join("\n")

Item_To_Persist.time_series = time_series

This assumes that your item is a dimensioned item. If it’s a plain Number item, simply remove | unit from inside map above.

1 Like

… very cool, thanks a lot - this gets me going!

1 Like

I’m tryiing to do the same thing, but with a different json outpunt and setup. Can’t get it to work… Who can help out?

the json outpunt give a forcast per hour of weather conditions, all I need is time (data[i].tijd_nl) and temperature (data[i].temp). And I like to have them in a time series item so I can use it in the Entsoe script.

thanks!

need more info, sample json data, your existing code, etc.

apologies, forgot the json to paste (in total it gives forecasts for 3 days, every hour):

{
  "plaatsnaam": [
    {
      "plaats": "De Bilt"
    }
  ],
  "data": [
    {
      "tijd": "1735646400",
      "tijd_nl": "31-12-2024 13:00",
      "offset": "19",
      "loc": "none",
      "temp": "3",
      "winds": "6",
      "windb": "4",
      "windknp": "12",
      "windkmh": "21.6",
      "windr": "207",
      "windrltr": "Z",
      "vis": "50000",
      "neersl": "0",
      "luchtd": "1023.5",
      "luchtdmmhg": "767.7",
      "luchtdinhg": "30.22",
      "hw": "100",
      "mw": "0",
      "lw": "100",
      "tw": "100",
      "rv": "91",
      "gr": "11",
      "gr_w": "30",
      "cape": "-",
      "snd": "0",
      "snv": "0",
      "cond": "2",
      "ico": "2",
      "samenv": "Bewolkt",
      "icoon": "bewolkt"
    },
    {
      "tijd": "1735650000",
      "tijd_nl": "31-12-2024 14:00",
      "offset": "20",
      "loc": "none",
      "temp": "3",
      "winds": "7",
      "windb": "4",
      "windknp": "14",
      "windkmh": "25.2",
      "windr": "206",
      "windrltr": "Z",
      "vis": "50000",
      "neersl": "0",
      "luchtd": "1022.9",
      "luchtdmmhg": "767.2",
      "luchtdinhg": "30.21",
      "hw": "99",
      "mw": "0",
      "lw": "100",
      "tw": "100",
      "rv": "92",
      "gr": "6",
      "gr_w": "17",
      "cape": "-",
      "snd": "0",
      "snv": "0",
      "cond": "2",
      "ico": "2",
      "samenv": "Bewolkt",
      "icoon": "bewolkt"
    },
    {

and this is the Ruby code I’m using, I can’t figure out what to do:

require "json"

# data = JSON.parse(event.state.to_s)
data = HTTP.get("https://data.meteoserver.nl/api/uurverwachting.php?locatie=Wezep14400&key=2eded036b7")
#logger.info data
time_zone = "Europe/Amsterdam"

#ZoneId.of(data["timezone"])
unit = data["data"]
logger.info unit
data = data["data"].map.with_index do |time, index|
  [
    ZonedDateTime.of(java.time.LocalDateTime.parse(time), time_zone),
    data["data"]["temp"][index] | unit
  ]
end

time_series = TimeSeries.new

data.each do |entry|
  time_series << entry
end

logger.info time_series.map { |entry| "#{entry.timestamp}: #{entry.state}" }.join("\n")

Item_To_Persist.time_series = time_series

thanks!

It seems your json was truncated? EDIT: Nevermind, I’ll check the url from your code

@emiel, give this a try, but change the Item_To_Persist accordingly. I’m assuming that your item is a Number:Temperature item.

Hopefully the code is self explanatory. It’s a file-based rule that triggers every day.

# frozen_string_literal: true

require "json"

rule "Update Temperature time series" do
  every :day
  run do
    time_series = TimeSeries.new
    json = HTTP.get("https://data.meteoserver.nl/api/uurverwachting.php?locatie=Wezep14400&key=2eded036b7")
    data = JSON.parse(json)
    data["data"].each { |entry| time_series << [Time.at(entry["tijd"]), entry["temp"].to_f | "°C"] }

    Item_To_Persist.time_series = time_series
  end
end

PS, your key is displayed here. Want me to redact it from my post?

thanks! I’ll give it a try and I’ll change the key, so you can leave it here.

So I changed the Item_to_persist into:

Weersverwachting_Wezep_Temperature.time_series = time_series

The item is a number and has the predefined AllForecastItems persistance policy.

The output is only teh first Temp value in the json. The time stamp in de database isn’t the value of the json but the time it was updated.

The rule only runs at midnight. Has it run yet?
You can also add on_load line before or after the every :day line so the rule will also run whenever it loads (eg as soon as it’s saved)

Is your item a Number:Temperature ?

Yes, the Item Temperatuur (typo) is a number with dimension Temperature. This is the code I use:

# frozen_string_literal: true

require "json"

rule "Update Temperature time series" do
  on_load
  every :day
  run do
    time_series = TimeSeries.new
    json = HTTP.get("https://data.meteoserver.nl/api/uurverwachting.php?locatie=Wezep14400&key=xxxxxxxxxxx")
    data = JSON.parse(json)
    logger.info(data)
    data["data"].each { |entry| time_series << [Time.at(entry["tijd_nl"]), entry["temp"].to_f | "°C"] }

    Weersverwachting_Wezep_Temperatuur.time_series = time_series
  end
end

I can now run it manually and it logs the full json output (as a test). But before that, I get this error:


```yaml
2025-01-01 08:53:39.916 [WARN ] [mmon.WrappedScheduledExecutorService] - Scheduled runnable ended with an exception:

java.lang.NoClassDefFoundError: com/oracle/truffle/polyglot/PolyglotObjectProxyHandler

at com.oracle.truffle.polyglot.PolyglotFunctionProxyHandler.invoke(PolyglotFunctionProxyHandler.java:152) ~[bundleFile:?]

at jdk.proxy1.$Proxy1061.run(Unknown Source) ~[?:?]

at org.openhab.automation.jsscripting.internal.threading.ThreadsafeTimers.lambda$0(ThreadsafeTimers.java:90) ~[bundleFile:?]

at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$12(SchedulerImpl.java:189) ~[?:?]

at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$1(SchedulerImpl.java:88) ~[?:?]

at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[?:?]

at java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[?:?]

at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[?:?]

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) [?:?]

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) [?:?]

at java.lang.Thread.run(Thread.java:840) [?:?]

Caused by: java.lang.ClassNotFoundException: com.oracle.truffle.polyglot.PolyglotObjectProxyHandler cannot be found by org.openhab.automation.jsscripting_4.3.0

... 11 more

and finally this error:

2025-01-01 09:07:57.767 [ERROR] [ting.script.rule.script:8a7c7ba6f8:5] - can't convert String into an exact number (TypeError)
<script>:13:in `block in <main>'
<script>:13:in `block in <main>'
<script>:5:in `<main>'

How are you invoking this rule?

I just hit the Run Now button in the edit script window