I have an update, storm v1.1.  I misunderstood the timestamp on archive 
records.  I now see the timestamp value is the time at end of the interval 
of rain accumulation.  Fixed that!   

On Thursday, April 5, 2018 at 4:39:41 PM UTC-7, Rich Altmaier wrote:
> This is my first try at an extension.  This is one file to place in user 
> (/usr/share/weewx/user), with info in the header for the skin.conf and 
> Standard/index.html.tmpl changes to place the data into a report.
> Please do give feedback!
> Thanks, Rich

You received this message because you are subscribed to the Google Groups 
"weewx-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to weewx-user+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
# Copyright 2018 Rich Altmaier  richa...@yahoo.com
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   GNU General Public License for more details.
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

storm.py  v1.1

This search list extension calculates tags, one is a time interval,
     and one is a duration, of the most recent rainstorm:

    'last_rain_storm': finds the last rain interval, allowing stats to
           be calculated over that interval.
                 We search back to find the storm end, which might be still on-going,
                 and search still further back to find the rain start (after a dry period)
    'storm_duration':  duration in days

 1) copy this file to the user directory

 2) add the option search_list_extensions in the 
   /etc/weewx/skins/Standard/skin.conf configuration file, 
   adding the name of this extension.  When you're done, 
   it will look  like this:

    search_list_extensions = user.storm.RainStorm

 3) in the html template /etc/weewx/skins/Standard/index.html.tmpl
    in the Current Conditions section, perhaps after Inside Temperature,
    add this table row:
      <td class="stats_label">last storm total rain,
      <br />ending $last_rain_storm.end
      <br />of duration $storm_duration days</td>
      <td class="stats_data">$last_rain_storm.rain.sum</td>

You can also use other tags such as $last_rain_storm.outTemp.max for the max
temperature, or $last_rain_storm.rain.sum for the total rainfall in the last

import datetime
import time

from weewx.cheetahgenerator import SearchList
from weewx.tags import TimespanBinder
from weeutil.weeutil import TimeSpan

