Help wanted with a for me complex rule

I’m not great at complex rules, but ever since chatGPT I made some progress. Where I had simple rules like ‘when off, change to on’ and a separate rule for ‘when on, change to off’ I started to merge them with some help of chatGPT and they all work. Now I started to ask for more complex things, but I can’t seem to figure out why they don’t work and why I can’t copy paste it properly, even with a passage through VST to check the syntax. They seem correct to me, as the logic seems, well, logic :slight_smile:

Anyway here is the code, and the error from the log.

This is the code from chatGPT, used in openhabian and build 4.0.0

configuration: {}
triggers:
  - id: "1"
    configuration:
      event: PRESSED
      channelUID: nikobus:push-button:ead36cb534:5F8542:Nikobus_Push_Button_10A87E_3_BP35_D_2
    type: core.ChannelEventTrigger
  - id: "2"
    configuration:
      event: PRESSED
      channelUID: nikobus:push-button:ead36cb534:3F8542:Nikobus_Push_Button_10A87E_7_BP35_C_1
    type: core.ChannelEventTrigger
  - id: "3"
    configuration:
      event: PRESSED
      channelUID: nikobus:push-button:ead36cb534:7F8542:Nikobus_Push_Button_10A87E_6_BP35_D_1
    type: core.ChannelEventTrigger
  - id: "4"
    configuration:
      event: PRESSED
      channelUID: nikobus:push-button:ead36cb534:BF8542:Nikobus_Push_Button_10A87E_7_BP35_A_1
    type: core.ChannelEventTrigger
  - id: "5"
    configuration:
      event: PRESSED
      channelUID: nikobus:push-button:ead36cb534:FF8542:Nikobus_Push_Button_10A87E_8_BP35_B_1
    type: core.ChannelEventTrigger
conditions: []
actions:
  - inputs: {}
    id: "7"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: >-
        // Define item names
        val itemName = triggeringItem.name
        val itemMappings = [
            "ID1": "MQTTSonoff4chan2_Fontein",
            "ID2": "MQTTSonoff4chan1_Tuinpad",
            "ID3": "MQTTSonoff4chan1_DownlightTuinhuis",
            "ID4": "MQTTSonoff4chan1_UplightBomen",
            "ID5": "MQTTSonoff4chan1_UplightTuinhuis"
        ]

        // Check if the button was pressed
        if (itemMappings.containsKey(itemName)) {
            val itemToSwitch = itemMappings.get(itemName)
            val currentState = items[itemToSwitch].state.toString()

            if (currentState == "ON") {
                sendCommand(itemToSwitch, "OFF")
            } else if (currentState == "OFF") {
                sendCommand(itemToSwitch, "ON")
            }
        }
    type: script.ScriptAction

This is what openhab interface makes of after saving (the rest remains the same, I did a side by side comparison in VSC.

actions:
  - inputs: {}
    id: "7"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: >-
        // Define item names val itemName = triggeringItem.name val itemMappings
        = [
            "ID1": "MQTTSonoff4chan2_Fontein",
            "ID2": "MQTTSonoff4chan1_Tuinpad",
            "ID3": "MQTTSonoff4chan1_DownlightTuinhuis",
            "ID4": "MQTTSonoff4chan1_UplightBomen",
            "ID5": "MQTTSonoff4chan1_UplightTuinhuis"
        ]

        // Check if the button was pressed if (itemMappings.containsKey(itemName)) {
            val itemToSwitch = itemMappings.get(itemName)
            val currentState = items[itemToSwitch].state.toString()

            if (currentState == "ON") {
                sendCommand(itemToSwitch, "OFF")
            } else if (currentState == "OFF") {
                sendCommand(itemToSwitch, "ON")
            }
        }
    type: script.ScriptAction

and this is the error in the log…

