I’m using the excellent wmbusmeters software and AMB8465 USB stick a to capture and store the Wireless M-Bus values broadcasted by my heat meter (Techem / Kamstrup vario 3 type 3.2.1) and water meter (Techem radio 4). Unfortunately, both meters only broadcast the cumulative (ever growing) values of the energy / water consumed irregularly (every 5 - 20 minutes). These cumulative values are also only updated irregularly every ~24 hours.
Simply logging these values in openHAB results in something like this:
While this gives you a good rough impression on the total energy consumed, understanding more nuanced differences in consumption (e.g. between days, times of the year, etc) is difficult.
The goal was to come up with something that calculates and persists the consumption per time. As indicated here by @Sven_Festersen , since energy is the time integral of power, for e.g. the power meter the time derivative of that value P = dE/dt
needs to be calculated. @hhors provided a solution here, but my requirement was slightly different, and I wanted to understand this bottom-up myself.
This is the result. Comments & suggestions are welcome.
rule "Konvertierung - Warmwasserzaehler"
when
Item warmwasserzaehler_gesamt changed
then
// The following rule calculates the average consumption (e.g. W or l/h) between two (cummulative) meter readings (e.g. kWh or l).
// This is needed when the connected meter frequently only provides cummulative meter readings, instead of average consumption values.
// To do so, the script stores the last two meter readings and their timestamps (Step 1), then calculates the delta between the two
// timestamps and meter readings (Step 2), and then calculates the consumption per timeframe by dividing consumption by time (Step 3).
// It must be triggered only by a change in the meter reading.
// The following variables are at play:
// warmwasserzaehler_zeit_t_minus_1 - Stores the timestamp (# of epoch minutes since 1970) of the last meter reading, manually created, Type: Number, Semantic Class: Point
// warmwasserzaehler_zeit_t_minus_2 - Stores the timestamp (# of epoch minutes since 1970) of the second last meter reading, manually created, Type: Number, Semantic Class: Point
// warmwasserzaehler_zeit_delta - Stores the difference in minutes between the last meter reading and the second last meter reading, manually created, Type: Number, Semantic Class: Point
// warmwasserzaehler_zaehlerstand_t_minus_1 - Stores the meter reading (e.g. kWh or l) of the last meter reading, manually created, Type: Number, Semantic Class: Point
// warmwasserzaehler_zaehlerstand_t_minus_2 - Stores the meter reading (e.g. kWh or l) of the second last meter reading, manually created, Type: Number, Semantic Class: Point
// warmwasserzaehler_zaehlerstand_delta - Stores the difference in meter reading (e.g. kWh or l) between the last meter reading and the second last meter reading, manually created, Type: Number, Semantic Class: Point
// warmwasserzaehler_leistung : Stores the calculated average consumption (e.g. W or l/h) for the time between the last and second last meter reading, manually created, Type: Number, Semantic Class: Point
// Step 0: Check if the current meter reading is different from the latest stored meter reading (warmwasserzaehler_zaehlerstand_t_minus_1)
// If yes, an energy consumption can be calculated: Execute the script and calculate the energy consumption.
// If no, no new meter reading is available, and executing the script would "reset" the time difference",
// which would result in wrong values for the subsequent calculation
if(warmwasserzaehler_gesamt.getState() != warmwasserzaehler_zaehlerstand_t_minus_1.state){
// Step 1.1: Store the last two meter readings
// Make room to store the new (latest) meter reading by moving the old (second last) meter reading to the second last meter reading variable warmwasserzaehler_zaehlerstand_t_minus_2
warmwasserzaehler_zaehlerstand_t_minus_2.postUpdate(warmwasserzaehler_zaehlerstand_t_minus_1.state)
// Capture the new (latest) meter reading in warmwasserzaehler_zaehlerstand_t_minus_1 (freed up before)
warmwasserzaehler_zaehlerstand_t_minus_1.postUpdate(warmwasserzaehler_gesamt.getState())
// Print the values as cross-check
logWarn("status", "t-2: " + warmwasserzaehler_zaehlerstand_t_minus_2.getState() + " MWh; " + "t-1: " + warmwasserzaehler_zaehlerstand_t_minus_1.getState() + " MWh")
// Step 1.2: Store the timestamps of the last two meter readings
// Make room to store the new (latest) timestamp by moving the old (second last) timestamp to the second last timestamp variable warmwasserzaehler_time_t_minus_2
warmwasserzaehler_zeit_t_minus_2.postUpdate(warmwasserzaehler_zeit_t_minus_1.state)
// Capture the new (latest) timestamp in warmwasserzaehler_zeit_t_minus_1 (freed up before)
var current_Time = now.toInstant().toEpochMilli() / 60000
warmwasserzaehler_zeit_t_minus_1.postUpdate(current_Time)
// Print the values as cross-check
logWarn("status", "t-2: " + warmwasserzaehler_zeit_t_minus_2.getState() + " minutes; " + "t-1: " + warmwasserzaehler_zeit_t_minus_1.getState() + " minutes")
// Step 2.1: Calculate the differences between the two timestamps
val delta_zeit = (warmwasserzaehler_zeit_t_minus_1.state as Number) - (warmwasserzaehler_zeit_t_minus_2.state as Number)
warmwasserzaehler_zeit_delta.postUpdate(delta_zeit)
// Print the values as cross-check
logWarn("notifications", "Delta Zeit: " + delta_zeit + " Minutes")
// Step 2.1: Calculate the differences between the two meter readings
val delta_zaehlerstand = (warmwasserzaehler_zaehlerstand_t_minus_1.state as Number) - (warmwasserzaehler_zaehlerstand_t_minus_2.state as Number)
warmwasserzaehler_zaehlerstand_delta.postUpdate(delta_zaehlerstand)
// Print the values as cross-check
logWarn("notifications", "Delta Zählerstand: " + delta_zaehlerstand + " MWh")
// Step 3: Calculate the energy consumption by dividing the the delta in meter reading by the delta in time
val leistung = ((delta_zaehlerstand) / (delta_zeit/60))*1000
warmwasserzaehler_leistung.postUpdate(String.format("%.1f", leistung))
// Print the values as cross-check (rounded to 1 digit)
logWarn("notifications", "Leistung: " + String.format("%.1f", leistung) + " kW")
}
else {
logWarn("notifications", "No heat meter differnece in meter reading detected: Nothing updated.")
}
end
Update: Adjusted by an additional check that protects against unintended script execution (and thus wrong results)