CalDav usage and manually set events

Hello
I would like to share a solution that I made in my Openhab and at the same time to discuss whether anyone can think of how to improve the solution.
Openhab is synchronized with the calendar. In the calendar I write planned activities such as vacantion, holiday, visit, home alone, etc. Using the CalDav addon - Openhab detects when an event started and ended, and sets the switch for that event. Subsequently, Openhab adapts the operation of the automatic household on the basis of a switch.

For example:
String VacantionEventName “Event: [%s]” { caldavPersonal=“calendar:program type:ACTIVE eventNr:1 value:NAME filter-name:‘Vacantion’”}
Switch Vacantion “Event Vacantion: [%s]”

rule "Change Vacantion"
when
    System started or
	Item VacantionEventName changed    
then
    if (VacantionEventName.state == NULL) {
        // nothing
    } 
    if (VacantionEventName.state == UNDEF) {
        sendCommand(Vacantion, OFF)
    } else
    if (VacantionEventName.state.toString.length > 0) {
        sendCommand(Vacantion, ON)
    } else {
        sendCommand(Vacantion, OFF)
    }
end

This basic setting works nicely. My next request is that I would also like to set up unscheduled activities without setting up a calendar, but with just one click on the screen that the event has occurred / ended. That’s why I made another Item:
Switch VacantionManual “Set event Vacantion: [%s]”

And it extended the previous rule that when sendCommand(Vacantion,OFF/ON), it also executes sendCommand(VacantionManual,OFF/ON) to keep the states synchronized.

Then I added the rule that when I change a manual event, an calendar event will be created or expired according to the circumstances of the event, which will be set by the resulting Vacantion switch.

rule "Change Vacantion Manual"
when
	Item VacantionManual changed    
then
    if (VacantionManual.state == ON) {
        if (VacantionEventName.state == UNDEF) {
            // create Event in calendar
            val results = executeCommandLine("sudo@@/home/openhabian/createEvent.sh@@program@@Vacantion", 15000)
            logInfo("createEvent", results)
        }
    } else {
        if (VacantionEventName.state.toString.length > 0) {
            // end Event in calendar
            val results = executeCommandLine("sudo@@/home/openhabian/endEvent.sh@@program@@Vacantion", 15000)
            logInfo("endEvent", results)
        }
    }
end

The scripts: createEvent.sh and endEvent.sh ensure the creation or termination of the calendar event. I implemented them as a bash shell by calling the CalDav API using the command curl. I have used Baikal as a server.
If I am interested, I can provide them, I am not a Linux programmer, so maybe some things could be written more optimally.

One more caveat: Item VacantionEventName must be saved, as it will result in an unwanted cancellation of a running event during a restart.

First of all, please How to use code fences for your code.

Where possible, always use the method on the Item rather than the Action to sendCommand or postUpdate.

A switch statement would be a little bit cleaner for the first Rule.

    switch(VacantionEventName.state) {
        // case NULL: do nothing
        case VacantionEvent.state.toString.length > 0: Vacantion.sendCommand(ON)
        case VacantionEvent.state.toString == "",
        case UNDEF: Vancantion.sendCommand(OFF)
    }

I like the idea of adding a new entry to the calendar in response to the Manual Item changing as it keeps the calendar up to date. But if you don’t care about that, you could just moderately change your first Rule to include the Manual Item too.

rule "Change Vacantion"
when
    System started or
    Item VacantionEventName changed or
    Item VacantionManual changed
then
    var newState = Vacantion.state

    // Let Manual take precedence over EventName by checking it first
    if(VacantionManual.state == ON)                       newState = ON
    else if(VacantionEventName.state == UNDEF)            newState = OFF
    else if(VacantionEventName.state.toString.length > 0) newState = ON
    else if(VacantionEventName.state.toString == "")      newState = OFF

    Vacantion.sendCommand(newState)
end

You can further make this generic using Design Pattern: Associated Items.