2023-09-07 20:23:02.289 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID '5608ee7d5e' failed: // Define item names val itemName = triggeringItem.name val itemMappings = [
    "ID1": "MQTTSonoff4chan2_Fontein",
    "ID2": "MQTTSonoff4chan1_Tuinpad",
    "ID3": "MQTTSonoff4chan1_DownlightTuinhuis",
    "ID4": "MQTTSonoff4chan1_UplightBomen",
    "ID5": "MQTTSonoff4chan1_UplightTuinhuis"
]
// Check if the button was pressed if (itemMappings.containsKey(itemName)) {
    val itemToSwitch = itemMappings.get(itemName)
    val currentState = items[itemToSwitch].state.toString()
    if (currentState == "ON") {
        sendCommand(itemToSwitch, "OFF")
    } else if (currentState == "OFF") {
        sendCommand(itemToSwitch, "ON")
    }
}
2023-09-07 20:23:02.300 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'b435a99536' failed: val itemName = triggeringItem.name val itemToSwitch1 = "MQTTSonoff4chan2_Fontein" val itemToSwitch2 = "MQTTSonoff4chan1_Tuinpad" val itemToSwitch3 = "MQTTSonoff4chan1_DownlightTuinhuis" val itemToSwitch4 = "MQTTSonoff4chan1_UplightBomen" val itemToSwitch5 = "MQTTSonoff4chan1_UplightTuinhuis"
// Check which button was pressed if (itemName == "ID1") {
    val currentState = items[itemToSwitch1].state.toString()
    if (currentState == "ON") {
        sendCommand(itemToSwitch1, "OFF")
    } else if (currentState == "OFF") {
        sendCommand(itemToSwitch1, "ON")
    }
} else if (itemName == "ID2") {
    val currentState = items[itemToSwitch2].state.toString()
    if (currentState == "ON") {
        sendCommand(itemToSwitch2, "OFF")
    } else if (currentState == "OFF") {
        sendCommand(itemToSwitch2, "ON")
    }
} else if (itemName == "ID3") {
    val currentState = items[itemToSwitch3].state.toString()
    if (currentState == "ON") {
        sendCommand(itemToSwitch3, "OFF")
    } else if (currentState == "OFF") {
        sendCommand(itemToSwitch2, "ON")
    }
}

I have no idea

  • why openhab does not save the rule correctly
  • why the rule does not work.

I’m not a big fan of ChatGPT and crap like this makes me dislike it even more. Frankly, if you can’t code it yourself, you can’t use ChatGPT to code it for you because, at least when it comes to OH rules, it almost never gets it right and you have to have the skills to have coded it in the first place to fix what it got wrong.

In this case, under script: >- you need two newlines to indicate a single newline.

Got turned into

Which is obviously nonsense from a coding perspective.

If you cannot code this rule yourself, I cannot recommend trying to get ChatGPT to do it for you.

That’s the syntax error. As far as the overall logic is concerned this is never going to work. All of your triggers are Channel Event Triggers. There are no Item triggers. So even without the syntax errors above it’s going to fail on the very first line:

val itemName = triggeringItem.name

There is no triggeringItem to get the name from.

The whole itemMappings has no relationship to anything. Those Items are not referenced anywhere else and "ID": "MQTTblahblahblah" isn’t a thing you can put into a List/Array. It kind of looks like ChatGPT was trying to use a List and a Map at the same time, event though a List makes no sense what-so-ever in this context and it got the syntax for a Map completely wrong.

After that, the code is kind of redundant but at least it might work as expected.

There is no triggeringItem as the rule is triggered by a channel, not an item. You need to use

triggeringChannel

Maybe it is also easier to work with if…then…else block to control which item to send a command to.

Hey @Oliver2 @rlkoshak thanks for the swift replies.

After my first initial successes with chatGPT, these are clearly to complex for me.

My aim was to simplify my setup with 60+ rules to some easier to maintain complex rules, but as I never got to a programming course, I’ll stick with what I do can and understand. And which I can get to work.

So thanks!

If you don’t understand the code and how it works, you can’t maintain them in the first place. You are far better off sticking to what you understand, or even better, dive in and try to learn how to write the rule yourself.

For writing rules, even more complex ones, it is not required that you attended programming courses.
Once you have done your first easy rule, you can add more and more features. There are so many code examples here which will help you add some extra stuff.
And there is this forum which is here to help.

Just trying to prove a point I still have a rule that works just fine under openhab 4 and this was started back in the days of openhab 2.5 but I don’t dare to change anything and keep updating as necessary. I know it doesn’t make any sense but hey I understand it and that counts.

And believe me it started small with just turning the charger on if the inverter was injecting the grid.

rule "lets charge the car based on the sun for evse1"

when
    Item Grid_load changed or
    Item Stop_charging_evse_1 changed or
    Item Status_chargin_evse1 changed or
    Item Status_error_chargin_evse1 changed or
    Item Force_charging_evse_1 changed or
    Item Battery_load changed or
    Item gss_modbus_measure_car1_amps changed
