Yet another Heating Setup

Cool setup. How does that schedule component (the days of the week thing) translate to an item and/or times?

I’m using helper string item Heating_AutoState which is being populated by timepicker component with values Night, Away or Heating
and in rules, based on which states it is I’m assigning desired temperature for comparison with actual measured temperature

What to the values of Heating_AutoState look like? So if it’s a black area of the schedule the component set s the value to “Night” and in a green period it’s set to “Heating”?

My heating has a lot more rules, yours is nice and simple…but mine is set with one sort of automatic period that ignores if it’s the weekend etc…and I’d like to add that.

it’s String, so “Night” is being populated to the Heating_AutoState when it’s in the dark area, “Heating” when in green etc.

It’s customizable, you can use 0 1 2 if you like… Rules have to be changed accordingly then

if(Heating_AutoState.state == "Heating"){
// do stuff
}

Hmm seems I now have a weekend project to put in the timeline picker…

NOTE: some of these suggestions are mutually exclusive.

  • You could benefit from the trinary operator.

  • Avoid the use of primitives unless absolutely necessary. Use of primitives is known to greatly extend the amount of time it takes .rules files to parse on an RPi. Just case Item states to Numbers and you can safely do all the necessary operations (comparisons, addition, etc) without problem.

    val actualTemp = (if(Heating_Style.state == ON) HeatingHouseMin.state else HeatingHouseTemp.state) as Number
  • This is more of a style thing, but I find the code easier to follow and shorter if you initialize your variables to a reasonable default to start and then your conditions are checking whether the default values need to be changed. Often you can eliminate 30% or more of if/else or switch cases by doing this. Using switch statements can also help with code clarity.
    // Initialized to summer values
    var targetTemp = 17
    var lowerTemp = 15
...
    // No longer require the summer else case
  • If you assign a value to a variable when you create it instead of null, you do not and should not supply the Type. as with the use of primitives, over specifying the Type is known to extend .rules file parsing by minutes.

  • Look at Design Pattern: How to Structure a Rule

  • Name your Heating_Temp Items the same as your modes and you can use Design Pattern: Associated Items to get the right Temp Item.

import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "Heating logic"
when
    Item HeatingHouseTemp changed or
    Item Heating_Temp_Manual changed or
    Item Heating_Mode changed or
    Item Heating_Plan changed
then
    // 1. See if you need to run the Rule at all
    // You should add some checks for UNDEF and NULL for all your Items used in this Rule here
    // and return; if so. Exercise left to the student.

    // 2. Calculate what needs to be done.
    var actualTemp = (if(Heating_Style.state == ON) HeatingHouseMin.state else HeatingHouseTemp.state) as Number

    var baseTemp = 0
    var upper = 0
    var lower = 0

    if(Heating_Mode.state == ON){
        baseTemp = Heating_Temp_Manual.state as Number
        if(vTimeOfDay.state.toString == "BED") { upper = -1.5; lower = -3.5 }
        else                                   { upper = 0.3; lower = -0.5 }
    }

    // auto mode
    else {
        switch(Heating_Plan.state.toString){
            case "WINTER": {
                baseTemp = ScriptServiceUtil.getItemRegistry.getItem("Heating_Temp_"+Heating_AutoState.state.toString).state as Number
                if(Heating_AutoState.state.toString == "Heating") { upper = 0.3; lower = -0.5 }
                else                                              { upper = 0.2; lower = -0.7 }
            }
            case "AWAY": { baseTemp = 15; upper = 0; lower = -1.5 }
            default:     { baseTemp = 17; upper = 0; lower = -2 }
        }
    }

    val targetTemp = baseTemp + upper
    val lowerTemp = baseTemp + lower

    // 3. Do it
    Heating_Visibility.sendCommand(Heating_Mode.state) // consider just using Heating_Mode to control visibility on your sitemap

    // temp is higher, switch OFF
    var newState = "STAY"
    
    if(actualTemp > targetTemp) newState = "OFF"
    else if(actualTemp <= lowerTemp) newState = "ON"

    if(newState != "STAY && Heating.state.toString != newState)
        Heating.sendCommand(newState)
        logInfo("Heating", "Current temp is " + actualTemp + " and target temp is " + targetTemp + " so switching Heating to " + newState)
    }
end

thank you
I still find OH rules bit odd from other languages I know better even while am working with them for some time already so your inputs are very appreciated!

Iam not running production OH on rpi anymore so I dont have concerns about performance, but your code looks even cleaner so I will adjust it.

Thanks

It would be a good idea to consider migrating to JSR223 where you can code Rules in Python, JavaScript or Groovy. At some point, hopefully OH 3, Rules DSL will be deprecated or at the very least no longer be the default.

The Python version of this Rule would look something like (note, I’m just typing this in, it’s probably riddled with errors, also, there might be more Pythonic ways to do it):

from core.rules import rule
from core.triggers import when

