Hi all,
The main purpose of this topic is to give back after reading and using numerous posts of knowledgeable people here on the OpenHAB forum and beyond.
After searching through the internet for clear examples to calculate gas usage per time unit, I decided to build my own rules script, as there is not a comprehensive guide to build your own rules.
The main problem I am solving here is to calculate the precise amount of gas that is consumed every minute, hour and day, based on periodic readings of the gasmeter. The binding that I am using is the excellent DSMR-binding (https://docs.openhab.org/addons/bindings/dsmr1/readme.html) that reads values every 5 minutes from my ISKRA AM550 smart meter.
About the script:
I have tried to document it as extensive as I can in order to explain what I am doing.
Also I have added the ability to control the amount of logmessages using an OpenHAB number item “loglevel”
I welcome your comments and suggestions to improve it further.
// SlimmeMeter.rules - Created by Harold Horsman - February 2018
// These 3 rules can calculate the gas usage per minute, per hour and per day.
// Trigger for usage per minute is the change of a gas meter reading value. The DSMR gasmeter binding reads the gasmeter every 5 minutes.
// The hourly rule is triggered on the whole hour and the daily is triggered at midnight.
// Each calculation takes the value of the gasmeter reading and the previous reading. The difference is then calculated
// Then the time difference between the two readings is calculated.
// Finally the usage is calculated based on the 2 values.
// After the calculation is done, the current gasmeter reading and its timestamp are stored into OpenHAB items to use for the next calculation.
// Used variables and items:
// INPUTS
// M3Meter_DeliveryM3 OH measured item from DSMR Contains gasmeter counter value
// M3Meter_Timestamp OH measured item from DSMR Contains timestamp of last measurement
// LogLevel OH item Loglevel determines the amount of logoutput
// USED BY CALCULATIONS:
// P1GasActualUsage internal rule variable Used to calculate the difference between 2 gasmeter readings
// TimePeriodInMins internal rule variable Used to calculate the exact time difference in minutes between 2 gasmeter readings
// P1GasActualUsage internal rule variable Used to calculate the actual gas usage over a certain period.
// P1GasActualUsagePerMinute internal rule variable Used to calculate the actual gas usage per minute over a certain period.
// P1GasActualUsagePerHour internal rule variable Used to calculate the actual gas usage per hour over a certain period.
// P1GasActualUsagePerDay internal rule variable Used to calculate the actual gas usage per day over a certain period.
// newDate internal rule variable Used to determine the timestamp of the most recent reading and make calculations with it
// oldDate internal rule variable Used to determine the timestamp of the previous calculation and make calculations with it
// OUTPUTS
// P1_Gas_Actual_UsagePerMinute OH item Calculated delivery over last period (M3/minute)
// P1_Gas_Actual_UsagePerHour OH item Calculated delivery over last hour (M3/hour)
// P1_Gas_Actual_UsagePerDay OH item Calculated delivery over last day (M3/day)
// P1_Gas_TS_Actual_Usage OH item Timestamp of Calculated delivery over last period (M3/minute)
// P1_Gas_TS_Actual_Hour OH item Timestamp of Calculated delivery over last hour (M3/hour)
// P1_Gas_TS_Actual_Day OH item Timestamp of Calculated delivery over last day (M3/day)
// Import the right Java classes
// import org.joda.time.*
// import java.text.SimpleDateFormat
// import java.util.Date
// import java.time
// 2018-01-08 Note: the import of the java libraries above is not required (anymore)
//
// ---------------------------------------------------------------------------------------
// **************************************************************************************
rule "P1 Calculate Gas Usage per minute after EVERY update"
when
Item M3Meter_DeliveryM3 changed // Every time there is a gasmeter reading, calculate the usage per minute
then
// Calculate the difference in readings and its timestamps and then calculate the usage over that period
// val double daymsec = 86400000
// val double millisperhour = 3600000
val double millisperminute = 60000
var Number P1GasActualUsagePerMinute = 0
var oldDate = new DateTime((P1_Gas_TS_Last_Value_Actual.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli)
var newDate = new DateTime((M3Meter_Timestamp.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli)
// Calculate the difference between the gasmeter readings
var Number P1GasActualUsage = (M3Meter_DeliveryM3.state as Number) - (P1_Gas_Last_Value_Actual.state as Number)
// Calculate the period between the two timestamps (in hours)
var Number TimePeriodInMins = ((newDate.millis - oldDate.millis) / millisperminute)
// Check that value of TimePeriodInMins is not zero and then calculate and update the items
if (TimePeriodInMins == 0) {
logInfo ("SlimmeMeterRules", "UPDATE As TimePeriodInMins = 0, not updates are done")
}
else {
// Calculate the actual usage over the determined period (in minutes)
P1GasActualUsagePerMinute = (P1GasActualUsage / TimePeriodInMins)
//Update the output values:
P1_Gas_Actual_UsagePerMinute.postUpdate(P1GasActualUsagePerMinute)
P1_Gas_Last_Value_Actual.postUpdate(M3Meter_DeliveryM3.state as Number)
P1_Gas_TS_Last_Value_Actual.postUpdate(M3Meter_Timestamp.state as DateTimeType)
logInfo ("SlimmeMeterRules", "UPDATE P1GasActualUsagePerMinute = {}", P1GasActualUsagePerMinute)
} // End else
end
// ---------------------------------------------------------------------------------------
// **************************************************************************************
rule "P1 Calculate Gas Usage per HOUR"
when
// cron for every hour
// cron "S M H D M Day [Y]"
Time cron "0 0 * * * ?" // Set CRON to run this rule every hour at HH:00
then
logInfo ("SlimmeMeterRules", "================= START CALCULATION HOURLY VALUE ===================")
// val double daymsec= 86400000
val double millisperhour= 3600000
// val double millisperminute= 60000
var Number P1GasActualUsagePerHour = 0
// Reset previous values to some reasonable values when they are NULL
if (P1_Gas_TS_Last_Value_Hour.state == NULL) {
P1_Gas_TS_Last_Value_Hour.postUpdate(OH_SystemDownTime.state as DateTimeType)
}
else {
if (P1_Gas_Last_Value_Hour.state == NULL) {
P1_Gas_Last_Value_Hour.postUpdate(M3Meter_DeliveryM3.state as Number)
logInfo ("SlimmeMeterRules", "HOUR P1_Gas_Last_Value_Hour was reset from NULL to ={}", P1_Gas_Last_Value_Actual)
}
}
val oldDate = new DateTime((P1_Gas_TS_Last_Value_Hour.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli)
val newDate = new DateTime((M3Meter_Timestamp.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli)
// Calculate the difference between the gasmeter readings
var Number P1GasActualUsage = ((M3Meter_DeliveryM3.state as Number) - (P1_Gas_Last_Value_Hour.state as Number))
// Calculate the period between the two timestamps (in hours)
var Number TimePeriodInHours = ((newDate.millis - oldDate.millis) / millisperhour)
// Check that value of TimePeriodInMins is not zero and then calculate and update the items
if (TimePeriodInHours == 0) {
logInfo ("SlimmeMeterRules", "HOUR As TimePeriodInHours = 0, not updates are done")
}
else {
// Calculate the actual usage over the determined period (in hours)
P1GasActualUsagePerHour = (P1GasActualUsage / TimePeriodInHours)
//Update the output values:
P1_Gas_TS_Last_Value_Hour.postUpdate(M3Meter_Timestamp.state as DateTimeType)
P1_Gas_Last_Value_Hour.postUpdate(M3Meter_DeliveryM3.state as Number)
P1_Gas_Actual_UsagePerHour.postUpdate(P1GasActualUsagePerHour)
logInfo ("SlimmeMeterRules", "HOUR P1_Gas_Actual_UsagePerHour ={}", P1_Gas_Actual_UsagePerHour)
} // End else
logInfo ("SlimmeMeterRules", "================= END CALCULATION HOURLY VALUE ===================")
end
//---------------------------------------------------------------------------------------
// **************************************************************************************
rule "P1 Calculate Gas Usage per DAY"
when
// cron for every day
// cron "S M H D M Day [Y]"
Time cron "0 0 0 * * ?" // Set CRON to run this rule every day at 00:00:00
then
logInfo ("SlimmeMeterRules", "================= START CALCULATION DAILY VALUE ===================")
val double daymsec= 86400000
// val double millisperhour= 3600000
// val double millisperminute= 60000
var Number P1GasActualUsagePerDay = 0
// First reset previous values to some reasonable values when they are NULL
if (P1_Gas_TS_Last_Value_Day.state == NULL) {
P1_Gas_TS_Last_Value_Day.postUpdate(M3Meter_Timestamp.state as DateTimeType)
logInfo ("SlimmeMeterRules", "DAILY P1_Gas_TS_Last_Value_Day was reset from NULL to ={}", P1_Gas_TS_Last_Value_Day)
}
if (P1_Gas_Last_Value_Day.state == NULL) {
P1_Gas_Last_Value_Day.postUpdate(M3Meter_DeliveryM3.state as Number)
logInfo ("SlimmeMeterRules", "DAILY P1_Gas_Last_Value_Day was reset from NULL to ={}", P1_Gas_Last_Value_Actual)
}
val oldDate = new DateTime((P1_Gas_TS_Last_Value_Day.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli)
val newDate = new DateTime((M3Meter_Timestamp.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli)
// Calculate the difference between the gasmeter readings
var Number P1GasActualUsage = ((M3Meter_DeliveryM3.state as Number) - (P1_Gas_Last_Value_Day.state as Number))
// Calculate the period between the two timestamps (in hours)
var Number TimePeriodInDays = ((newDate.millis - oldDate.millis) / daymsec)
// Check that value of TimePeriodInDays is not zero and then calculate and update the items
if (TimePeriodInDays == 0) {
logInfo ("SlimmeMeterRules", "DAILY As TimePeriodInDays = 0, not updates are done")
}
else {
// Calculate the actual usage over the determined period (in days)
P1GasActualUsagePerDay = (P1GasActualUsage / TimePeriodInDays)
//Update the output values:
P1_Gas_TS_Last_Value_Day.postUpdate(M3Meter_Timestamp.state as DateTimeType)
P1_Gas_Last_Value_Day.postUpdate(M3Meter_DeliveryM3.state as Number)
P1_Gas_Actual_UsagePerDay.postUpdate(P1GasActualUsagePerDay)
logInfo ("SlimmeMeterRules", "DAILY P1_Gas_Actual_UsagePerDay ={}", P1_Gas_Actual_UsagePerDay)
} // End else
if (LogLevel.state > 0) logInfo ("SlimmeMeterRules", "================= END CALCULATION DAILY VALUE ===================")
end
//---------------------------------------------------------------------------------------