How to process data in a time series?

Hi,

may I ask for some advice on how to access time series information e.g. through a JRuby rule (@JimT)? I got the time series data persisted with the inmemory service. Now I want to find the 3 lowest value elements in the time series. I built something in JS but its working off of the incoming JSON, not the time series. Its parsing the JSON into an array, sorting the array and that way gives me access to the lowest value elements through the array index. Can one send me an example how to use the time series to achieve something similar?

Thanks for your help!

TimeSeries (much like everything else) in JRuby has been enhanced and made to work like a normal Ruby array.

time_series.sort_by(&:state).take(3)

See:
sort_by
take

The syntax sort_by(&:state) is called pretzel-colon. It is equivalent to sort_by { |entry| entry.state }. See The Pretzel Colon in Ruby. I’d like to introduce you to a method… | by Chris Jones | Medium

… ahh, very cool :star_struck:

How do I get a subset of the time series first? Like all values for today. In DSL I’m trying to use the persistence extensions like .getAllStatesUntil(ZonedDateTime) or getAllStatesBetween(ZonedDateTime, ZonedDateTime). JRuby has access to those too, no?

Thanks again!

As does JS Scripting and I believe in both cases what is returned is an Array: JavaScript Scripting - Automation | openHAB

  • .getAllStatesBetween(begin, end, serviceId) ⇒ Array[PersistedItem]

I’m not sure I understand why you’ve had to go about messing with the JSON like that in JS. Once it’s in database it should be a simple manner of:

var futureItems = items.myItem.getAllStatesUntil(time.toZDT('13:00')); // get all states until 13:00 today

And then, as with jRuby, you can do anything you can do with an Array. To get the three lowest values:

var threeLowest = items.MyItem.getAllStatesUntil(time.toZDT('13:00')).sort((a,b) => {
  if(a.numericState < b.numericState) return -1;
  else if(a.numericState > b.numericState) return 1;
  else return 0;
}).slice(2);

I’m assuming you still want the timestamp associated with the three lowest values.

I’m not trying to argue you shouldn’t use jRuby. I’m just pointing out that what you want to do is also possible in JS without using the round about approach you are using.

Can you clarify?

Do you want to filter what’s in the time series for only today’s timestamp, or do you want to get data from persistence? Those are two different things, no?

Thanks Rich & Jim,

I’m trying to find my way through DSL, JS, JRuby - whatever works. I’m most familiar with DSL, have 2 JRuby rules though and used JS for transformations only so far.

So I’m receiving a JSON with time stamps and data that gets persisted as a time series. That time series may contain data across multiple days. I want to pull the data (states?) for today from the time series into an array that I can process like to find the 3 lowest values (states?). I don’t care about the associated time stamps (yet).

Reading the JS code from Rich looks like its close. Not sure yet what that if/else section does. I guess I will explore JS rules next :smile:

BTW, in DSL I get an error when i do this:

val allStates = TS.getAllStatesUntil(ZonedDateTime.now().with(LocalTime.MIDNIGHT).plusDays(1),"inmemory").getState()

I get in the log:

2024-09-13 17:08:20.624 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'test-1' failed: 'getState' is not a member of 'java.lang.Iterable<org.openhab.core.persistence.HistoricItem>'; line 14, column 18, length 112 in test

So if I understand it correctly, you have:

JSON Data → TimeSeries → Persistence

You can either grab this data once you’ve parsed the json data, or once you’ve built the time series, or you can extract it out of persistence after you’ve persisted it.

I don’t know your json data structure / code, but with time series:

begin_of_day = LocalTime::MIDNIGHT.to_zoned_date_time.to_instant
end_of_day = begin_of_day.plus(1.day)
lowest_3_states = time_series.select { |entry| entry.timestamp.between?(begin_of_day, end_of_day) }.map(&:state).sort.take(3)

Using persistence:

begin_of_day = LocalTime::MIDNIGHT.to_zoned_date_time
end_of_day = begin_of_day.plus(1.day)
lowest_3_states = TS.all_states_between(begin_of_day, end_of_day).map(&:state).sort.take(3)

This gives you an array, so you can’t just call .getState right away. You’d need .get(0).getState

great, that works :+1:

I tried your “Using persistence” example as this is what I want to do. How do I point to the right persistence service of the time series? Default is rrdj4 while the time series item “TS” is persisted using inmemory service.

one of three ways

  1. Specify the service id/name at the end of the persistence call
begin_of_day = LocalTime::MIDNIGHT.to_zoned_date_time
end_of_day = begin_of_day.plus(1.day)
lowest_3_states = TS.all_states_between(begin_of_day, end_of_day, :inmemory).map(&:state).sort.take(3)
  1. put the persistence call within a persistence block
begin_of_day = LocalTime::MIDNIGHT.to_zoned_date_time
end_of_day = begin_of_day.plus(1.day)
lowest_3_states = persistence(:inmemory) do
  TS.all_states_between(begin_of_day, end_of_day).map(&:state).sort.take(3)
end
  1. Use persistence! to indicate that you’d like to set the given persistence service as the default for the subsequent calls within this script (only!)
persistence!(:inmemory) # This will apply to this entire script only

begin_of_day = LocalTime::MIDNIGHT.to_zoned_date_time
end_of_day = begin_of_day.plus(1.day)
lowest_3_states = TS.all_states_between(begin_of_day, end_of_day).map(&:state).sort.take(3)

Pick whichever you prefer

For compleness, in Rules DSL and JS Scripting this is how you would do it too. The block and script/file approach are jRuby only.

1 Like