@rule("Heating logic")
@when("Item HeatingHouseTemp changed")
@when("Item Heating_Temp_Manual changed")
@when("Item Heating_Mode changed")
@when("Item Heating_Plan changed")
def heating(event):
    # 1. See if we need to run the Rule at all
    for i in ["HeatingHouseTemp", "Heating_Temp_Manual", "Heating_Mode", "Heating_Plan"]:
        if isinstance(items[i], UnDefType):
            heating.log.error("{} is {}, cannot process heating rule!".format(i, items[i]))

    # 2. Calculate what needs to be done.
    actualTemp = items["HeatingHouseMin"] if items["Heating_Style"] == ON else items["HeatingHouseTemp"]

    baseTemp = 0
    upper = 0
    lower = 0

    if items["Heating_Mode"] == ON:
        baseTemp = items["Heating_Temp_Manual"]
        if items["vTimeOfDay"] == StringType("BED"):
            upper = -1.5
            lower = -3.5
        else:
            upper = 0.3
            lower = -0.5

    # auto mode
    else:
        if items["Heating_Plan"] == StringType("WINTER"):
            baseTemp = items["Heating_Temp_{}".format(items["Heating_AutoState"])]
            if items["Heating_AutoState"] == StringType("Heating"):
                upper = 0.3
                lower = -0.5
            else:
                upper = 0.2
                lower = -0.7
        elif items["Heating_Plan"] == StringType("AWAY"):
            baseTemp = 15
            upper = 0;
            lower = -1.5
        else:
            baseTemp = 17
            upper = 0
            lower = -2

    targetTemp = baseTemp + upper
    lowerTemp = baseTemp + lower

    # 3. Do it
    events.sendCommand("Heating_Visibility", str(items["Heating_Mode"]))

    newState = "STAY"
    if actualTemp > targetTemp:
        newState = "OFF"
    elif actualTemp <= lowerTemp:
        newState = "ON"

    if(newState != "STAY" and str(items["Heating"]) != newState:
        events.sendCommand(newState)
        heating.log.info("Current temp is {} and target temp is {} so switching Heating to {}"
                                  .format(actualTemp, targetTemp, newState)

The code can be somewhat simplified further by taking advantage of some features built into the helper libraries. There is a hysteresis function awaiting merging you can find here and there is a send_command_if_different function in core.util which can make the last part of the function above look something like


    # 3. Do it
    send_command_if_different("Heating_Visibility", items["Heating_Mode"])
    hyst = hysteresis(targetTemp, actualTemp, lower=lower, high=high)

    if not hyst:
        send_command_if_different("Heating", "ON" if hyst == 1 else "OFF")

It does look quite messy in this, eventho python itself is very readable language this looks not very pretty.
And if OH3 will have DSL depreciated is ok, but till then I don’t know what will be the “default” therefore there is no point to sleve up trousers before ford crossing.

doing things in even more non friendly format when I dont know if that will be the prefered and supported way seems bit unnecessary effort to me.

even documentantion for jsr is bit tricky as you have to edit paths, install exact versions etc.
It’s not yet time for me to go this way :wink:

Default will be the NGRE which will be building Rules in the browser (replacement to PaperUI) using Python, JavaScript, or Groovy in cases where you need to write your own custom Script Actions. NGRE and JSR223 Rules both run on the same execution engine so any work you do in JSR223 right now will be directly portable to the future default.

Scott is working on making it installable like a plugin or it just comes with OH. The fiddly bits with the paths and such is only required for Python. JavaScript comes embedded in OH by default, though he Helper Libraries are more mature in Python.

Great project! About 25 years ago I created a heating system, completely written in Visualbasic for Dos. It works great but the hardware side is becoming a risk: an old PC with Windows 98 and no way to just replace the hardware. So 2 years ago I started searching alternatives. As I like the old application, I decided to port the software on a Raspberry with Dosbox-x and connect the sensors cia a combination of Python scripts, MQTT and OpenHab. As every room has its own sensors, I also decided to replace the old NTC’s with Wemos & DHT22 sensors.
My first priority us to get everything working, but I’m already dreaming about all the possible extensions I could do. The automatic schedule you created looked very familiar: I have exactly the same thing in the Dos application so once I find some tie I’ll take a closer look at it.
Thanks for sharing this project!

glad you like it, thanks.

I’ve added some features into it, now it got “comfort” schedule which maintains bit higher temperature with less hiatus in the evening and as well cleaned and simplied ruleset thanks to @rlkoshak hints.

next thing will be chart from grafana and some statistics about runtime and such, but those are just some cosmetics things.
Overall setup works absolutely great and this is where OH shines. OH learning curve might be sometimes quite pain, but after that, everything seems to be easy and possible :wink:

actual code (yes i don’t care about unset Items here as i do have it solved by mqtt out of the rules for now)

val logName = "Heating"

rule "Heating logic"
when
    Item HeatingHouseTemp changed or
    Member of HeatingTrigger changed
then
    var actualTemp = (if(Heating_Style.state == ON) HeatingHouseMin.state else HeatingHouseTemp.state) as Number

    var Number baseTemp = 0
    var Number upper = 0
    var Number lower = 0

    // manual override
    if(Heating_Mode.state == ON){
        baseTemp = Heating_Temp_Manual.state as Number
        if(vTimeOfDay.state.toString == "BED") { upper = -1.5;  lower = -3.5 } // 22 => 20.5 - 18.5
        else                                   { upper = 0.3;   lower = -0.5 } // 22 => 22.3 - 21.5
    }

    // auto mode
    else {
        switch(Heating_Plan.state.toString){
            case "WINTER": {
                if      (Heating_AutoState.state.toString == "Comfort") { upper = 0.5; lower = 0;    baseTemp = Heating_Temp_Normal.state as Number }   // comfort
                else if (Heating_AutoState.state.toString == "Normal")  { upper = 0.3; lower = -0.5; baseTemp = Heating_Temp_Normal.state as Number }   // normal
                else if (Heating_AutoState.state.toString == "Away")    { upper = 0;   lower = -0.5; baseTemp = Heating_Temp_Away.state as Number }     // away
                else                                                    { upper = 0.2; lower = -0.7; baseTemp = Heating_Temp_Night.state as Number }    // night
            }
            case "AWAY": { baseTemp = 15; upper = 0; lower = -1.5 }
            default:     { baseTemp = 17; upper = 0; lower = -2 }
        }
    }

    val Number targetTemp = baseTemp + upper
    val Number lowerTemp = baseTemp + lower


    // Heating ON/OFF
    var newState = "STAY"
    
    if(actualTemp > targetTemp) newState = "OFF"
    else if(actualTemp <= lowerTemp) newState = "ON"

    if(newState != "STAY" && Heating.state.toString != newState){
        Heating.sendCommand(newState)
        logInfo(logName, "Current temp is " + actualTemp + " and target temp is " + targetTemp + " : switching Heating " + newState)
    }
end

rule "End Manual Heating mode at 2.02 am"
when
    Time cron "0 2 2 ? * * *"
then
    Heating_Mode.sendCommand(OFF)
end

That might not be sufficient. If, for example, the connection between the MQTT broker and OH is lost, I believe the binding will set the Items to UNDEF. Also, when using Rules DSL you cannot guarantee that your Items will have been restoreAtStartup or retained MQTT messages retrieved before the Rules start running. In either case, the Rule will generate errors.

no it wont

as they are triggered and evaluated by values from mqtt I can

might, but fix itself when new data arrives anyway

why I basically dont do that in rules for now is fact I dont like fact there are two states NULL and UNDEFF which factically is the same and because my mqtt works 100% reliable + retained msgs.

And for heating there is no point to have it at all, as you need to work with proper data, not some default initialisation values.

Personally I would limit to 3 temperatures: min, medium & max. It worked out for me during all that time, besides that: I have this for each room seperately. On the other side, I would keep Saturday & Sunday seperated.
Did you also think about monitoring the sensors, for example send an alert when one of the Wemos stops sending updates?

It used to. Maybe there has been a change recently.

How? Let’s say your Rule starts triggering before MQTT has completed connecting and retrieving all the retained messages and restoreOnStartup hasn’t finished. How can you guarantee that every Item used in that Rule isn’t NULL?

I don’t like it so I’ll ignore it doesn’t sound like a good reason to let your code blow up to me. It works 100% reliable for you right now only because you are lucky with the timing in how everything loads on your system. Some day you will make a change or upgrade OH and you won’t be lucky any more.

And they are not “factually” the same thing. NULL means uninitialized, the Item has never received a State since OH started. UNDEF means that the binding or a Rule is asserting that it does not and cannot know what state the Item is in. There are lots of use cases users other than yourself have where the distinction is both useful and meaningful.

Perhaps, or perhaps one wants the rule to fail gracefully instead of exploding with null errors in the logs.

But I guess I don’t know what I’m talking about. It’s not like I’ve been using OH for years and helping people with problems, like the one described above, in all that time.

nobody said that, dont act like child.
my approach here is different for reasons, why thats bothers you so much? chill

i had this scenario as well and i have it still. fourth interval has got different hiatus not the temperature.
On top of that, I dont want each room to have separate temperature because in my house its not needed, simple as that

indeed but it is not part of this rule, i have separate rules for sensors
anyway I have own sketches in there or custom tasmota which handles self issues well enough so OH is just a information processor as they are always allright

I care not because of you. You can make any decisions you want to and do what ever you want and I don’t care.

But I cannot leave misleading or incorrect assertions about how OH works unchallenged because users come across threads like these, make similar or the same choice and end up consuming lots of our time asking for help when it blows up in their face.

You have reasons but the only reason it works for you right now is luck. Other users may not be as lucky.

1 Like

HI !
I see you use “time scheduled”, so, you use in sitemap “transferItem=TransferItem1”
why only TransferItem1 ?
Maybe you can share more information ? (lasted code items, sitemap, rules and “time scheduled rules(config)”.

Thank you.
B.R.

hi,
all relevant items, things and rules are in first post already…

is there something missing?