then
    var charge_level_get = ((Solar_load.getStateAs(Number)) / 230 / 3)
    var buffer = 2
    var charge_level = Math::round(((charge_level_get) - buffer).intValue)

    // Check for null values and set default values
    if (Force_charging_evse_1.state == null) {
        Force_charging_evse_1.sendCommand(OFF)
    }

    if (Stop_charging_evse_1.state == null) {
        Stop_charging_evse_1.sendCommand(OFF)
    }

    if (Night_charging_evse_1.state == null) {
        Night_charging_evse_1.sendCommand(0)
    }

    if (Sunday_charging_evse_1.state == null) {
        Sunday_charging_evse_1.sendCommand(0)
    }

    if (charge_level >= 6 && Charging_evse_1_current.state != charge_level && Status_chargin_evse1.state == "67" && Force_charging_evse_1.state == OFF && Enable_station_evse_1.state == ON && Night_charging_evse_1.state == 0 && Sunday_charging_evse_1.state == 0) {
        logInfo("evse1" , "Charging evse1 at final " + charge_level)
        Charging_evse_1_current.sendCommand(charge_level)
        
    }

    if (Status_chargin_evse1.state == "66" && Status_error_chargin_evse1.state == "0" && Force_charging_evse_1.state == OFF && Enable_station_evse_1.state == ON && Night_charging_evse_1.state == 0 && Sunday_charging_evse_1.state == 0) {
        if (Grid_load.averageSince(now.minusMinutes(5)) > 3000 | "W") {
            if (Enable_charging_evse_1.state == OFF) {
                Enable_charging_evse_1.sendCommand(ON)
                logInfo("evse1" , "Charging evse1 started value is " + Grid_load.averageSince(now.minusMinutes(5)))
                Thread::sleep(3000)
                if (Enable_charging_evse_1.state == ON) {
                    sendBroadcastNotification("Solar charging ev 1 started", "energy")
                    
                }
            }
        }
    

        else if ((Grid_load.averageSince(now.minusMinutes(5)) < -1000 | "W" || gss_modbus_measure_car1_amps.state == 0) && Force_charging_evse_1.state == OFF && Status_chargin_evse1.state == "67" && Status_error_chargin_evse1.state == "0" && Night_charging_evse_1.state == 0 && Sunday_charging_evse_1.state == 0) {
            if (Grid_load.averageSince(now.minusMinutes(5)) < -1000 | "W") {
                if (Enable_charging_evse_1.state == ON) {
                    Enable_charging_evse_1.sendCommand(OFF)
                    logInfo("evse1" , "Charging evse1 stopped value of grid is " + Grid_load.averageSince(now.minusMinutes(10)))
                    Thread::sleep(3000)
                    if (Enable_charging_evse_1.state == OFF) {
                        
                        sendBroadcastNotification("Solar charging ev 1 stopped because value of grid is: " + Grid_load.averageSince(now.minusMinutes(10)), "energy")
                        
                    }
                }
            }
        }
        else if (gss_modbus_measure_car1_amps.state == 0 && Force_charging_evse_1.state == OFF && Status_chargin_evse1.state == "67" && Status_error_chargin_evse1.state == "0") {
            if (Enable_charging_evse_1.state == ON) {
                Enable_charging_evse_1.sendCommand(OFF)
                logInfo("evse1" , "Charging evse1 stopped value of amps is " + gss_modbus_measure_car1_amps.state)
                Thread::sleep(3000)
                if (Enable_charging_evse_1.state == OFF) {
                    
                    sendBroadcastNotification("Solar charging ev 1 stopped because the amps drawn are: " + gss_modbus_measure_car1_amps.state, "energy")
                    
                }
            }
        }
    
    }

    if (Force_charging_evse_1.state == ON && Status_chargin_evse1.state == "66" && Status_error_chargin_evse1.state == "0") {
        if (Enable_charging_evse_1.state == OFF) {
            Enable_charging_evse_1.sendCommand(ON)
            if (Charging_evse_1_current.state != 16) {
                Charging_evse_1_current.sendCommand(16)
            }
            if (Enable_station_evse_1.state == OFF) {
                Enable_station_evse_1.sendCommand(ON)
            }
            logInfo("evse1" , "Charging evse1 forced fast started")
            Thread::sleep(40000)
            if (Enable_charging_evse_1.state == ON && Charging_evse_1_current.state == 16 && Enable_station_evse_1.state == ON) {
                
                sendBroadcastNotification("Force charging started for ev 1", "energy")
                
            }
        }
    }

    if (Stop_charging_evse_1.state == ON && Status_chargin_evse1.state == "67") {
        if (Force_charging_evse1.state == ON) {
            Force_charging_evse1.postUpdate(OFF)
            if (Enable_charging_evse_1.state == ON) {
                Enable_charging_evse_1.sendCommand(OFF)
            }
            if (Charging_evse_1_current.state != 6) {
                Charging_evse_1_current.sendCommand(6)
            }
            if (Stop_charging_evse_1.state == ON) {
                Stop_charging_evse_1.postUpdate(OFF)
            }
            logInfo("evse1" , "Charging evse1 forced stop")
            Thread::sleep(40000)
            if (Enable_charging_evse_1.state == OFF && Charging_evse_1_current.state == 0 && Stop_charging_evse_1.state == OFF) {
                
                sendBroadcastNotification("Force charging ev 1 stopped", "energy")
                
            }
        }
    }

    if (now.isAfter(now.withHour(16).withMinute(0).withSecond(0)) && Status_chargin_evse1.state == "66" && Status_error_chargin_evse1.state == "0" && Force_charging_evse_1.state == OFF) {
        if (now.getDayOfWeek.getValue == 1 || now.getDayOfWeek.getValue == 2 || now.getDayOfWeek.getValue == 3 || now.getDayOfWeek.getValue == 4) {
            if (Enable_charging_evse_1.state == OFF) {
                Enable_charging_evse_1.sendCommand(ON)
                if (Charging_evse_1_current.state != 6) {
                    Charging_evse_1_current.sendCommand(6)
                }
                if (Enable_station_evse_1.state == OFF) {
                    Enable_station_evse_1.sendCommand(ON)
                }
                Night_charging_evse_1.sendCommand(1)
                logInfo("evse1" , "Charging evse1 started at slowly evening mode for work")
                Thread::sleep(40000)
                if (Enable_charging_evse_1.state == ON && Charging_evse_1_current.state == 6 && Enable_station_evse_1.state == ON && Night_charging_evse_1.state == 1) {
                    
                    sendBroadcastNotification("The charging ev 1 started for next day work")
                    
                }
            }
        }
    }

    if (((now.isAfter(now.withHour(16).withMinute(0).withSecond(0)) && now.getDayOfWeek.getValue == 7) || (now.isAfter(now.withHour(1).withMinute(0).withSecond(0)) && now.getDayOfWeek.getValue == 1)) && Status_chargin_evse1.state == "66" && Status_error_chargin_evse1.state == "0" && Force_charging_evse_1.state == OFF) {
        if (Enable_charging_evse_1.state == OFF) {
            Enable_charging_evse_1.sendCommand(ON)
            if (Charging_evse_1_current.state != 16) {
                Charging_evse_1_current.sendCommand(16)
            }
            if (Enable_station_evse_1.state == OFF) {
                Enable_station_evse_1.sendCommand(ON)
            }
            Sunday_charging_evse_1.sendCommand(1)
            logInfo("evse1" , "Charging evse1 started at fast evening mode for Monday work")
            Thread::sleep(40000)
            if (Enable_charging_evse_1.state == ON && Charging_evse_1_current.state == 6 && Enable_station_evse_1.state == ON && Sunday_charging_evse_1.state == 1) {
                
                sendBroadcastNotification("The charging ev 1 started for monday day work")
                
            }
        }
    }

    if (Status_error_chargin_evse1.state != "0") {
        if (Enable_charging_evse_1.state == ON) {
            Enable_charging_evse_1.sendCommand(OFF)
            logInfo("evse1" , "Something is wrong, stop the charging evse1")
            sendBroadcastNotification("There is an error with ev 1 charger. Now its stopped.", "energy", "severe")
            
        }
    }

    if (Status_chargin_evse1.state == "65") {
        if (Enable_charging_evse_1.state == ON) {
            Enable_charging_evse_1.sendCommand(OFF)
            if (Charging_evse_1_current.state != 0) {
                Charging_evse_1_current.sendCommand(0)
            }
            if (Force_charging_evse_1.state == ON) {
                Force_charging_evse_1.sendCommand(OFF)
            }
            if (Stop_charging_evse_1.state == ON) {
                Stop_charging_evse_1.sendCommand(OFF)
            }
            if (Sunday_charging_evse_1.state == 1) {
                Sunday_charging_evse_1.sendCommand(0)
            }
            if (Night_charging_evse_1.state == 1) {
                Night_charging_evse_1.sendCommand(0)
            }
            logInfo("evse1" , "Charging stopped evse1 because the cable is disconnected")
            Thread::sleep(40000)
            if (Enable_charging_evse_1.state == OFF && Charging_evse_1_current.state == 0 && Force_charging_evse_1.state == OFF && Stop_charging_evse_1.state == OFF && Sunday_charging_evse_1.state == 0 && Night_charging_evse_1.state == 0) {
                
                sendBroadcastNotification("Cable disconnected ev 1 charging stopped", "energy")
            }
        }
    }
    
    // Added logic to pause charging when Battery_load goes negative
    val lastPositiveTime = ScriptExecution.createTimer(now.plusMinutes(10), [|
        if (Battery_load.state <= -800 | "W" && Night_charging_evse_1.state == 0 && Force_charging_evse_1.state == OFF) {
            if (Enable_charging_evse_1.state == ON) {
                Enable_charging_evse_1.sendCommand(OFF)
                logInfo("evse1" , "Charging paused due to negative Battery_load")
                Thread::sleep(5000)
                if (Enable_charging_evse_1.state == OFF && Charging_evse_1_current.state == "66") {
                    
                    sendBroadcastNotification("Charging pause due to negative battery level.", "energy")
                }
            }
        }
        ])
        if (Battery_load.state > 0 | "W" && Night_charging_evse_1.state == 0 && Sunday_charging_evse_1.state == 0) {
                lastPositiveTime.reschedule(now.plusMinutes(10))
        }
    
