Hot Tank monitoring, with energy calculations

Nice tutorial! Very thorough and detailed. I love the graphs.

My only suggestion is you can easily merge your two “Calculate high and low” Rules into one using the triggeringItem implicit variable.

There are a few other minor changes I’d recommend too like using the method instead of the action on postUpdate and use Number instead of DecimalType.

Below I’m applying (for further reading):

rule "Calculate high and low of hot tank sensors"
when
    Member of gHotTankTemperature changed
then
    if(triggeringItem.state == NULL || triggeringItem.state == UNDEF) return; // do nothing if the Item changed to not a number

    // 2. Calculate what to do
    val min = triggeringItem.minimumSince(now.minusHours(12), "influxdb").state as Number
    val max = triggeringItem.maximumSince(now.minusHours(12), "influxdb").state as Number

    // 3. Do it
    postUpdate(triggeringItem.name+"_Min12h_Val", if(min === null) "UNDEF" else min.toString)
    postUpdate(triggeringItem.name+"_Max12h_Val", if(max === null) "UNDEF" else max.toString)
end

The above Rule works for all four sensors and will set the Min and Max Items to UNDEF if for some reason the calls to persistence to get the min and max fails resulting in null. Note that even though I said to use the method instead of the Action for postUpdate, in the above I’m calculating the name of the Item in the Rule which is one of the few places where the Action is more appropriate.

The one line if/else is called the trinary operator.

Shouldn’t the label for FF_HotTank_02_Min12h_Val be “- Min. Temp …” instead of Max?

In the second Rule I’m going to bring out some more advanced stuff using Rules, much of which is documented in Design Pattern: Working with Groups in Rules.

rule "Calculate the amount of energy in the hot tank in kWh"
when
    Item gHotTankTemperature changed
then
    // 1. Is there anything to do?
    if(gHotTankTemperature.state == NULL || gHotTankTemperature == UNDEF) return;

    // 2. Calculate what to do

    // ------ Calculate the total energy ---------
    // Formula: (Volume in L * 4 * Difference in temperature) / 3412 = Potential energy in kW.
    // This does not take into account the heating method or efficiency - electric, gas, oil, etc,
    //
    // My tank is 300L, I have four sensors installed on it, one on the top, one a quarter of the way down and so forward.
    // Hence I assigned an equivalent volume of 75L to each sensor.
    //
    // - filter[ t | ... : returns a List of all the members that are not NULL and not UNDEF
    // - map[ state as Number ] : returns a List of all the States of the result from the filter as Numnbers
    // - reduce[ sum, temp | ... : loops through all the Numbers returned from the map, performs the calculation
    //   on each one storing the result in sum
    val totEnergy = gHotTankTemperature.members.filter[ t | t.state != NULL && t.state != UNDEF ].map[ state as Number ].reduce[ sum, temp | 
        sum = sum + (75*4*(temp - 20)/3412) 
    ]

    // ------ Total energy five minutes ago ------
    val 5minsBack = FF_HotTank_TotEnergy.historicState(now.minusMinutes(5), "influxdb").state as Number
    
    // 3. Do it
    FF_HotTank_TotEnergy.postUpdate(valEnergy)
    FF_HotTank_Delta.postUpdate((valEnergy - 5minsBack) * 1000)
end

I just typed in the above. There are likely mistakes and typos. And I don’t expect you or anyone else to have come up with the above code. I mainly am posting it as reference. Perhaps when you or future users come and see duplicative code there will be some techniques illustrated above that can be applied to avoid the duplication.

Thanks for posting! We more need tutorials like this!