class RainStorm(SearchList): 

    def __init__(self, generator): 
        SearchList.__init__(self, generator)
    def get_extension_list(self, timespan, db_lookup): 
        """Returns a search list extension for storm detection
          timespan: An instance of weeutil.weeutil.TimeSpan. This will
                    hold the start and stop times of the domain of 
                    valid times.

          db_lookup: This is a function that, given a data binding
                     as its only parameter, will return a database manager

        # first find end of most recent storm.
        #   may be still ongoing (e.g. rain in most recent record).
        #   parameterized with:  
        storm_dry_period = 24   # number of hours of no rain to delimit
                                # storm end and storm start.
        storm_in_past = 20      # number of days to look back for a storm
        storm_now = int(time.time() )   # right now this second
                           # can choose other epoch value for testing!

        # set now to a testing time, to look at various past rain records
        #StructTime  = time.strptime('2017-02-05 16:00:00-PST', '%Y-%m-%d %H:%M:%S-%Z')
             # 0.80in, from 2017-02-01 to 2017-02-04
        #StructTime  = time.strptime('2017-01-05 16:00:00-PST', '%Y-%m-%d %H:%M:%S-%Z')
             # 1.58 in, from 2017-01-01 to 2017-01-05
        StructTime  = time.strptime('2017-07-05 16:00:00-PST', '%Y-%m-%d %H:%M:%S-%Z')
             # 0.0 in. 
        #storm_now = int(time.mktime(StructTime))

        wx_db_mgr = db_lookup()
	c = wx_db_mgr.connection.cursor()

        #caching method:
        #  temporary table RainStormcache,
        #    fields:  entryEpoch, startE, endE    all are Integer
        #    just one record
        #  when entryEpoch+N > now, then the entry is recent enough
        CacheValid = False
        NowEpoch = int(time.time() )
        c.execute("PRAGMA table_info(RainStormcache)")
        CacheInfo = c.fetchone()
        if CacheInfo is not None:
           c.execute("select count(*) from RainStormcache")
           #print "number of rows in storm cache ", int(c.fetchone()[0])
           c.execute("SELECT * from RainStormcache")
           CacheData = c.fetchone()
           #print "CacheData ", CacheData
           CacheEpoch = CacheData[0]
           StartTepoch = CacheData[1]
           storm_end_epoch = CacheData[2] 
           if CacheEpoch+30 > NowEpoch:  # if aged cache is still ahead of now...
               CacheValid = True
           #print "Cache valid ", CacheValid, " NowEpoch ", NowEpoch, " start ", StartTepoch, " end ", storm_end_epoch
        #end cache fetch.  CacheValid tells us if data found

        if CacheValid == False:
	    c.execute("drop table IF EXISTS RainWeek")
    	        create TEMPORARY table RainWeek as 
    	        select dateTime, rain, 
                   datetime(dateTime, 'unixepoch', 'localtime') as dateString,
                   dateTime as startEpoch, dateTime as endEpoch
               from archive where dateTime >= :now - (24 * 3600 * :past )
               AND dateTime <=  :now
               ORDER BY dateTime
               """, {"now": storm_now, "past": storm_in_past})

            c.execute("select count(*) from temp.RainWeek")
            print "number of rows in storm viewing table is ", int(c.fetchone()[0])

                -- find end of most recent wet period ending at or before storm_now
                -- or storm_now if has been wet within storm_dry_period hours of now. 
                -- Note: dateTime stamp is end time of that row of accum rainfall
                select  CASE 
                  ((select MAX(dateTime) as RainEndT  from temp.RainWeek where rain <> 0 ) 
                    + (:dry * 3600))
                  :now  -- storm is still on-going
                ELSE -- find most recent last wet time
                  (select MAX(dateTime) as LastWetT from temp.RainWeek where rain <> 0 )
                --above is most recent record with rainfall
                END  as RainEndTime
                """, {"now": storm_now, "past": storm_in_past, "dry": storm_dry_period})
            EndT = c.fetchone()
            if EndT[0] is None:
                StormFound = False
                print "No storm in past", storm_in_past, " days"
                storm_end_epoch = storm_now - storm_in_past*24*3600 -1
                StormFound = True
                #print "most recent wet record ", EndT
                storm_end_epoch = int(EndT[0])
                print "Storm end epoch ", storm_end_epoch, ", local ", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(storm_end_epoch))

            if StormFound:
                    -- find dry period preceeding storm_end_epoch.
                    -- look at storm_dry_period hours of records preceeding each record to find 
                    --  a preceeding sequence of 0 rain lasting storm_dry_period.
                    select MAX(dateTime) as RainStartT from  
                      (select f1.rowid AS ROWID, sum(f2.rain) as RunningRain, f1.dateTime
                        FROM temp.RainWeek f1  INNER JOIN temp.RainWeek f2
                        ON f1.dateTime >= f2.dateTime AND f2.dateTime > (f1.dateTime - ( :dry *3600))
                        GROUP BY ROWID
                        HAVING  RunningRain = 0 and f1.dateTime < :ending   -- Rain end time
                    """, {"ending": storm_end_epoch, "dry": storm_dry_period})
                row = c.fetchone()
                if row[0] is not None:
                    StartTepoch = int(row[0])
                    print "Storm start time epoch ", StartTepoch, ", local ", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(StartTepoch))
                    StartTepoch = storm_now + storm_in_past*24*3600
                    print "No dry start found in search interval" 

                #if we hit the earliest record in the TEMPORARY table, then 
                #  there is really not a beginning of storm found,
                #  and our rain accum calculation will start to reduce as
                #  early portions of the storm go out of our search window.
                #Therefore don't match a storm which is getting too old! 
                if StartTepoch < storm_now - storm_in_past*24*2600:
                   #also interpret this rain receeding into the past as
                   #no storm found
                   print "Storm is too far past, dont use it"
                   StartTepoch = storm_now -1
                   storm_end_epoch = storm_now

            else: # no rain :-(
                StartTepoch = storm_now -1
                storm_end_epoch = storm_now

            #now stuff data into the cache
            c.execute("drop table IF EXISTS RainStormcache")
            c.execute("""create TEMPORARY table RainStormcache (
                CacheEpoch integer not null,
                CacheStartE integer not null,
                CacheEndE integer not null)
            c.execute("""INSERT INTO RainStormcache VALUES (
                :stamp , :start , :end )
                """, {"stamp": int(time.time()), "start": StartTepoch, "end": storm_end_epoch})
            #end of if not CacheValid

        #have storm delimiting times, form TimespanBinder

        # Form a TimespanBinder object, using the time span we just
        # calculated:
        LastStorm = TimespanBinder(TimeSpan(StartTepoch, storm_end_epoch),

        StormDurationDays = ( storm_end_epoch - StartTepoch +(3600*24)-2) / (3600*24)
        # Now create a small dictionary
        search_list_extension = {
                                 'last_rain_storm' : LastStorm,
                                 'storm_duration' : StormDurationDays} 
        # Finally, return our extension as a list:
        return [search_list_extension]  

Reply via email to