end

And also it’s a prime example of what you get to of you try to combine to many things into one rule :stuck_out_tongue_winking_eye:

That’s the most important thing that counts.

That’s indeed what I try to do, but it seems that the things that I want to achieve are always just a little bit more different than just adapting basic code where I can see the logic.

Nevertheless, I succeeded in my first goal, in stead of having two rules for turning on an off, I have one now.

But I still do not understand why the copy/paste of openhab paper ui does not work. The first remark of @rlkoshak under script was exactly what I meant (and of course understood, I’m not that bad at all :slight_smile: )

The code gets adapted by openhab itself, not by me, so openhab makes it not working.

I copy/pasted this part from https://opentux.eu/solutions/smart-home/how-to/configuration-home-automation-rules, adapted it in vscode, copied it exactly with the same layout and openhab made it look like the second code. That still is a mystery to me.

actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: |2
        
         if(L111.state == ON){   
         sendCommand(L111, OFF)
         }else{
         sendCommand(L111, ON)
         }
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: >-
        
        if(MQTTSonoff4chan1_UplightBomen.state == ON){ sendCommand(MQTTSonoff4chan1_UplightBomen, OFF) }else{ sendCommand(MQTTSonoff4chan1_UplightBomen, ON) }
    type: script.ScriptAction

To start it’s important to use the right names. PaperUI doesn’t exist in OH 3+. We now have MainUI.

Another thing is you’ve not mentioned it but I assume you are pasting it into the Code tab, right?

We can go into a deep dive and I will if you want. Suffice it to say for now that what you see in the Code tab of the rule is a translation from the actual code that is stored as JSON. In order to translate it a number of modifications are required including replacing the newlines with \n.

As I said above, in the YAML you see in the code tab, if you want a newline between two lines of code, you need to insert two newlines.

Note that there is nothing wrong with having that block of code all on one line of code. It’s syntactically correct and will run just fine.

If you want it to appear on separate lines, you need to insert two newlines for every one. (There is a subtlety here that makes that rule not entirely complete but it’s good enough for our purposes here.)

      script: |2
        
         if(L111.state == ON){

           sendCommand(L111, OFF)

         }else{

           sendCommand(L111, ON)

         }
    type: script.ScriptAction

Right, I meant indeed MainUI :). Assumption is correct.

I know, because they worked. I’ll try this way then, thanks for your help.

I‘d recommend to use the script editor anyway. Instead of clicking on the „code“ tab, scroll down a bit to the „Then“ section and click on the script.
You probably need to clean the script up the first time but then all line breaks, indents, etc will stay.

1 Like