Which ones? While not as bad as it could be, I’m part of a power coop (gotta love unincorporated community living) which is also a little backwards in their ways. I’d also like to track my usage a bit. I didn’t know there were zwave meters I could use on the main feeds.
Did you see the Alarm Clock examples and were any of them of help to you?
Dimmers is IMHO a good way to handle this.
This also points out that we really do need a Calendar and Clock widget in the UIs.
SetPoints would also work.
But you can use a StringBuilder.
val StringBuilder Email_Body = "First line of email.\r\n"
Group_PowerKWHReport.members.forEach[item |
Email_Body.append("Another line of the email body. Item: " + item.name + "\r\n"
]
or, you can use the map/reduce functions
val String Email_Body = "First line of email.\r\n" +
Group_PowerKWHReport.members.map[name].reduce[body, name | body = body + "Another line of the email " + name + "\r\n"]
I’ve recently written up a tutorial on using Groups here.
That seems a reasonable approach.
I probably wouldn’t choose rrd4j for this as it starts to “compress” the data in days which will skew your end of month result.
The rules look pretty good as is, with the changes I talk about above. Here is what I would probably do. Not sure they are better. But when I see rules that do almost exactly the same thing I tend to want to combine them. Any reason you are not using DateTime Items instead of Strings for the REPORT_DATEs?:
import org.xtext.lib.base.Functions
val String Mail_Destination = "test@test.com"
val Number Cost_Per_KWH = 0.125 // dollars per KWH
val Functions$Function2 <DimmerItem, StringItem, Boolean> procDaysChange = [ ago, rpt |
var int MyDays = (ago.state as DecimalType).intValue
var startStop = if(ago.name.contains("START")) "start" else "stop"
logInfo("openhab", "number of days ago for report to " + startStop + ": " + MyDays)
rpt.sendCommand(now.minusDays(MyDays).withTimeAtStartOfDay.toLocalDateTime.toString) // I assume you are shooting for midnight MyDays ago
true
]
// There is no problem recalculating both dates when either dimmer changes
rule "recalculate power report dates"
when
Item VIRTUAL_START_POWER_REPORT_DAYS_AGO changed or
Item VIRTUAL_STOP_POWER_REPORT_DAYS_AGO changed
then
procDaysChange.apply(VIRTUAL_START_POWER_REPORTS_DAYS_AGO, VIRTUAL_START_POWER_REPORT_DATE)
procDaysChange.apply(VIRTUAL_STOP_POWER_REPORTS_DAYS_AGO, VIRTUAL_STOP_POWER_REPORT_DATE)
end
I already addressed how you can use StringBuilder instead of a String Item to build your report.
DaysStart = VIRTU ...
Why not just initialize this var with this value in the first place?
You can prefilter the list of values that don’t have a historicState in the table to avoid the MySQL error.
You can replace your switch FriendlyName as String with a .map file and FriendlyName = transform("MAP", "kwh.map", "item.name") It really cleans up tedious code like this.
I’m pretty sure you are trying to get to midnight DaysStart days ago in which case you can use `now.minusDays(DaysStart.intValue).withTimeAtStartOfDay
Putting it all together:
rule "generate and mail power report"
when
Item VIRTUAL_POWER_REPORT_GO changed to ON
then
{
VIRTUAL_POWER_REPORT_GO.sendCommand(OFF)
logInfo("openhab","power report button pushed")
StringBuilder report = new StringBuilder
report.append("\r\nOpenHAB power usage report for ")
val reportLength = ((VIRTUAL_START_POWER_REPORT_DAYS_AGO.state as DecimalType) - (VIRTUAL_STOP_POWER_REPORT_DAYS_AGO.state as DecimalType) )
report.append(reportLength)
report.append(" day")
if (reportLength > 1) report.append("s")
report.append(", ending with ")
report.append(VIRTUAL_STOP_POWER_REPORT_DATE.state)
report.append("\r\n")
var int DaysStart = (VIRTUAL_START_POWER_REPORT_DAYS_AGO.state as DecimalType).intValue
var int DaysStop = (VIRTUAL_STOP_POWER_REPORT_DAYS_AGO.state as DecimalType).intValue
var stopTime = now.minusDays(DaysStop).withTimeAtStartOfDay
Group_PowerKWHReport.members.filter[item|item.historicState(stopTime) != NULL].forEach[item|
var Number KWHStart = 0
var Number KWHStop = 0
var Number KWHDifference = 0
var Number DaysStartOffset = 0
var String FriendlyName = transform("MAP", "kwh.map", item.name)
if (DaysStop > DaysStart)
{
// logInfo("openhab","power report: end date is before start date. setting start date to end date - 1.")
DaysStart = DaysStop + 1 // but counterintuitively that's actually "one more day ago" than the stop date
}
var startTime = now.minusDays(DaysStart).withTimeAtStartOfDay
logInfo("openhab","Power report for " + FriendlyName)
KWHStop = item.historicState(stopTime).state as DecimalType
logInfo("openhab","power report: got end value")
report.append("\r\n" + FriendlyName + ": ")
while (item.historicState(startTime) == null && DaysStart > DaysStop)
{
// if we don't have any historic data for the start date, go forward day by day until either we have data or we overrun the stop date
// logInfo("openhab", "power report: no valid info " + DaysStart + " days ago. Going to next day.")
startTime = startTime.plusDays(1)
DaysStart = DaysStart - 1
DaysStartOffset = DaysStartOffset + 1 // increment counter for how many days we skipped
}
while (item.historicState(startTime).state as DecimalType > KWHStop && startTime.isBefore(stopTime))
{
// the meter was reset between the start and end points, resulting in a negative KWH.
// go forward until either that's not true, or we overrun the end date
// logInfo("openhab","power report: negative KWH (meter reset) for " + FriendlyName + ", going forward a day")
startTime.plusDays(1) // go back one fewer day
DaysStart = DaysStart - 1
DaysStartOffset = DaysStartOffset + 1 // inc counter for how many days we skipped ahead
}
if (DaysStartOffset > 0)
{
logInfo("openhab", "power report: skipped forward " + DaysStartOffset + " to get to valid data for " + FriendlyName)
}
KWHStart = item.historicState(startTime).state as DecimalType
logInfo("openhab","power report: got start value for " + FriendlyName)
KWHDifference = KWHStop - KWHStart
// logInfo("openhab","power report, " + FriendlyName + " start: " + KWHStart + " stop:" + KWHStop + " difference:" + KWHDifference)
var String FormattedNumber = String::format("%1$.3f",KWHDifference)
report.append(FormattedNumber + " KWH")
if (DaysStartOffset > 0)
{
report.append(" (" + (DaysStart - DaysStop) + " day")
if (DaysStart - DaysStop > 1) report.append("s")
report.append(")")
}
FormattedNumber = String::format ("%1$.2f",(KWHDifference * Cost_Per_KWH))
report.append(" [$" + FormattedNumber + "]")
}
] // close forEach
report.append("\r\n\r\nEnd of OpenHAB power report.\r\n")
sendMail(Mail_Destination,"OpenHAB Power Report",report.toString) // mail it!
logInfo("openhab","End of power report")
}
end
Note: I just typed this in. I probably introduced a myriad of errors.
Thanks for sharing!