Another Rule Migration to OH3 Issue - An error occurred during the script execution: null

Hi all, now that it is getting cold I have the heater setup and the propane turned on this weekend. Just in time. Having moved to OH3 at the new greenhouse I am moving my rules over and have run into an issue. I have several rules that provide data for the heater. Most are not working.

I get the following error:

 Script execution of rule with UID 'mainheater-2' failed: An error occurred during the script execution: null in mainheater

I don’t understand what UID ‘mainheater-2’ refers to.

I any case, here is the rule that this is referring to. I think. I have multiple rules around the heater functions so one step at a time.

rule "Main Heater time ascending"
when
	Time cron "0 * * * * ?" 
then
    if( MainHeater.state == ON ) {
        var DateTime m =  new DateTime(MainHeater.lastUpdate )
	var diff = now.millis - m.millis;
	MainHeater_On_Asc.postUpdate( diff );
	logInfo("Track Ascending Duration", "MainHeater changed last after  " + diff  )	
    }
    else {
       MainHeater_On_Asc.postUpdate( 0 );
    }
end

rule "Main Heater On time total"
when
	Item MainHeater received command
then
    if( receivedCommand==OFF ) {
        var DateTime m =  new DateTime(MainHeater.lastUpdate )
	var diff = now.millis - m.millis;
	MainHeater_On_Duration.postUpdate( diff );
	logInfo("Track On Duration", "MainHeater on duration was  " + diff  )	
    }
    else {
    	logInfo("Track On Duration", "MainHeater is now on "  )
    	
    }
end

I also have this rule:

var DateTime whenStarted = null
var Timer MainHeater_timer = null

rule "Main Heater Started"
when
    Item MainHeater received command ON
then	
	if (MainHeater_timer === null) {  /// Check equivalent to lock
    	MainHeater_timer = createTimer(now.plusSeconds(45), [ |
            if (MainHeater.state == ON) {
                whenStarted = now // Start Clock
                MainHeater_Failed.postUpdate(OFF)
                MainHeater_Cooling.postUpdate(OFF)
                createTimer(now.plusSeconds(15), [ |
                    //sendMail("myemail@gmail.com", "MainHeater", "Main Heater Started, load at " + Buy_Total_Watts.state + " watts")
                ])
            } else {
                whenStarted = null
                MainHeater_Failed.postUpdate(ON)
                MainHeater_Override.postUpdate(OFF)
                //sendMail("myemail@gmail.com", "Main Heater", "Main Heater Failed to Start!!")
            }
    		MainHeater_timer = null
	    ])
	}
end


rule "Main Heater Stopped"
when
    Item MainHeater received command OFF
then	
    whenStarted = null //Stops clock
    MainHeater_Cooling.postUpdate(ON)
	createTimer(now.plusSeconds(90), [ |
        MainHeater_Cooling.postUpdate(OFF)
	])
end

rule "Check Main Heater and Increment Runtimes"
when
    Time cron "0 0/1 * * * ?"
then	
    // Check if MainHeater failed
    if (MainHeater_Failed.state == ON) {
        //sendMail("myemail@gmail.com", "Main Heater", "Main Heater Failed!!")
    }

    // Increment Runtimes
    if (MainHeater.state == ON && whenStarted !== null) {
        val Number nowMSec = now.millis
        val Number wsMSec = whenStarted.millis
        whenStarted = now	
        val Number timeSinceLastUpdate = nowMSec - wsMSec
        val Number oldVal = if (MainHeater_Runtime_MSec.state == UNDEF || GeneratorRuntimeMSec.state == NULL) 0 else GeneratorRuntimeMSec.state as Number
        val Number totalMSec = oldVal + timeSinceLastUpdate // calculate total runtime
        MainHeater_Runtime_MSec.postUpdate(totalMSec) // post the full runtime
        val Number hours = totalMSec / 3600000 // (1000 / 60 / 60)
        MainHeater_Runtime_Hours.postUpdate(hours)

        // Calculate runtime for today		
        val Number todayOldVal = if(Today_MainHeater_Runtime_MSec.state == UNDEF || Today_MainHeater_Runtime_MSec.state == NULL) 0 else (Today_MainHeater_Runtime_MSec.state as Number).longValue
        val Number todayTotalMSec = todayOldVal + timeSinceLastUpdate // calculate total runtime
        Today_MainHeater_Runtime_MSec.postUpdate(todayTotalMSec) // post the full runtime
        val Number todayHours = todayTotalMSec/1000.0/60.0/60.0
        Today_MainHeater_Runtime_Hours.postUpdate(todayHours)
    }
end

//Timestamp for Main Heater Last Run 

rule "Timestamp for Main Heater Last Run"
when
	Item MainHeater received command ON        // Heater Run
then
	MainHeater_timestamp.postUpdate(new DateTimeType())   //defaults to now
	logInfo("mainheatertime", "Main Heater Timestamp :"+MainHeater_timestamp.state )
       
end

rule "Today Main Heater Runtime Reset"
when
    Time cron "50 59 23 * * ?"
then
    Today_MainHeater_Runtime_MSec.postUpdate(0)
    Today_MainHeater_Runtime_Hours.postUpdate(0)
end

I guess I don’t know which rule that error applies to but assuming the mainheater.rule.

This is a rule that @vzorglub helped with. I have the items and persistence setup.

Any help much appreciated!

Can’t comment on the rules, but if you have a DSL rule file with multiple rules OH3 breaks them into -1,-2,-3 etc. Two is the second rule in the file. You can also validate by using the UI-Setting-Rules and look at the fine print.

