Daily totals & a case for subroutines & sanity checks

In these posts about totalizing (both analog[/U] & [U][URL=“http://www.opto22.com/community/showthread.php?t=1346”]digital values), we talked about getting running totals of various measurements. But what’s the next step in turning your data into something helpful?

You have various options for logging and putting info into databases, but what if you just want some visibility into usage for this week, month, or year? Maybe even how this year’s usage compares to previous years?

The groovFan mentioned in this analog totalize example wanted to show his daily total on 7-day (1-week) calendar, and up to 31 daily totals for the current month. Those daily totals would also be summed so he could see the monthly totals. Those three lists of 7, 31, and 12 totals would each get cleared out at the end of the week, month, and year, respectively.

Additionally, we wanted totals for each year (also updated daily) in a, for example, persistent 200-element list – where first element represented 2000, and element [15] would be 2015.

He’d written some code that ran just after the clock rolled over at midnight, but it wasn’t working quite right – he was off by one here and there – so he asked me for a little logic/coding help. The basic summing, etc. logic wasn’t too exciting, but this real-world example touched on several of my favorite topics:

  • Why/when to use OptoScript (vs. flow charts), and [B]subroutines[/B] (vs. copy/paste)
  • The importance of [B]good variable names[/B]
  • [B]Sanity checking[/B] (your floats and your assumptions)
  • [B]Testing[/B] (to make sure what you meant is what you coded)

He wanted to have a similar dashboard/summary for not just his power production, but also for his water usage, and a few other values he’s not yet connected to his system. Perfect place for a SUBROUTINE! (BTW, some of the “downsides” to subs mentioned in that [URL=“http://www.opto22.com/community/showthread.php?t=696”]sub 101 post will be going away in an upcoming release of PAC Control. If you’re already a subroutine fan, make sure you’re [U][URL=“http://info.opto22.com/optonews-subscription-form”]subscribed to OptoNews[/U] for more info.)

This particular OptoFan likes to name his variables differently that how I like to name mine. An added benefit of a sub is that when you import someone else’s sub into your strategy, the variables are local therefore not contaminating your lovely naming scheme with whatever lovely naming scheme they favor. The sub is a black box that way.

Whatever your favorite naming scheme, don’t forget you have up to 50 characters, so increase your odds of success by including units in those names, if appropriate, and giving yourself clues and reminders. For example, in this subroutine, the list of annual sums is going to start at 2000 by convention. So I called my local variable for that table: ftYearlyTotalsElement0IsYear2000 to remind myself of that convention.

Like I mentioned in previous totalizing posts, be watchful for invalid floats. If you get a nan (“not a number”) in your readings (for example, if your power supply is low), you could accidentally wipe out a year’s worth of data, for example, by adding it to a previously perfectly good total. PAC Control’s command/condition: “Float Valid?” makes it easy to check, and the Error Handling Commands, for example my favorite: “Add Message to Queue” will put warnings and/or errors in the message queue if you do find something amiss.

I ended up writing almost as much code to TEST my subroutine as the sub includes itself… especially since I planned to share this code and use it in multiple places. Also, some of this code won’t get used until a whole year went by, and I’d hate to accidentally lose that much data. So the test code fakes out some date values so I could watch and make sure the sub worked how I expected for a year and then some.

Here’s the guts (where we actually totalize, notice most of this code is sanity checking):

  if ( IsFloatValid( fYesterdaysTotal ) ) then // it's okay to add to all our other totals, it won't clobber (turn to qnan) those out too
    // we're all good, no need to make any changes to that total
    fSanityCheckedTotalForYesterday = fYesterdaysTotal;
    NumberToString(nMonthToUpdate, sValue_A);
    NumberToString(nDOM_to_update, sValue_B);
    NumberToString(nYearToUpdate, sValue_C);
    sErrorMessage = "Invalid float on: Month: " + sValue_A + ", Day: " + sValue_B + ", Year: " + sValue_C + 
      " Discarding bad value (will add 0 instead).";
    // put a warning in the queue
    AddMessageToQueue(16, sErrorMessage);
    fSanityCheckedTotalForYesterday = 0;
  if (nDOW_to_update == 0) then // we're starting a new week (values: 0 - 6), clear out the old numbers
    MoveToNumTableElements(0, 0, -1, ftWeeklyUse7Totals);
  ftWeeklyUse7Totals[nDOW_to_update] = fSanityCheckedTotalForYesterday; // update the total in our weekly tally
  // We're starting a new month, then clear out the old month before updating this 1st day
  if (nDOM_to_update == 1) then
    // Ok, now that we have added up the whole month, clear it. This way we dont have to keep track of 29 day years.
    MoveToNumTableElements(0, 0, -1, ftMonthlyUse31Totals);
  ftMonthlyUse31Totals[nDOM_to_update] = fSanityCheckedTotalForYesterday; // update the total in our monthly tally
  if (nMonthToUpdate == 1) then // starting a new year, clear out our 12-month list
    MoveToNumTableElements(0, 0, -1, ft12ElementMonthTotals);          
  fMonthSoFar = 0.0; // initialize from zero so we don't get confused
  for nDOMIndex = 1 to 31 step 1 // extra days will have zero in them since we wipe out the whole month once we're done with this total
    fMonthSoFar = fMonthSoFar + ftMonthlyUse31Totals[nDOMIndex];
  ft12ElementMonthTotals[nMonthToUpdate] = fMonthSoFar; 
  // no need to clear out elements here, since we're keeping them indefinitely, assuming the element # is the year to update - 2000, so [0] is y2k
  fYearSoFar = 0.0; // intialize year for zero
  for nMonthIndex = 1 to 12 step 1 // this time we're counting up all the months
    fYearSoFar = fYearSoFar + ft12ElementMonthTotals[nMonthIndex];
  ftYearlyTotalsElement0IsYear2000[ nYearToUpdate - 2000 ] = fYearSoFar; 

Note there near the bottom, to “totalize” all the days of the month, then all the months for the year, I use a “for” loop for each. You could do this in action blocks, but much easier it OptoScript! Also note I did NOT use a for loop for setting the tables to 0 at the start of each week, month, etc. Instead I used the handy MoveToNumTableElement command and passed the shorthand -1 for “do the whole table.” Super fast/easy!

Here’s the full sub in 9.1 basic, including a (detached) OptoScript block which I used a for testing/making time pass quickly (for testing purposes only, not recommended in Real Life).

Happy summing!


9.1BasicSubroutineToUpdateWeeklyMonthlyYearlyTotals.zip (4.51 KB)

Thanks to Mary for writing this super totalizing subroutine…
I am using it at my house to keep track of kWh’s and [URL=“http://blog.opto22.com/optoblog/visualizing-water-usage-with-groov”]gallons of water used.

One important thing… The 8 element integer table you pass it that has the date / time in it… Be sure and NOT use that table anywhere else in your strategy. (You can read from it, but never should you write to it).
[B]Note that ALL these tables must be persistent.[/B]
The length of each of the tables is also (very) important.
Weekly must be a float, 8 elements.
Month must be a float, 32 elements.
Year must be a float, 13 elements.
Decade must be a float, 200 elements.
ntTimeTable must be an int32, 8 elements.

Here is an example of what the sub looks like in action in some OptoScript that I wrote;

if (GetSecondsSinceMidnight() == 0) then

  // set the controller time once a day
  time_zone_status = SetTimeZoneConfiguration("PST1,11,0,1,0200/PDT,3,0,2,0200");
  time_sync_status = SynchronizeClockSNTP(5, time_server_url);

  // run Mary's week / month / year / decade subroutine
  UpdateWeeklyMonthlyYearlyTotals(live_kwh, kwh_weekly, kwh_month, kwh_year, kwhTimeTable, kwh_decade);

  // clear the house totalizier
    live_kwh = GetClearAnalogTotalizerValue(house_power) / 1000.0;

There are few other bits in here, so quickly here is what is happening.
At midnight, [URL=“http://blog.opto22.com/optoblog/how-to-set-the-clock-on-a-pac-controller”]set the controller real time clock, then we run Mary’s sub which takes the live kwh value and totalizes weekly, monthly, yearly and decade (or longer if you like) values.

(Again, all these tables must be persistent!!! (Don’t ask me why I am emphasizing this so much!))

We then clear the kWh totalizer so that we can do the whole thing again for the next 24 hours.

Pretty straight forward and very nice to have to not worry about keeping track of all the totals.

If you want to display some of the toalizer data in groov or Pac Display, you might want to tweak a few things depending on how you want to visualize the data.

Here is what I did;
(This is not definitive, it is just an example to get you started).

// Move the totalized data into some vars for groov to display
// Get and use the month and year index from the date/time table so it automagicly updates
// Hint; index 0=month, 1=date, 2=year
house_kwh_year_groov = house_kwh_decade[ntHousekwhTime[2]-2000];
house_kwh_month_groov = house_kwh_year[ntHousekwhTime[0]];

// add up how many kWh's the house has used this week
n1 = 0;
temp_tally = 0;
for n1 = 0 to 6 step 1
  temp_tally = temp_tally + house_kwh_week[n1];
house_kwh_week_groov = temp_tally;

// show me the money [no tiers here, just a flat rate of 14 cents per kilowatt hour]
house_kwh_money_year = house_kwh_year_groov * 0.14;
house_kwh_money_month = house_kwh_month_groov * 0.14;
house_kwh_money_week = house_kwh_week_groov * 0.14;

Now we have week, month and year variables.
We also have the same three totals in dollars.

The live value and cost are easy to add into some chart that will update them as often as needed.
(For extra points, I have added some code into the ‘seconds since midnight’ if statement that moves the live value into a variable called ‘house_kwh_yesterday_groov’ before I reset the live value).

I quickly threw those variables at a groov screen;

(It was heavy overcast in the afternoon when I took the screen shot, hence the 3 cents of savings and no amps!!)

Anyone got any thoughts on a better way to visualize the totalized data?