From 1c42de511c8003e868e8ef45d433e2ae258b73c8 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Wed, 27 Aug 2014 16:41:21 +0000 Subject: [PATCH] Changed how the accumulators work. There is now a dictionary holding special types (instead of using a subclass). --- TODO.txt | 2 + bin/weewx/accum.py | 193 +++++++++++++++++++++--------------------- bin/weewx/stats.py | 8 +- bin/weewx/wxengine.py | 13 +-- weewx.conf | 3 - 5 files changed, 101 insertions(+), 118 deletions(-) diff --git a/TODO.txt b/TODO.txt index 6b6a99ef..569b49fa 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,6 @@ TODO before the next release: + +Check the vector average windspeeds (vecavg). Get the test test_templates working. diff --git a/bin/weewx/accum.py b/bin/weewx/accum.py index b7cbace8..4ae79e77 100644 --- a/bin/weewx/accum.py +++ b/bin/weewx/accum.py @@ -11,6 +11,7 @@ etc., of a sequence of records.""" import math +import weewx class OutOfSpan(ValueError): """Raised when attempting to add a record outside of the timespan held by an accumulator""" @@ -210,14 +211,14 @@ class VecStats(object): return _result #=============================================================================== -# Class BaseAccum +# Class Accum #=============================================================================== -class BaseAccum(dict): +class Accum(dict): """Accumulates statistics for a set of observation types.""" def __init__(self, timespan): - """Initialize a BaseAccum. + """Initialize a Accum. timespan: The time period over which stats will be accumulated.""" @@ -234,11 +235,12 @@ class BaseAccum(dict): if not self.timespan.includesArchiveTime(record['dateTime']): raise OutOfSpan, "Attempt to add out-of-interval record" - # For each type... for obs_type in record: - # ... add to myself - self._add_value(record[obs_type], obs_type, record['dateTime'], add_hilo) - + # Find the proper function to use for this type ... + func = add_record_dict[obs_type] if obs_type in add_record_dict else Accum.add_value + # ... then call it. + func(self, record, obs_type, add_hilo) + def updateHiLo(self, accumulator): """Merge the high/low stats of another accumulator into me.""" if accumulator.timespan.start < self.timespan.start or accumulator.timespan.stop > self.timespan.stop: @@ -247,7 +249,7 @@ class BaseAccum(dict): self._check_units(accumulator.unit_system) for obs_type in accumulator: - self._init_type(obs_type) + self.init_type(obs_type) self[obs_type].mergeHiLo(accumulator[obs_type]) def getRecord(self): @@ -256,117 +258,114 @@ class BaseAccum(dict): # All records have a timestamp and unit type record = {'dateTime': self.timespan.stop, 'usUnits' : self.unit_system} + # Go through all observation types. for obs_type in self: - # For most observations, we want the average seen during the - # timespan: - record[obs_type] = self[obs_type].avg + # Is this type in the custom extraction dictionary? + if obs_type in extract_dict: + # Yes it is. Use the supplied function + extract_dict[obs_type](self, record, obs_type) + else: + # No. Use the default, which is the average value during the period: + record[obs_type] = self[obs_type].avg + return record def set_stats(self, obs_type, stats_tuple): - self._init_type(obs_type) + self.init_type(obs_type) self[obs_type].setStats(stats_tuple) - def _init_type(self, obs_type): + def wind_extract(self, record, obs_type): + """Extract wind values from myself, and put in a record.""" + # Wind records must be flattened into the separate categories: + record['windSpeed'] = self[obs_type].avg + record['windDir'] = self[obs_type].vec_dir + record['windGust'] = self[obs_type].max + record['windGustDir'] = self[obs_type].max_dir + + def sum_extract(self, record, obs_type): + record[obs_type] = self[obs_type].sum + + def last_extract(self, record, obs_type): + record[obs_type] = self[obs_type].last + + def init_type(self, obs_type): """Add a given observation type to my dictionary.""" # Do nothing if this type has already been initialized: if obs_type in self: return - # Assume this is a scalar type. The function can be overridden if it is - # something else. - self[obs_type] = ScalarStats() + + # Check to see if this type requires a special accumulator: + if obs_type in init_dict: + # Yes. Instantiate one and assign it. + self[obs_type] = init_dict[obs_type]() + else: + # No. Use the default ScalarStats + self[obs_type] = ScalarStats() - def _add_value(self, val, obs_type, ts, add_hilo): + def add_value(self, record, obs_type, add_hilo): """Add a single observation to myself.""" - if obs_type == 'usUnits': - self._check_units(val) - elif obs_type == 'dateTime': - return - else: - # If the type has not been seen before, initialize it - self._init_type(obs_type) - # Then add to highs/lows, and to the running sum: - if add_hilo: - self[obs_type].addHiLo(val, ts) - self[obs_type].addSum(val) + val = record[obs_type] - def _check_units(self, other_system): + # If the type has not been seen before, initialize it + self.init_type(obs_type) + # Then add to highs/lows, and to the running sum: + if add_hilo: + self[obs_type].addHiLo(val, record['dateTime']) + self[obs_type].addSum(val) + + def add_wind_value(self, record, obs_type, add_hilo): + """Add a single observation of type wind to myself.""" + + if obs_type in ['windDir', 'windGust', 'windGustDir']: + return + if weewx.debug: + assert(obs_type == 'windSpeed') + + # If the type has not been seen before, initialize it + self.init_type('wind') + # Then add to highs/lows, and to the running sum: + if add_hilo: + self['wind'].addHiLo((record.get('windGust'), record.get('windGustDir')), record['dateTime']) + self['wind'].addSum((record['windSpeed'], record.get('windDir'))) + + def _check_units(self, record, obs_type, add_hilo): + if weewx.debug: + assert(obs_type == 'usUnits') # If no unit system has been specified for me yet, adopt the incoming # system if self.unit_system is None: - self.unit_system = other_system + self.unit_system = record['usUnits'] else: # Otherwise, make sure they match - if self.unit_system != other_system: - raise ValueError("Unit system mismatch %d v. %d" % (self.unit_system, other_system)) - -#=============================================================================== -# class WXAccum -#=============================================================================== - -class WXAccum(BaseAccum): - """Subclass of BaseAccum, which adds weather-specific logic.""" - - def addRecord(self, record, add_hilo=True): - """Add a record to my running statistics. - - The record must have keys 'dateTime' and 'usUnits'. - - This is a weather-specific version.""" - - # Check to see if the record is within my observation timespan - if not self.timespan.includesArchiveTime(record['dateTime']): - raise OutOfSpan, "Attempt to add out-of-interval record" - - # This is pretty much like the loop in my superclass's version, except - # that wind is treated as a vector. - for obs_type in record: - if obs_type in ['windDir', 'windGust', 'windGustDir']: - continue - elif obs_type == 'windSpeed': - self._add_value((record['windSpeed'], record.get('windDir')), 'wind', record['dateTime'], add_hilo) - if add_hilo: - self['wind'].addHiLo((record.get('windGust'), record.get('windGustDir')), record['dateTime']) - else: - self._add_value(record[obs_type], obs_type, record['dateTime'], add_hilo) + if self.unit_system != record['usUnits']: + raise ValueError("Unit system mismatch %d v. %d" % (self.unit_system, record['usUnits'])) - def getRecord(self): - """Extract a record out of the results in the accumulator. - - This is a weather-specific version. """ - # All records have a timestamp and unit type - record = {'dateTime': self.timespan.stop, - 'usUnits' : self.unit_system} - # Go through all observation types. - for obs_type in self: - if obs_type == 'wind': - # Wind records must be flattened into the separate categories: - record['windSpeed'] = self[obs_type].avg - record['windDir'] = self[obs_type].vec_dir - record['windGust'] = self[obs_type].max - record['windGustDir'] = self[obs_type].max_dir - elif obs_type in ['rain', 'ET']: - # We need totals during the timespan, not the average: - record[obs_type] = self[obs_type].sum - elif obs_type in ['hourRain', 'dayRain', 'rain24', 'monthRain', 'yearRain', 'totalRain']: - # For these types, we want the last observation: - record[obs_type] = self[obs_type].last - else: - # For everything else, we want the average: - record[obs_type] = self[obs_type].avg - return record + def noop(self, record, obs_type, add_hilo): + pass - def _init_type(self, obs_type): +#=============================================================================== +# Configuration dictionaries +#=============================================================================== - # Do nothing if this type has already been initialized: - if obs_type in self: - return - elif obs_type == 'wind': - # Observation 'wind' requires a special vector accumulator - self['wind'] = VecStats() - else: - # Otherwise, pass on to my base class - return BaseAccum._init_type(self, obs_type) +init_dict = {'wind' : VecStats} + +add_record_dict = {'windSpeed' : Accum.add_wind_value, + 'usUnits' : Accum._check_units, + 'dateTime' : Accum.noop} + +extract_dict = {'wind' : Accum.wind_extract, + 'rain' : Accum.sum_extract, + 'ET' : Accum.sum_extract, + 'dayET' : Accum.last_extract, + 'monthET' : Accum.last_extract, + 'yearET' : Accum.last_extract, + 'hourRain' : Accum.last_extract, + 'dayRain' : Accum.last_extract, + 'rain24' : Accum.last_extract, + 'monthRain' : Accum.last_extract, + 'yearRain' : Accum.last_extract, + 'totalRain' : Accum.last_extract} diff --git a/bin/weewx/stats.py b/bin/weewx/stats.py index 5d47b1fd..3265aecf 100644 --- a/bin/weewx/stats.py +++ b/bin/weewx/stats.py @@ -108,9 +108,6 @@ class DaySummaryArchive(weewx.archive.Archive): In addition to all the tables for each type, there is one additional table called 'day__metadata', which currently holds the time of the last update. """ - # Accumulator to be used with this class: - AccumClass = weewx.accum.BaseAccum - def __init__(self, archive_db_dict, archiveSchema=None): """Initialize an instance of DaySummarArchive @@ -396,7 +393,7 @@ class DaySummaryArchive(weewx.archive.Archive): _timespan = weeutil.weeutil.archiveDaySpan(sod_ts,0) # Get an empty day accumulator: - _day_accum = self.__class__.AccumClass(_timespan) + _day_accum = weewx.accum.Accum(_timespan) _cursor = cursor if cursor else self.connection.cursor() @@ -480,9 +477,6 @@ class WXDaySummaryArchive(DaySummaryArchive): Like a regular stats database, except it understands wind, and heating- and cooling-degree days.""" - # Accumulator to be used with this class: - AccumClass = weewx.accum.WXAccum - wx_sql_create_str = "CREATE TABLE day_wind (dateTime INTEGER NOT NULL UNIQUE PRIMARY KEY, "\ "min REAL, mintime INTEGER, max REAL, maxtime INTEGER, sum REAL, count INTEGER, "\ "wsum REAL, sumtime REAL, "\ diff --git a/bin/weewx/wxengine.py b/bin/weewx/wxengine.py index 75f020eb..43be40d2 100644 --- a/bin/weewx/wxengine.py +++ b/bin/weewx/wxengine.py @@ -486,8 +486,6 @@ class StdArchive(StdService): self.setup_database(config_dict) - self.set_accumulator(config_dict) - self.bind(weewx.STARTUP, self.startup) self.bind(weewx.PRE_LOOP, self.pre_loop) self.bind(weewx.POST_LOOP, self.post_loop) @@ -589,13 +587,6 @@ class StdArchive(StdService): # In case this is a recent update or the user has dropped the daily summary tables, backfill them: self.archive.backfill_day_summary() - def set_accumulator(self, config_dict): - """Get the class to be used for the accumulator.""" - - # An alternative class can be specified in the configuration file - accumulator_class_name = config_dict['StdArchive'].get('accumulator', 'weewx.accum.WXAccum') - self.accumulator_cls = weeutil.weeutil._get_object(accumulator_class_name) - def shutDown(self): self.archive.close() @@ -632,8 +623,8 @@ class StdArchive(StdService): self.archive_interval) end_archive_ts = start_archive_ts + self.archive_interval - # Instantiate a new instance of the accumulator, using the specified class - new_accumulator = self.accumulator_cls(weeutil.weeutil.TimeSpan(start_archive_ts, end_archive_ts)) + # Instantiate a new accumulator + new_accumulator = weewx.accum.Accum(weeutil.weeutil.TimeSpan(start_archive_ts, end_archive_ts)) return new_accumulator #=============================================================================== diff --git a/weewx.conf b/weewx.conf index 837e1cb0..401e7fe3 100644 --- a/weewx.conf +++ b/weewx.conf @@ -553,9 +553,6 @@ version = 3.0.0a1 # Thereafter, the types are retrieved from the database. archive_schema = user.schemas.defaultArchiveSchema - # The accumulator class to be used. - accumulator = weewx.accum.WXAccum - # The database binding to be used. binding = wx_binding