Hello, Martin Not exactly sure what's going on here. Could you please replace your copy of accum.py with the attached? It has been instrumented to give more information about the error.
You can find it at /usr/share/weewx/weewx/accum.py. -tk On Wed, Nov 13, 2019 at 12:01 PM Martin Nielsen <martin....@gmail.com> wrote: > Hi > > My weewx shuts down shortly after every midnight. It began immediately > after DST changed a few weeks back. But this could just be a coincidence. > > I have monit running to kick if back to action but it is annoying me - and > i have been unable to figure out what is going on.. > > I am running davis vantage pro2 iss with a metostick driver on raspberry > pi. > > Weewx is installed from a deb repository. > > > -- > 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. > To view this discussion on the web visit > https://groups.google.com/d/msgid/weewx-user/1521f01d-64fa-44c2-aaa9-50a51821fa8c%40googlegroups.com > <https://groups.google.com/d/msgid/weewx-user/1521f01d-64fa-44c2-aaa9-50a51821fa8c%40googlegroups.com?utm_medium=email&utm_source=footer> > . > -- 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. To view this discussion on the web visit https://groups.google.com/d/msgid/weewx-user/CAPq0zEDvsH3VB1bEZwJkX0gaq5WWOpBdM%3DfPs4MHCFP3Z8w2nw%40mail.gmail.com.
# # Copyright (c) 2009-2016 Tom Keffer <tkef...@gmail.com> # # See the file LICENSE.txt for your full rights. # """Statistical accumulators. They accumulate the highs, lows, averages, etc., of a sequence of records.""" import math import configobj import weewx class OutOfSpan(ValueError): """Raised when attempting to add a record outside of the timespan held by an accumulator""" #=============================================================================== # ScalarStats #=============================================================================== class ScalarStats(object): """Accumulates statistics (min, max, average, etc.) for a scalar value. Property 'last' is the last non-None value seen. Property 'lasttime' is the time it was seen. """ default_init = (None, None, None, None, 0.0, 0, 0.0, 0) def __init__(self, stats_tuple=None): self.setStats(stats_tuple) self.last = None self.lasttime = None def setStats(self, stats_tuple=None): (self.min, self.mintime, self.max, self.maxtime, self.sum, self.count, self.wsum,self.sumtime) = stats_tuple if stats_tuple else ScalarStats.default_init def getStatsTuple(self): """Return a stats-tuple. That is, a tuple containing the gathered statistics. This tuple can be used to update the stats database""" return (self.min, self.mintime, self.max, self.maxtime, self.sum, self.count, self.wsum, self.sumtime) def mergeHiLo(self, x_stats): """Merge the highs and lows of another accumulator into myself.""" if x_stats.min is not None: if self.min is None or x_stats.min < self.min: self.min = x_stats.min self.mintime = x_stats.mintime if x_stats.max is not None: if self.max is None or x_stats.max > self.max: self.max = x_stats.max self.maxtime = x_stats.maxtime if x_stats.lasttime is not None: if self.lasttime is None or x_stats.lasttime >= self.lasttime: self.lasttime = x_stats.lasttime self.last = x_stats.last def mergeSum(self, x_stats): """Merge the sum and count of another accumulator into myself.""" self.sum += x_stats.sum self.count += x_stats.count self.wsum += x_stats.wsum self.sumtime += x_stats.sumtime def addHiLo(self, val, ts): """Include a scalar value in my highs and lows. val: A scalar value ts: The timestamp. """ if val is not None: # Check for non-numbers and for NaN if not isinstance(val, (float, int)) or val != val: raise ValueError("accum: ScalarStats.addHiLo expected float or int, " "got type '%s' ('%s')" % (type(val), val)) if self.min is None or val < self.min: self.min = val self.mintime = ts if self.max is None or val > self.max: self.max = val self.maxtime = ts if self.lasttime is None or ts >= self.lasttime: self.last = val self.lasttime= ts def addSum(self, val, weight=1): """Add a scalar value to my running sum and count.""" if val is not None: # Check for non-numbers and for NaN if not isinstance(val, (float, int)) or val != val: raise ValueError("accum: ScalarStats.addSum expected float or int, " "got type '%s' ('%s')" % (type(val), val)) self.sum += val self.count += 1 self.wsum += val * weight self.sumtime += weight @property def avg(self): return self.wsum / self.sumtime if self.count else None class VecStats(object): """Accumulates statistics for a vector value. Property 'last' is the last non-None value seen. It is a two-way tuple (mag, dir). Property 'lasttime' is the time it was seen. """ default_init = (None, None, None, None, 0.0, 0, 0.0, 0, None, 0.0, 0.0, 0, 0.0, 0.0) def __init__(self, stats_tuple=None): self.setStats(stats_tuple) self.last = (None, None) self.lasttime = None def setStats(self, stats_tuple=None): (self.min, self.mintime, self.max, self.maxtime, self.sum, self.count, self.wsum,self.sumtime, self.max_dir, self.xsum, self.ysum, self.dirsumtime, self.squaresum, self.wsquaresum) = stats_tuple if stats_tuple else VecStats.default_init def getStatsTuple(self): """Return a stats-tuple. That is, a tuple containing the gathered statistics.""" return (self.min, self.mintime, self.max, self.maxtime, self.sum, self.count, self.wsum,self.sumtime, self.max_dir, self.xsum, self.ysum, self.dirsumtime, self.squaresum, self.wsquaresum) def mergeHiLo(self, x_stats): """Merge the highs and lows of another accumulator into myself.""" if x_stats.min is not None: if self.min is None or x_stats.min < self.min: self.min = x_stats.min self.mintime = x_stats.mintime if x_stats.max is not None: if self.max is None or x_stats.max > self.max: self.max = x_stats.max self.maxtime = x_stats.maxtime self.max_dir = x_stats.max_dir if x_stats.lasttime is not None: if self.lasttime is None or x_stats.lasttime >= self.lasttime: self.lasttime = x_stats.lasttime self.last = x_stats.last def mergeSum(self, x_stats): """Merge the sum and count of another accumulator into myself.""" self.sum += x_stats.sum self.count += x_stats.count self.wsum += x_stats.wsum self.sumtime += x_stats.sumtime self.xsum += x_stats.xsum self.ysum += x_stats.ysum self.dirsumtime += x_stats.dirsumtime self.squaresum += x_stats.squaresum self.wsquaresum += x_stats.wsquaresum def addHiLo(self, val, ts): """Include a vector value in my highs and lows. val: A vector value. It is a 2-way tuple (mag, dir). ts: The timestamp. """ speed, dirN = val if speed is not None: # Check for non-numbers and for NaN if not isinstance(speed, (float, int)) or speed != speed: raise ValueError("accum: VecStats.addHiLo expected float or int, " "got type '%s' ('%s')" % (type(speed), speed)) if self.min is None or speed < self.min: self.min = speed self.mintime = ts if self.max is None or speed > self.max: self.max = speed self.maxtime = ts self.max_dir = dirN if self.lasttime is None or ts >= self.lasttime: self.last = (speed, dirN) self.lasttime= ts def addSum(self, val, weight=1): """Add a vector value to my sum and squaresum. val: A vector value. It is a 2-way tuple (mag, dir) """ speed, dirN = val if speed is not None: # Check for non-numbers and for NaN if not isinstance(speed, (float, int)) or speed != speed: raise ValueError("accum: VecStats.addSum expected float or int, " "got type '%s' ('%s')" % (type(speed), speed)) self.sum += speed self.count += 1 self.wsum += weight * speed self.sumtime += weight self.squaresum += speed**2 self.wsquaresum += weight * speed**2 if dirN is not None : self.xsum += weight * speed * math.cos(math.radians(90.0 - dirN)) self.ysum += weight * speed * math.sin(math.radians(90.0 - dirN)) self.dirsumtime += weight @property def avg(self): return self.wsum / self.sumtime if self.count else None @property def rms(self): return math.sqrt(self.wsquaresum / self.sumtime) if self.count else None @property def vec_avg(self): if self.count: return math.sqrt((self.xsum**2 + self.ysum**2) / self.sumtime**2) @property def vec_dir(self): if self.dirsumtime and (self.ysum or self.xsum): _result = 90.0 - math.degrees(math.atan2(self.ysum, self.xsum)) if _result < 0.0: _result += 360.0 return _result # Return the last known direction when our vector sum is 0 return self.last[1] #=============================================================================== # Class Accum #=============================================================================== class Accum(dict): """Accumulates statistics for a set of observation types.""" def __init__(self, timespan): """Initialize a Accum. timespan: The time period over which stats will be accumulated.""" self.timespan = timespan # The unit system is left unspecified until the first observation comes in. self.unit_system = None def addRecord(self, record, add_hilo=True, weight=1): """Add a record to my running statistics. The record must have keys 'dateTime' and 'usUnits'.""" # 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") for obs_type in record: # Get the proper function ... func = get_add_function(obs_type) # ... then call it. func(self, record, obs_type, add_hilo, weight) 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: import syslog syslog.syslog(syslog.LOG_ERR, "Attempt to merge an accumulator whose timespan is not a subset") syslog.syslog(syslog.LOG_ERR, "My accumulator: %s" % self.timespan) syslog.syslog(syslog.LOG_ERR, "Their accumulator: %s" % accumulator.timespan) raise OutOfSpan("Attempt to merge an accumulator whose timespan is not a subset") self._check_units(accumulator.unit_system) for obs_type in accumulator: # Initialize the type if we have not seen it before self._init_type(obs_type) # Get the proper function ... func = get_merge_function(obs_type) # ... then call it func(self, accumulator, obs_type) def getRecord(self): """Extract a record out of the results in the accumulator.""" # All records have a timestamp and unit type record = {'dateTime': self.timespan.stop, 'usUnits' : self.unit_system} return self.augmentRecord(record) def augmentRecord(self, record): # Go through all observation types. for obs_type in self: # If the type does not appear in the record, then add it: if obs_type not in record: # Get the proper extraction function... func = get_extract_function(obs_type) # ... then call it func(self, record, obs_type) return record def set_stats(self, obs_type, stats_tuple): self._init_type(obs_type) self[obs_type].setStats(stats_tuple) # # Begin add functions. These add a record to the accumulator. # def add_value(self, record, obs_type, add_hilo, weight): """Add a single observation to myself.""" val = record[obs_type] # 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, weight=weight) def add_wind_value(self, record, obs_type, add_hilo, weight): """Add a single observation of type wind to myself.""" if obs_type in ['windDir', 'windGust', 'windGustDir']: return if weewx.debug: assert(obs_type == 'windSpeed') # First add it to regular old 'windSpeed', then # treat it like a vector. self.add_value(record, obs_type, add_hilo, weight) # If the type has not been seen before, initialize it. self._init_type('wind') # Then add to highs/lows. if add_hilo: self['wind'].addHiLo((record.get('windSpeed'), record.get('windDir')), record['dateTime']) # If the station does not provide windGustDir, then substitute windDir. # See issue #320, https://bit.ly/2HSo0ju self['wind'].addHiLo((record.get('windGust'), record.get('windGustDir', record.get('windDir'))), record['dateTime']) # Add to the running sum. self['wind'].addSum((record['windSpeed'], record.get('windDir')), weight=weight) def check_units(self, record, obs_type, add_hilo, weight): # @UnusedVariable if weewx.debug: assert(obs_type == 'usUnits') self._check_units(record['usUnits']) def noop(self, record, obs_type, add_hilo=True, weight=1): pass # # Begin hi/lo merge functions. These are called when merging two accumulators # def merge_minmax(self, x_accumulator, obs_type): """Merge value in another accumulator, using min/max""" self[obs_type].mergeHiLo(x_accumulator[obs_type]) def merge_avg(self, x_accumulator, obs_type): """Merge value in another accumulator, using avg for max""" x_stats = x_accumulator[obs_type] if x_stats.min is not None: if self[obs_type].min is None or x_stats.min < self[obs_type].min: self[obs_type].min = x_stats.min self[obs_type].mintime = x_stats.mintime if x_stats.avg is not None: if self[obs_type].max is None or x_stats.avg > self[obs_type].max: self[obs_type].max = x_stats.avg self[obs_type].maxtime = x_accumulator.timespan.stop if x_stats.lasttime is not None: if self[obs_type].lasttime is None or x_stats.lasttime >= self[obs_type].lasttime: self[obs_type].lasttime = x_stats.lasttime self[obs_type].last = x_stats.last # # Begin extraction functions. These extract a record out of the accumulator. # def extract_wind(self, record, obs_type): """Extract wind values from myself, and put in a record.""" # Wind records must be flattened into the separate categories: if 'windSpeed' not in record: record['windSpeed'] = self[obs_type].avg if 'windDir' not in record: record['windDir'] = self[obs_type].vec_dir if 'windGust' not in record: record['windGust'] = self[obs_type].max if 'windGustDir' not in record: record['windGustDir'] = self[obs_type].max_dir def extract_sum(self, record, obs_type): record[obs_type] = self[obs_type].sum def extract_last(self, record, obs_type): record[obs_type] = self[obs_type].last def extract_avg(self, record, obs_type): record[obs_type] = self[obs_type].avg def extract_min(self, record, obs_type): record[obs_type] = self[obs_type].min def extract_max(self, record, obs_type): record[obs_type] = self[obs_type].max def extract_count(self, record, obs_type): record[obs_type] = self[obs_type].count # # Miscellaneous, utility functions # 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 # Get a new accumulator of the proper type self[obs_type] = new_accumulator(obs_type) def _check_units(self, new_unit_system): # If no unit system has been specified for me yet, adopt the incoming # system if self.unit_system is None: self.unit_system = new_unit_system else: # Otherwise, make sure they match if self.unit_system != new_unit_system: raise ValueError("Unit system mismatch %d v. %d" % (self.unit_system, new_unit_system)) @property def isEmpty(self): return self.unit_system is None #=============================================================================== # Configuration dictionaries #=============================================================================== # # Mappings from convenient string names, which can be used in a config file, # to actual functions and classes # accum_types = {'scalar' : ScalarStats, 'vector' : VecStats} add_functions = {'add' : Accum.add_value, 'add_wind' : Accum.add_wind_value, 'check_units' : Accum.check_units, 'noop' : Accum.noop} merge_functions = {'minmax' : Accum.merge_minmax, 'avg' : Accum.merge_avg} extract_functions = {'avg' : Accum.extract_avg, 'sum' : Accum.extract_sum, 'min' : Accum.extract_min, 'max' : Accum.extract_max, 'count': Accum.extract_count, 'last' : Accum.extract_last, 'wind' : Accum.extract_wind, 'noop' : Accum.noop} # # Default mappings from observation types to accumulator classes and functions # defaults_ini = """ [Accumulator] [[dateTime]] adder = noop [[usUnits]] adder = check_units [[rain]] extractor = sum [[ET]] extractor = sum [[dayET]] extractor = last [[monthET]] extractor = last [[yearET]] extractor = last [[hourRain]] extractor = last [[dayRain]] extractor = last [[rain24]] extractor = last [[monthRain]] extractor = last [[yearRain]] extractor = last [[totalRain]] extractor = last [[stormRain]] extractor = last [[wind]] accumulator = vector extractor = wind [[windSpeed]] adder = add_wind merger = avg extractor = noop [[windDir]] extractor = noop [[windGust]] extractor = noop [[windGustDir]] extractor = noop """ try: # Python 2 from StringIO import StringIO except ImportError: # Python 3 from io import StringIO defaults = configobj.ConfigObj(StringIO(defaults_ini)) del StringIO accum_type_dict = None add_dict = None merge_dict = None extract_dict = None def initialize(config_dict): """Must be called before using any of the accumulators""" global defaults, accum_type_dict, merge_dict, add_dict, extract_dict accum_type_dict = {} add_dict = {} merge_dict = {} extract_dict = {} # Initialize with the default values: _initialize(defaults) # Now do the overrides from the config file _initialize(config_dict) def _initialize(config_dict): global accum_type_dict, add_dict, merge_dict, extract_dict extras = config_dict.get('Accumulator', configobj.ConfigObj({})) for obs_type in extras.sections: # Get the accumulator type accum_type = extras[obs_type].get('accumulator', 'scalar').lower() # Fail hard if this is an unknown accumulator type accum_type_dict[obs_type] = accum_types[accum_type] # Get the adder function to use add_function = extras[obs_type].get('adder', 'add').lower() # Fail hard if this is an unknown adder function add_dict[obs_type] = add_functions[add_function] # Get the Hi/Lo function to use hilo_function = extras[obs_type].get('merger', 'minmax').lower() # Fail hard if this is an unknown Hi/Lo function merge_dict[obs_type] = merge_functions[hilo_function] # Get the type of extraction function to use extract_function = extras[obs_type].get('extractor', 'avg').lower() # Fail hard if this is an unknown extraction type: extract_dict[obs_type] = extract_functions[extract_function] def new_accumulator(obs_type): global accum_type_dict # If the dictionaries have not been initialized, do so with the defaults if accum_type_dict is None: initialize(defaults) return accum_type_dict.get(obs_type, ScalarStats)() def get_add_function(obs_type): global add_dict # If the dictionaries have not been initialized, do so with the defaults if add_dict is None: initialize(defaults) return add_dict.get(obs_type, Accum.add_value) def get_merge_function(obs_type): global merge_dict # If the dictionary has not been initialized, do so with the defaults if merge_dict is None: initialize(defaults) return merge_dict.get(obs_type, Accum.merge_minmax) def get_extract_function(obs_type): global extract_dict # If the dictionaries have not been initialized, do so with the defaults if extract_dict is None: initialize(defaults) return extract_dict.get(obs_type, Accum.extract_avg)