I would also add I get a similar error on a Group item on startup, but it is just one-time and everything works afterward.

Bob

The error message isn’t very helpful, but as you have DateTime objects kicking about these are known for breaking changes OH2->OH3. Essentially there is no DateTime anymore, use alternative.

Also beware that OH3 has some persistence by default, and it may not be the one you expected when allowing persistence extensions in rules to default requested service…

Well sugar. Ok, will play around with this. I started through it but seems to be stuck on the millis conversion.

Changed the DateTime to DateTimeType

var DateTimeType m =  new DateTimeType(MainHeater.lastUpdate )

That got rid of the null error.

Then found I needed to change the millis to Milli.

Still error. Found that I needed to add the epoch info…

var diff = now.toInstant().toEpochMilli() - m.Milli;

so changed that but still error:

 Script execution of rule with UID 'mainheater-2' failed: 'Milli' is not a member of 'org.openhab.core.library.types.DateTimeType'; line 22, column 46, length 7 in mainheater

Getting closer but this is certainly a little more complicated.

Current code:

rule "Main Heater time ascending"
when
	Time cron "0 * * * * ?" 
then
    if( MainHeater.state == ON ) {
        var DateTimeType m =  new DateTimeType(MainHeater.lastUpdate )
	var diff = now.toInstant().toEpochMilli() - m.Milli;
	MainHeater_On_Asc.postUpdate( diff );
	logInfo("Track Ascending Duration", "MainHeater changed last after  " + diff  )	
    }
    else {
       MainHeater_On_Asc.postUpdate( 0 );
    }
end

rule "Main Heater On time total"
when
	Item MainHeater received command
then
    if( receivedCommand==OFF ) {
        var DateTimeType m =  new DateTimeType(MainHeater.lastUpdate )
	var diff = now.toInstant().toEpochMilli() - m.Milli;
	MainHeater_On_Duration.postUpdate( diff );
	logInfo("Track On Duration", "MainHeater on duration was  " + diff  )	
    }
    else {
    	logInfo("Track On Duration", "MainHeater is now on "  )
    	
    }
end

I’d go back to first principles, using the already linked guide.
What is rule 1 trying to do?
Get a difference between two date-times in milliseconds.
The date-time A is now. That’s a Java time, we could get now.toInstant.toEpochMilli
The date-time B is MainHeater.lastUpdate. That’s a Java time. Don’t mess about converting it to a DateTimeType (as used for Item states), just use it as it comes with MainHeater.lastUpdate.toInstant.toEpochMilli

1 Like

great, makes sense. I made some changes but still getting the same error.

The date-time A is now . That’s a Java time, we could get now.toInstant.toEpochMilli

I get that and i see that in #7 getting the current time stamp.

so in my code

var diff = now.toInstant.toEpochMilli - m.Milli;

Is the .Milli allowed or should that be .toInstant.toEpochMilli as well?

The date-time B is MainHeater.lastUpdate
Changed to:

var DateTime m =  new DateTime(MainHeater.lastUpdate.toInstant.toEpochMilli )

This one I didn’t see in the doc.

Latest code:

rule "Main Heater time ascending"
when
	Time cron "0 * * * * ?" 
then
    if( MainHeater.state == ON ) {
        var DateTime m =  new DateTime(MainHeater.lastUpdate.toInstant.toEpochMilli )
	var diff = now.toInstant.toEpochMilli - m.Milli;
	MainHeater_On_Asc.postUpdate( diff );
	logInfo("Track Ascending Duration", "MainHeater changed last after  " + diff  )	
    }
    else {
       MainHeater_On_Asc.postUpdate( 0 );
    }
end

rule "Main Heater On time total"
when
	Item MainHeater received command
then
    if( receivedCommand==OFF ) {
        var DateTime m =  new DateTime(MainHeater.lastUpdate.toInstant.toEpochMilli )
	var diff = now.toInstant.toEpochMilli - m.Milli;
	MainHeater_On_Duration.postUpdate( diff );
	logInfo("Track On Duration", "MainHeater on duration was  " + diff  )	
    }
    else {
    	logInfo("Track On Duration", "MainHeater is now on "  )
    	
    }
end

Error:

Script execution of rule with UID 'mainheater-2' failed: An error occurred during the script execution: null in mainheater

No idea, it all depends what sort of object m is as to whether it has any particular method.
Does it work?
No, and it tells you why.

You’re still also still converting things to DateTime

You take lastUpdate (which is a Java zonedDateTime), convert it to milliseconds, then use milliseconds to create a new DateTime.
This is crackers, because the next thing you try to do is get back to the milliseconds of your new DateTime.

var mmm =  MainHeater.lastUpdate.toInstant.toEpochMilli
var diff = now.toInstant.toEpochMilli - mmm

Semi-colons at the ends of lines are generally not required in DSL.

ahh! ok, i got it. i think. that’s what is described in 7a. no DateTime. if i would have just put that in i would have had it. thanks so much for working through that with me. i have some other places i have to get it fixed as well so this will be handy to know.

onward and upward!

thanks!

1 Like

This isn’t a suggestion to change how you are doing things. I just want to mention that one almost never has to mess with raw milliseconds and often it’s much more flexible not to.

There is a Duration class that you can pass two ZonedDateTimes (or most any other representation of date time) and it will calculate the difference. The advantage there is you can do all sorts of meaningful math with it and you can get the duration in any units you might be interested in, not just milliseconds. The toString() will give you HH:MM:SS.mmm format also.

It really does make things much nicer to work with compared to getting down to milliseconds and then needing to do math to get something more meaningful.