Freezer door open alarm

Posted by

We all need to minimize energy consumption these days, so what you really don’t want is for the freezer door to be not completely closed, accidentally, wasting a lot of electrical energy.

Possible solutions

There are several published projects where people have installed a sensor on the freezer door, similar to the sensors you might have on the doors and windows in your house. This looks like it might solve the problem, but I didn’t like it, because
a) I didn’t want to install anything on the freezer door, cables hanging out, etc., and
b) a few times, I had noticed that the freezer door was almost closed, leaving a gap of < 1 cm, which would probably be classified as “closed” by a door sensor, but still wastes energy.

My solution: directly detect the condition to be avoided, i.e. increased energy consumption of the freezer. I plugged the freezer into mains via a WiFi-controlled power switch with power measurement capability. I used a Gosund SP1, flashed with Tasmota firmware, using the excellent Tuya-Convert tool.

The WiFi power meter reports power usage at regular intervals, via MQTT, and my plan was to just write an OpenHAB rule that would trigger an alarm if the power consumption exceeds a configurable threshold, indicating that the freezer needs to work hard, because the door has been left open.

What to measure?

Instantaneous power consumption is not useful. The freezer, at least our model, a Liebherr GP 1356, doesn’t consume power continuously, instead it appears to have a simple control circuit that switches on the cooling engine when the temperature gets too high, and switches the engine off when the temperature is low enough again. While the engine is running, it consumes about 60W; with the engine off, it consumes <5W.

Averaging power consumption, e.g. using the averageSince method of OpenHAB items in the rules DSL, and detecting when the average crosses a threshold, also isn’t ideal. In order to get a reasonably smooth curve, one would have to average over 3 hours, and I don’t want the freezer door to be open for hours before I get an alarm.

Duty cycle, i.e. the percentage of time the freezer cooling engine is on, works better. By looking at power consumption over time, I noticed that the freezer turns on the cooling engine at more or less regular intervals, but the duration of each “engine on” event increases as it needs to cope with an open door. So the rule detects when the freezer turns on and when it turns off, and divides the time form on to off by the time from on to next on. It raises an alarm when the duty cycle goes above a certain threshold. The details of the implementation are described below. This works well, as shown in the following chart.

Instantaneous energy consumption (filled area), energy consumption averaged over 3 hours (lines labeled “Avg”), and the calculated duty cycle (lines labeled “DC”), for two freezers and a refrigerator.

In the example shown above, the door of the main freezer was not completely closed, all night, which I didn’t notice until mid-morning. Obviously, this was before I had implemented an alarm signal. The averaged energy consumption (medium blue line, “Freezer Avg”) rises slowly, over several hours, and drops even more slowly after I closed the door in the morning. The calculated duty cycle (light blue line, “Freezer DC”) rises and falls much more quickly, making this a better candidate for triggering an alarm.

Implementation

The items definitions and rules are designed to be applicable for monitoring multiple appliances.

OpenHAB groups

Fist, we define a few groups that will later be used to define common behavior.

Group gIniOff   "turn off at system start"
Group gIniZero  "set to 0 at system start"
Group gPWM      "Group: PWM signal"
Group hPWM      "Group: PWM helper"
Group gpN       "Group: never persist"

OpenHAB items

For each appliance, there is one item that receives the energy reading reported by the Wifi power meter over MQTT, and several internal items used in the calculations. In this example, the MQTT topic is tele/gosund-D/SENSOR, the message goes to item Freezer_Power.

The names of all related items start with the same text, in this case Freezer_, following the design pattern of “groups in rules” described in the OpenHAB community forum.

Number Freezer_Power (gpU,gPWM) 
    {mqtt="<[mosquitto:tele/gosund-D/SENSOR:state:JSONPATH($.ENERGY.Power)]"}
Switch Freezer_State      (gpN,hPWM,gIniOff)
Number Freezer_TimeOn     (gpN,hPWM)
Number Freezer_TimeOff    (gpN,hPWM)
Number Freezer_DutyCycle  (hPWM,gIniZero)
Switch Freezer_Alarm      <siren>   (gpU,gIniOff)

OpenHAB rules

I have a generic rule that applies to more than this feature, to reset values at startup.

rule "system start" 
when 
   System started
then
   gIniOff.members.forEach[m | postUpdate(m,OFF)]
   gIniZero.members.forEach[m | postUpdate(m,0)]
end

