Freezer door open alarm

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.

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.

I measure duty cycle, i.e. the percentage of time the freezer cooling engine is on. 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.
image

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.

OpenHAB groups
First, 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 (hPWM,gIniOff) 
Number Freezer_TimeOn (hPWM) 
Number Freezer_TimeOff (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 (not 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.

For an extended version of this post, with more explanations, see my blog.

5 Likes

:+1:
Nice to see this piece of more advanced programming using groups in rules, as layout in Design Pattern: Working with Groups in Rules