Put all the event Switches into a Group, I’ll call it Events. Put the EventName and Manual Items in another Group, I’ll call it CalendarEvents.

import import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "A member of CalendarEvents changed"
when
    System started or
    Member of CalendarEvents changed
then
    // We will calculate the new states for all the events in the Group
    CalendarEvents.members.filter[ i | i.name.contains("EventName")].forEach[ eventName |
        // Get the corresponding <event> and <event>Manual Items
        val event = ScriptServiceUtil.getItemRegistry.getItem(eventName.replace("EventName", ""))
        val manual = ScriptServiceUtil.getItemRegistry.getItem(eventName.replace("EventName", "Manual")
        var newState = event.state

        // Let Manual take precedence over EventName by checking it first
        if(manual.state == ON)                       newState = ON
        else if(eventName.state == UNDEF)            newState = OFF
        else if(eventName.state.toString.length > 0) newState = ON
        else if(eventName.state.toString == "")      newState = OFF

        event.sendCommand(newState)
    ]

end

It’s just a little more complicated but now to add new Events you want to work with this Rule all you have to do is create the Items following the same naming scheme and add them to the CalendarEvents Group. You don’t have to copy the same code over again to work on a different set of Items.

The way it works is the Rule will trigger at System started or when any member of CalendarEvents changes state. The Rule will grab all of the Items that end in EventName from the CalendarEvents Group and loop over them. The first few lines of the loop gets the main event Item and the manual Item associated withe the eventName Item based on the naming scheme. Then we record the current state of the event Item. Next we have a series of if/else statements to determine whether the event Item needs to be ON or OFF based on the state of eventName (note that newState will be equal to the state event is already in for the case where eventName is NULL). Finally we send the calculated state to the event.

Thank you for your interesting comments.
I just point out that your construction is not identical to what I wrote. Because the resulting state is set to ON just when Manual is ON or Calendar Event exists. It is not possible to manually cancel a scheduled event. And also an unscheduled event will not appear in the calendars.

According to your construction and with the correction of the typo, it could look like this:

rule "A member of CalendarEvents changed"
when
    System started or
    Member of CalendarEvents changed
then
    // We will calculate the new states for all the events in the Group
    CalendarEvents.members.filter[ i | i.name.contains("EventName")].forEach[ eventName |
        // Get the corresponding <event> and <event>Manual Items
        val event = ScriptServiceUtil.getItemRegistry.getItem(eventName.name.toString.replace("EventName", ""))
        val manual = ScriptServiceUtil.getItemRegistry.getItem(eventName.name.toString.replace("EventName", "Manual"))
        switch(eventName.state) {
            // case NULL: do nothing
            case eventName.state.toString == "",
            case UNDEF: {
                event.sendCommand(OFF)
                manual.sendCommand(OFF)
            }
            case eventName.state.toString.length > 0: {
                event.sendCommand(ON)
                manual.sendCommand(ON)
            }
        }
    ]
end

rule "A member of ManualCalendarEvents changed"
when
    System started or
    Member of ManualCalendarEvents changed
then
    // We will calculate the new states for all the events in the Group
    ManualCalendarEvents.members.filter[ i | i.name.contains("Manual")].forEach[ manual |
        // Get the corresponding <event> and <event>Manual Items
        val event = ScriptServiceUtil.getItemRegistry.getItem(manual.name.toString.replace("Manual", ""))
        val eventName = ScriptServiceUtil.getItemRegistry.getItem(manual.name.toString.replace("Manual", "EventName"))

    if (manual.state == ON) {
        if (eventName.state == UNDEF) {
            // create Event
            val results = executeCommandLine("sudo@@/home/openhabian/createEvent.sh@@program@@"+event.name.toString, 15000)
            logInfo("createEvent", results)
        }
    } else {
        if (eventName.state.toString.length > 0) {
            // end Event
            val results = executeCommandLine("sudo@@/home/openhabian/endEvent.sh@@program@@"+event.name.toString, 15000)
            logInfo("endEvent", results)
        }
    }
    ]
end