Here is the rule for calculating duty cycle, for each appliance

rule "GENERIC: PWM threshold"
when
    Member of gPWM received update
then 
    // ..... collect all the items we need, and make sure they exist

    val nameItem = triggeringItem.name
    val nameTimeOn   = nameItem.replace("_Power","_TimeOn")
    val nameTimeOff  = nameItem.replace("_Power","_TimeOff") 
    val nameState    = nameItem.replace("_Power","_State")
    val nameDutyCycle= nameItem.replace("_Power","_DutyCycle")

    val itemTimeOn   = hPWM.members.findFirst[ t | t.name == nameTimeOn]
    val itemTimeOff  = hPWM.members.findFirst[ t | t.name == nameTimeOff]
    val itemState    = hPWM.members.findFirst[ t | t.name == nameState]
    val itemDutyCycle= hPWM.members.findFirst[ t | t.name == nameDutyCycle]

    if (itemTimeOn === null || 
        itemTimeOff === null || 
        itemDutyCycle === null || 
        itemState === null) return   

    // determine if device is now on or off, update xxx_State item if necessary

    val Threshold = 10
    val newV = (triggeringItem.state as Number).floatValue
    logDebug("freezer.rules", "{} is now {}", nameItem , newV)

    val oldOnOff = itemState.state
    var OnOffType newOnOff 
    if (newV > Threshold) {
        newOnOff = ON
    } else {
        newOnOff = OFF
    }
    if (newOnOff != oldOnOff || oldOnOff==NULL) {
        itemState.postUpdate(newOnOff)
        logDebug("freezer.rules", "updated {} to {}",nameState,newOnOff)
    }

    // detect rising and falling edge, and calculate duty cycle on rising edge

    val timeNow = triggeringItem.lastUpdate.millis

    if (newOnOff==ON && oldOnOff==OFF) {            
        // ----- rising edge
        if ((itemTimeOn.state !== NULL) && (itemTimeOff.state !== NULL)) {
            val timeOn = (itemTimeOn.state as Number).floatValue
            val timeOff = (itemTimeOff.state as Number).floatValue
            if ((timeOff > timeOn) && (timeNow > timeOff)) {
                val dc = 100 * (timeOff - timeOn)/(timeNow - timeOn)
                itemDutyCycle.postUpdate(dc)
                logInfo("freezer.rules", "{} duty cycle: {}", nameItem, dc)
            }
        }
        logDebug("freezer.rules", "{} rising edge, tOn={}", nameItem, timeNow as Number)
        itemTimeOn.postUpdate(timeNow)
    } else if (newOnOff==OFF && oldOnOff==ON) {     
        // ----- falling edge
        logDebug("freezer.rules", "{} falling edge, tOff={}", nameItem, timeNow as Number)
        itemTimeOff.postUpdate(timeNow)
    }
end 

And finally, a rule to set an alarm item when the duty cycle rises above a configurable threshold, indicating that the freezer door has been left open. This one is specific for one particular appliance, but it could also be rewritten in a more generic way.

val FreezerMaxDC = 60  // if we have > 60% duty cycle, something is wrong

rule "ALARM: Freezer door open"
when 
    Item Freezer_DutyCycle received update 
then 
    if (Freezer_DutyCycle.state as Number >= FreezerMaxDC) {
        if (Freezer_Alarm.state != ON)
            Freezer_Alarm.sendCommand(ON)
    } else {
        if (Freezer_Alarm.state != OFF)
            Freezer_Alarm.sendCommand(OFF)
    }

end

In addition (now shown), I have a UI element that shows the state of the Freezer_Alarm item, and allows me turn it off manually. Furthermore, there is a rule triggered when the Freezer_Alarm item changes, which turns an audible alarm on of off.

OpenHAB persistence

One last tweak: I didn’t see a need to have every value of every internal item tracked in the round-robin database, so I assigned those items to the gpN (group, persistence, never) group defined above, and then configured the persistence setting for that group, in the rrd4j.persist configuration file.

Strategies {
 everyMinute : "0 * * * * ?"
 everyHour   : "0 0 * * * ?"
 everyDay    : "0 0 0 * * ?"
 almostNever : "0 0 0 1 1 ?"
 default = everyChange
}

Items {
 // persist values only when updated
 gpU* : strategy = everyUpdate
 // persistence not important
 gpN* : strategy = almostNever
}

Context

This is part of my home automation setup, described in my blog here.

Leave a Reply