Chimney Detection (depends on temperature)

I would like to share my rule to detect the status of my chimney
It is more a kind of experimental rule, that you have to customize to your envirement

Why? (Ideas)

  • refuel firewood
  • heating control
  • music/light/etc.

Issues

  • delay of a few minutes
  • Inaccurate ( < 5% incorrect detections - depends on the config and envirement)

Code (ChimneyDetection.rules)

rule "ChimneyDetection"
when
	Time cron "11 */3 * ? * *" // At second :11, every 3 minutes starting at minute :00, of every hour
then
	var int nTimeShort = 4
	var int nTimeLong = 30
	var int nThreshold = 6
	var int nComfortTemperature = 23
	
	var Number nAvgTempShort = GroundFloor_Living_Temperature.averageSince(now.minusMinutes(nTimeShort))
	var Number nAvgTempLong = GroundFloor_Living_Temperature.averageSince(now.minusMinutes(nTimeLong))
	
	if (nAvgTempShort != NULL && nAvgTempLong != NULL)
	{
		var Double dAvgTempShort = nAvgTempShort.doubleValue
		var Double dAvgTempLong = nAvgTempLong.doubleValue
		
		logInfo("Rule", "Da heizt wohl einer, ist der Kamin an? " + ((dAvgTempShort) / dAvgTempLong * 100).toString + "----" + (100 + nThreshold).toString)
			
		
		if ((dAvgTempShort) / dAvgTempLong * 100 >= (100 + nThreshold))
		{
			logInfo("Rule", "Kamin wurde neu angemacht? " + ((dAvgTempShort) / dAvgTempLong * 100).toString + "----" + (100 + nThreshold).toString)
			Chimney.sendCommand(ON) 
		}
		else if ((dAvgTempShort) / dAvgTempLong * 100 >= (100 + (nThreshold/2)))
		{
			logInfo("Rule", "Frisches Holz ist im Kamin" + ((dAvgTempShort) / dAvgTempLong * 100).toString + "----" + (100 + nThreshold/2).toString)
			Chimney.sendCommand(ON)
			ChimneyHeating.sendCommand(ON)
		}
		else if ((dAvgTempShort) / dAvgTempLong * 100 <= (99))
		{
			logInfo("Rule", "Wohnzimmer kühlt weiter ab. " + ((dAvgTempShort) / dAvgTempLong * 100).toString + "---- 99")
			if (dAvgTempLong <= nComfortTemperature)
			{	
				// Wenn die Komfort Temperartur erreicht wurde, ist der Kamin vermutlich ganz aus. Eine bessere Idee habe ich gerade nicht
				logInfo("Rule", "Die Raumtemperatur rutscht unter die Komforttemperatur")
				Chimney.sendCommand(OFF)
			}
			ChimneyHeating.sendCommand(OFF)
		}
		else if ((dAvgTempShort) / dAvgTempLong * 100 <= (100 - nThreshold/2))
		{
			logInfo("Rule", "Da ist wohl ein Fenster offen" + ((dAvgTempShort) / dAvgTempLong * 100).toString + "----" + (100 - nThreshold/2).toString)
			ChimneyHeating.sendCommand(OFF)
		}
		else 
		{
			logInfo("Rule", "Kamin heizt dem Raum nicht mehr" + ((dAvgTempShort) / dAvgTempLong * 100).toString)
			ChimneyHeating.sendCommand(OFF)
		}
	}
end

Thanks for posting. You can make the code somewhat shorter and a little easier to maintain by applying Design Pattern: How to Structure a Rule.

Also, on RPis and other single board computers they see significantly increased Rules loading and parsing times when primitives are used.This problem does not appear when using .intValue though.

Since the first four lines are defining constants, they should definitely be defined using val instead of var and they should probably be defined as globals instead of in the Rule.

nAvgTempShort and nAvgTempLong are Numbers, not Items. They will never be NULL so the if statement isn’t meaningful. What you want to test against is null, which indicates there was a problem with the call to averageSince.

There is no reason to convert the nAvg variables to Doubles. They are already stored as BigDecimals which is the default type for the result of calculations.

val nTimeShort = 4
val nTimeLong = 30
val nThreshold = 6
val nComfortTemperature = 23

rule "ChimneyDetection"
when
	Time cron "11 */3 * ? * *" // At second :11, every 3 minutes starting at minute :00, of every hour
then
    val nAvgTempShort = GroundFloor_Living_Temperature.averageSince(now.minusMinutes(nTimeShort.intValue))
    val nAvgTempLong = GroundFloor_Living_Temperature.averageSince(now.minusMinutes(nTimeLong))

    // 1. See if the rule need to run at all
    if(nAvgTempShort === null || nAvgTempLong === null) {
        logError("chimney", "One or both of the averages is null!")
        return;
    }

    // 2. Calculate what to do.
    logInfo("Rule", "Da heizt wohl einer, ist der Kamin an? " + ((nAvgTempShort) / nAvgTempLong * 100).toString + "----" + (100 + nThreshold).toString)

    val ratio = nAvgTempShort / nAvgTempLong * 100
    var chimneyNewState = Chimney.state
    var chimneyHeatingState = ChimneyHeating.state
    var sendNotification = false

    switch(nAvgTempShort) {

        case ratio >= 100 + nThreshold: {
            chimneyNewState = ON
        }

        case ratio >= 100 + (nThreshold / 2): {
            chimneyNewState = ON
            chimneyHeatingNewState = ON
        }

        case ratio <= 99: {
            chimneyNewState = if(nAvgTempLong <= nComfortTemperature) ON else Chimney.state
            chimneyHeatingState = OFF
        }

        case ratio <= 100 - (nThreshold / 2): {
            sendNotification = true
            chimneyHeatingNewState = OFF
        }

        default: chimneyHeatingNewState = OFF
    }

    // 3. Do it
    if(sendNotification) sendNotification("email@server.com","Da ist wohl ein Fenster im Wohnzimmer offen") 

    if(Chimney.state != chimneyNewState) Chimney.sendCommand(chimneyNewState)
    if(ChimneyHeating.state != chimneyHeatingNewState) ChimneyHeating.sendCommand(chimneyHeatingNewState)
end

I don’t read German so I don’t know if the logging statements can be consolidated in section 3. If not, add them to the case statements.

2 Likes