Create timers from csv file in a rule

Hello,

In a rule, I want to read a csv file and do several actions based on each row.

An example:

time;item;action;duration
7:20;item1;ON;5
7:30;item2;ON;25

line #1 → send ON to item1 and 5 seconds later send OFF
line #2 → send ON to item2 and 25 seconds later send OFF

My questions:

  • parsing the file, and reading the strings, how can I retrieve the item from the strings that has its name
  • how can I create the timer with the item, since it won’t be visible inside the timer code.

Thanks.

For a DSL rule

For a DSL rule, the creation of a Timer ‘captures’ the variables from the outer parent rule that you may reference inside the Timer code.

This looks a little but like na XY Problem.

Why a CSV file? What is populating this file? What’s the overall end goal?

I suspect there may be more direct and easier to manage ways to handle this.

If you are using jruby this is what your script might look like:

require 'csv'

CSV.foreach("path/to/file.csv", headers: true) do |row|
  begin
    time = Time.parse(row["time"])
    after(time) do
      item = items[row["item"]]
      item.command row["action"], for: row["duration"]
    end
  rescue
    logger.warn("Error processing row: #{row}")
  end
end

If you have a lot of entries (thousands) in the file, you might consider implementing it differently, and not create one timer per line.

Without more detail I can’t say whether these ideas would make sense or not in this case. But some alternative approaches that do not include parsing a CSV file:

  • Put the events into persistence (through the REST API or simply recording the events as they happen) and then replay them using something like Persistence Presence Simulation

  • Whatever is populating the CSV file in the first place maybe could be modified to interact with OH directly. Either pushing this schedule in total or injecting the events at the right time through the REST API (or MQTT or what ever floats your boat).

  • Instead of creating a separate timer for each one, create a single timer which works off the list and reschedules itself as needed. In fact, the Gatekeeper library in my openhab_rules_tools will handle the timer stuff for you. It’d look something like this (ECMAScript 2021):

var { gatekeeper } = require('openhab_rules_tools'); // note this gets installed by openHABian by default now

var gk = cache.get(ruleUID+'_gk', () => { new gatekeeper.Gatekeeper('csv timers'); } ); // replace with privateCache in OH 3.4+
gk.cancelAll(); // cancel any already scheduled activities

var Duration = Java.type('java.time.Duration');
var rawCSV = actions.Exec.executeCommandLine(Duration.ofSeconds(5), 'cat', '/path/to/csv/file.csv'); // It's probably better to use the JavaScript `fs` to read the file but this was expedient

rawCSV.split('\n').filter(() => { return !line.startsWith('time'); } )
                  .forEach((line) => {
                    const parts = line.split(';');
                    gk.addCommand(parts[0], () => { items.getItem(parts[1]).sendCommand(parts[2]); } );
                    const command = (parts[2] != 'ON') ? 'OFF' : 'ON';
                    gk.addCommand('PT'+parts[3]+'s', () => { 
                    items.getItem(parts[1]).sendCommand(command); } );
                  });

I had to make a few assumptions here.

  • time is in 24 hour time
  • if action is anything other than “ON” the follow up command is “OFF”
  • the order of the lines are in the order they should run (meaning the rows are sorted by the time column)
  • if for some reason two events are scheduled to occur a the same time, it’s OK if one is a few milliseconds off.
  • duration is always in seconds and always an integer

The Gatekeeper manages a single internal timer which reschedules itself based on when the next command is to run. I had to convert the duration to an ISO8601 formatted duration string.

The above could be used as is in a UI rule as the script action or used as the action passed to a rule in a text based config.

Note I just typed the above in. There will likely be typos. It also could use more error checking. But with this approach you just have the one timer.

The first goal is control 14 roller shutters with different times for weekdays, Saturdays and Sundays. The time the roller shutters are UP/DOWN has to have second precision (my roller shutters controllers do not work with percentage, as more modern ones do). For each roller shutter there are 6 to 8 movements each day. My first thought was to create a rule for each roller shutter, but I would loose visibility of the schedules, spread over so many rules. Second, I tried the icalendar approach, but can’t have second precision.

I would be populating the csv file, manually from times to times

Didn’t have much time to inspect the code, but your code assumes only one timer is scheduled at once, right? That would not be a problem on my case, though.

The second use would be to create a fixed scheduler for presence simulation. Yes I know that I can record the items and replay them (this was my first attempt), but due to my usual routine, the result is not very good.

Any more suggestions to overcome this are welcome.

Another option to consider below. You just need to manage the SHUTTERS_SCHEDULE array as needed.

SHUTTERS_SCHEDULE = [
  ["7:20am", Item1, UP, 5.seconds],
  ["4:30pm", Item1, DOWN, 5.seconds],
  ["7:30am", Item2, UP, 25.seconds]
  # etc
].freeze

rule "roller shutters schedule" do
  SHUTTERS_SCHEDULE.each do |time, item, command, duration|
    every :day, at: time, attach: [item, command, duration]
  end

  run do |event|
    item, command, duration = event.attachment
    item.command command, for: duration, on_expire: STOP
  end
end

There are oh so many ways to achieve this and Rich provided great insights in detail above.

You could also embed the time / duration into each item’s metadata if that’s more convenient for you, e.g. if you have the items defined in an .items file and all the shutters are defined in one place.

You could go even more fine grained and not just create timers for each shade, but create rules using the method Rich posted above. You can do that in Jsscripting. I haven’t really found a use case for it, but below is my test code. Using this code I created rules for each thing I have. I haven’t worked out if you could do the same with rules.JSRule though. BTW I use file based rules. Don’t know how this would work in UI rules.

Note: this example is silly. Just use wildcards if you want a rule to trigger each time any thing changes. Point was just a proof of concept.


function () {
	console.log("testing thing function");
	for each (var thingX in things.getThings()) {
		console.log("Creating rule for " + thingX.label);
		rules.when().thing(thingX.uid).changed().from("ONLINE").or().thing(thingX.uid).changed().to("ONLINE").then(event => {
			console.log("Things offline rule: " + things.getThing(event.thingUID).label + " online status changed");
			var offlinethings = 0;
			for each (var thingY in things.getThings()) {
				if (thingY.status != "ONLINE"){
					offlinethings = offlinethings + 1;
				}
			}
			items.getItem("OfflineThings").postUpdate(offlinethings);
		}).build("Offline rule " + thingX.uid,  "Offline rule " + thingX.uid);
	}
}

There is an excellent scheduling system that you can install that gives you a seven day schedule with each 24 hours divided into a user selectable number of periods. You can find it here.

Maybe you could use that together with some items and maybe some small rules that use the expire binding so that they stay on for five, ten, fifteen, etc seconds and then use the timeline picker to select the five second item at 9am, then 15 second item at 1pm, etc.

1 Like

One use I found is to create a rule with a custom set of triggers. For example, rather than creating a Group and using a Member of trigger, find all the Items that match a given criteria (e.g. tag, name, Semantic tag, etc.) and create the triggers that way. It can lesson the amount of configuration from the end users sometimes.

However, I’ve found that the rules created in this way in the UI don’t end up in the Registry in the same way and they are hard to interact with after they are created (e.g. you can’t delete and recreate the rule later). I’ve never looked into why and it’s a real niche need so I never pushed for help from anyone to figure out why and whether there’s a fix.

I fully agree. It is more of a solution is search of a problem thing. This problem of multiple shades all needing their own rule is the first use case I saw that could be called legitimite. But yeah it remains niche, but if it helps xgon, that is good.