Temperature hysteresis and rule triggers

At home I have a set of rules for automating the roller shutters, depending on outside temperature.

The blinds will close automatically if the outside temperature reached a certain threshold. And inversely, the blinds will open again if the temperature drops below that threshold.

The problem is that on some cloudy days the temperature may oscillate around that threshold, resulting in a lot of annoying and unnecessary blind open/close operations in a row.

At the moment I implemented a time guard to prevent these oscillations by blocking inverse operations if they take place within the same 5 minutes. This already resuces the oscillations, but they still happen (like today).

I’d like to improve the rules by factoring in some hysteresis, in order to minimize these transient actions.

Would it suffice to use averageSince() from persistence to achieve this, like in (Jython):

temp_avg = round(
        PersistenceExtensions.averageSince(
            current_temperature_item, DateTime.now().minusMinutes(30)
        ).state.floatValue(),
        2,
    )

Or should I rather implement this through a more elaborate hysteresis approach as in:

  • if the previous transition was “NORMAL_TEMPERATURE → HOT_DAY” then the inverse transition “HOT_DAY → NORMAL_TEMPERATURE” can only take place if the current temperature drops below the hot day temperature (e.g. 25 °C) minus a hysteresis value (e.g., 0.5 °C)
  • if the previous transition was “HOT_DAY → NORMAL_TEMPERATURE” then the inverse transition “NORMAL_TEMPERATURE → HOT_DAY” can only take place if the current temperature raises above the hot day temperature (e.g. 25 °C) plus a hysteresis value (e.g., 0.5 °C)

In that case I’d probably have to use a state machine as explained in: Finished State Machine in Jython

Any recommendations?

I use a similar approach, in which I

  1. use a 30min period of “averagesince”
  2. use my internal temperature
  3. if triggered automatically, I update a DateTime item with the current time

with 1. I get a bit of a fuzzy logic as you tried, I played a bit and 30min seems ok for my purposes.
with 2. I don’t get my actions (shutters in my case) to move down in e.g. winter or in not-so-hot-days, so I use the sun to heat up my house
with 3. I get a “last resort”, if 1. and 2. would trigger, but it’s still hot/lightly cloudy/… so the shutters would go down the next round anyways…

Finally, I do have shutters to the east, which won’t be affected, but go up at around 175° sun azimuth (meaning, the sun isn’t directly visible anymore from that window) and my south-ward shutters go up after the sun is about 260° (and don’t heat up my rooms anymore)…
…and just as I wrote this, I remember now having a weather station, which shows me current UV and lux. Perhaps, I’d throw this one into the mix as well… so that hot cloudy days don’t result in a dark living room… :wink:

…and no, I won’t post my config, it’s tooo messy right now, and one of the reasons I’d like to refacture my rules with next-Gen rules…

Check back later today. After some final cleanup, I will be merging a PR into core.utils of the Jython helper libraries for a hysteresis function.

1 Like

awesome! :+1:

I’m not certain that averageSince really solves the problem well. It will slow down how much the value changes a bit but it won’t necessarily avoid the value bounding over and below your threshold. It will just slow it down. Maybe that’s OK?

Every time I’ve implemented a hysteresis it usually takes a similar format as you describe only the hysteresis buffers are set up such that you don’t really have to care what direction the temp is going.

var threashold = 22
var hysteresis = .5
if items["Temp"].floatValue() > (threshold + hysteresis):
  # close the blinds
else if items["Temp"].floatValue() <= threshold:
  # open the blinds
# else do nothing

That will open the blinds at (threshold + hysteresis) and close them at (threshold). You can do the opposite as well:

var threashold = 22
var hysteresis = .5
if items["Temp"].floatValue() > threshold:
  # close the blinds
else if items["Temp"].floatValue() <= (threshold + hysteresis):
  # open the blinds
# else do nothing

That will close the blinds when the temp gets below the threshold but only open them above the (threshold + hysteresis).

And you can apply a hysteresis on both sides of the threshold if you want to.

NOTE: I just typed the code above in, it’s likely riddled with errors.

As long as your hysteresis is larger than the usual noise you have in the sensor this alone should manage the flapping without needing to pay attention to the direction the temperature is moving which would greatly complicate matters. The larger the hysteresis the more change that is required before an action is taken. You might need to experiment with a value that causes the blinds to behave as you desire.

I just spent the past couple of weeks coding this for HestiaPi so it’s very fresh in my mind.

That’s smart. You basically eliminate the hysteresis interval from the state transitions. This is easy to implement, even with multiple thresholds in a row, as:

# Current temperature
temp_now = get_current_temperature()

# Lowest temperature in the past 30 minutes (used to check for cold day regime)
temp_min_prev_30min = round(
    PersistenceExtensions.minimumSince(
        current_temperature_item, DateTime.now().minusMinutes(30)
    ).state.floatValue(),
    2,
)

if temp_min_prev_30min < TEMP_COLD:
  # do nothing (too cold - avoid breaking a frozen blind)
elif temp_now < TEMP_WARM_DAY - TEMP_HYSTERESIS:
  # Normal day regime (open / close when scheduled)
elif temp_now < TEMP_WARM_DAY + TEMP_HYSTERESIS:
  # Do nothing (temperature hysteresis)
elif temp_now < TEMP_HOT_DAY - TEMP_HYSTERESIS:
  # Warm day regime (close sun-facing blinds, other blinds are open)
elif temp_now < TEMP_HOT_DAY + TEMP_HYSTERESIS:
  # Do nothing (temperature hysteresis)
else:
  # Hot day regime (close all blinds)

Hmmm… this could simplify the business logic.