Пример #1
0
    def __init__(self, engine, config_dict):
        # Pass the initialization information on to my superclass:
        super(BatteryAlarm, self).__init__(engine, config_dict)
        
        # This will hold the time when the last alarm message went out:
        self.last_msg_ts = 0
        # This will hold the count of the number of times the VP2 has signaled
        # a low battery alarm this archive period
        self.alarm_count = 0
        
        try:
            # Dig the needed options out of the configuration dictionary.
            # If a critical option is missing, an exception will be thrown and
            # the alarm will not be set.
            self.time_wait       = int(config_dict['Alarm'].get('time_wait', 3600))
            self.count_threshold = int(config_dict['Alarm'].get('count_threshold', 50))
            self.smtp_host       = config_dict['Alarm']['smtp_host']
            self.smtp_user       = config_dict['Alarm'].get('smtp_user')
            self.smtp_password   = config_dict['Alarm'].get('smtp_password')
            self.SUBJECT         = config_dict['Alarm'].get('subject', "Low battery alarm message from weewx")
            self.FROM            = config_dict['Alarm'].get('from', '*****@*****.**')
            self.TO              = option_as_list(config_dict['Alarm']['mailto'])
            syslog.syslog(syslog.LOG_INFO, "lowBattery: LowBattery alarm turned on. Count threshold is %d" % self.count_threshold)

            # If we got this far, it's ok to start intercepting events:
            self.bind(weewx.NEW_LOOP_PACKET,    self.newLoopPacket)
            self.bind(weewx.NEW_ARCHIVE_RECORD, self.newArchiveRecord)

        except Exception, e:
            syslog.syslog(syslog.LOG_INFO, "lowBattery: No alarm set. %s" % e)
Пример #2
0
    def __init__(self, engine, config_dict):
        # Pass the initialization information on to my superclass:
        super(BatteryAlarm, self).__init__(engine, config_dict)

        # This will hold the time when the last alarm message went out:
        self.last_msg_ts = 0
        # This will hold the count of the number of times the VP2 has signaled
        # a low battery alarm this archive period
        self.alarm_count = 0

        try:
            # Dig the needed options out of the configuration dictionary.
            # If a critical option is missing, an exception will be thrown and
            # the alarm will not be set.
            self.time_wait = int(config_dict['Alarm'].get('time_wait', 3600))
            self.count_threshold = int(config_dict['Alarm'].get(
                'count_threshold', 10))
            self.smtp_host = config_dict['Alarm']['smtp_host']
            self.smtp_user = config_dict['Alarm'].get('smtp_user')
            self.smtp_password = config_dict['Alarm'].get('smtp_password')
            self.SUBJECT = config_dict['Alarm'].get(
                'subject', "Low battery alarm message from weewx")
            self.FROM = config_dict['Alarm'].get('from', '*****@*****.**')
            self.TO = option_as_list(config_dict['Alarm']['mailto'])
        except KeyError as e:
            log.info("No alarm set.  Missing parameter: %s", e)
        else:
            # If we got this far, it's ok to start intercepting events:
            self.bind(weewx.NEW_LOOP_PACKET, self.new_loop_packet)
            self.bind(weewx.NEW_ARCHIVE_RECORD, self.new_archive_record)
            log.info("LowBattery alarm enabled. Count threshold is %d",
                     self.count_threshold)
Пример #3
0
 def __init__(self, engine, config_dict):
     # Pass the initialization information on to my superclass:
     super(MyAlarm, self).__init__(engine, config_dict)
     
     # This will hold the time when the last alarm message went out:
     self.last_msg_ts = 0
     
     try:
         # Dig the needed options out of the configuration dictionary.
         # If a critical option is missing, an exception will be thrown and
         # the alarm will not be set.
         self.expression    = config_dict['Alarm']['expression']
         self.time_wait     = int(config_dict['Alarm'].get('time_wait', 3600))
         self.smtp_host     = config_dict['Alarm']['smtp_host']
         self.smtp_user     = config_dict['Alarm'].get('smtp_user')
         self.smtp_password = config_dict['Alarm'].get('smtp_password')
         self.SUBJECT       = config_dict['Alarm'].get('subject', "Alarm message from weewx")
         self.FROM          = config_dict['Alarm'].get('from', '*****@*****.**')
         self.TO            = option_as_list(config_dict['Alarm']['mailto'])
         syslog.syslog(syslog.LOG_INFO, "alarm: Alarm set for expression: \"%s\"" % self.expression)
         
         # If we got this far, it's ok to start intercepting events:
         self.bind(weewx.NEW_ARCHIVE_RECORD, self.newArchiveRecord)    # NOTE 1
         
     except Exception, e:
         syslog.syslog(syslog.LOG_INFO, "alarm: No alarm set. %s" % e)
Пример #4
0
 def __init__(self, engine, config_dict):
     # Pass the initialization information on to my superclass:
     super(WindAlarm, self).__init__(engine, config_dict)
     
     # This will hold the time when the last alarm message went out:
     self.last_msg_ts = 0
     # En esta variable se acumula tiempo en segundos
     self.last_event_time = time.time()
     # Esta variable indica si previamente la alarma ha sido disparada
     self.in_event = False
     
     try:
         # Dig the needed options out of the configuration dictionary.
         # If a critical option is missing, an exception will be raised and
         # the alarm will not be set.
         self.expression    = config_dict['Alarm']['wind_expression']
         self.time_event    = int(config_dict['Alarm']['wind_time_event'])
         self.time_wait     = int(config_dict['Alarm']['wind_time_wait'])
         self.smtp_host     = config_dict['Alarm']['smtp_host']
         self.smtp_user     = config_dict['Alarm'].get('smtp_user')
         self.smtp_password = config_dict['Alarm'].get('smtp_password')
         self.SUBJECT       = config_dict['Alarm'].get('wind_subject', "Alarm message from weewx")
         self.FROM          = config_dict['Alarm'].get('from', '*****@*****.**')
         self.TO            = option_as_list(config_dict['Alarm']['mailto'])
         syslog.syslog(syslog.LOG_INFO, "alarm: Alarm set for wind_expression: '%s'" % self.expression)
         
         # If we got this far, it's ok to start intercepting events:
         self.bind(weewx.NEW_ARCHIVE_RECORD, self.newArchiveRecord)    # NOTE 1
         
     except KeyError, e:
         syslog.syslog(syslog.LOG_INFO, "alarm: No alarm set. %s" % e)
Пример #5
0
    def __init__(self, engine, config_dict):
        # Pass the initialization information on to my superclass:
        super(MyAlarm, self).__init__(engine, config_dict)

        # This will hold the time when the last alarm message went out:
        self.last_msg_ts = 0

        try:
            # Dig the needed options out of the configuration dictionary.
            # If a critical option is missing, an exception will be raised and
            # the alarm will not be set.
            self.expression = config_dict['Alarm']['expression']
            self.time_wait = int(config_dict['Alarm'].get('time_wait', 3600))
            self.timeout = int(config_dict['Alarm'].get('timeout', 10))
            self.smtp_host = config_dict['Alarm']['smtp_host']
            self.smtp_user = config_dict['Alarm'].get('smtp_user')
            self.smtp_password = config_dict['Alarm'].get('smtp_password')
            self.SUBJECT = config_dict['Alarm'].get(
                'subject', "Alarm message from weewx")
            self.FROM = config_dict['Alarm'].get('from', '*****@*****.**')
            self.TO = option_as_list(config_dict['Alarm']['mailto'])
            syslog.syslog(
                syslog.LOG_INFO,
                "alarm: Alarm set for expression: '%s'" % self.expression)

            # If we got this far, it's ok to start intercepting events:
            self.bind(weewx.NEW_ARCHIVE_RECORD,
                      self.new_archive_record)  # NOTE 1
        except KeyError as e:
            syslog.syslog(syslog.LOG_INFO,
                          "alarm: No alarm set.  Missing parameter: %s" % e)
Пример #6
0
    def __init__(self, config_dict, config_path, csv_config_dict,
                 import_config_path, options):

        # call our parents __init__
        super(CSVSource, self).__init__(config_dict, csv_config_dict, options)

        # save our import config path
        self.import_config_path = import_config_path
        # save our import config dict
        self.csv_config_dict = csv_config_dict

        # get a few config settings from our CSV config dict
        # csv field delimiter
        self.delimiter = str(self.csv_config_dict.get('delimiter', ','))
        # string format used to decode the imported field holding our dateTime
        self.raw_datetime_format = self.csv_config_dict.get(
            'raw_datetime_format', '%Y-%m-%d %H:%M:%S')
        # is our rain discrete or cumulative
        self.rain = self.csv_config_dict.get('rain', 'cumulative')
        # determine valid range for imported wind direction
        _wind_direction = option_as_list(
            self.csv_config_dict.get('wind_direction', '0,360'))
        try:
            if float(_wind_direction[0]) <= float(_wind_direction[1]):
                self.wind_dir = [
                    float(_wind_direction[0]),
                    float(_wind_direction[1])
                ]
            else:
                self.wind_dir = [-360, 360]
        except (KeyError, ValueError):
            self.wind_dir = [-360, 360]
        # get our source file path
        try:
            self.source = csv_config_dict['file']
        except KeyError:
            raise weewx.ViolatedPrecondition(
                "CSV source file not specified in '%s'." % import_config_path)
        # get the source file encoding, default to utf-8-sig
        self.source_encoding = self.csv_config_dict.get(
            'source_encoding', 'utf-8-sig')
        # initialise our import field-to-WeeWX archive field map
        self.map = None
        # initialise some other properties we will need
        self.start = 1
        self.end = 1
        self.increment = 1

        # tell the user/log what we intend to do
        _msg = "A CSV import from source file '%s' has been requested." % self.source
        print(_msg)
        log.info(_msg)
        _msg = "The following options will be used:"
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "     config=%s, import-config=%s" % (config_path,
                                                     self.import_config_path)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        if options.date:
            _msg = "     source=%s, date=%s" % (self.source, options.date)
        else:
            # we must have --from and --to
            _msg = "     source=%s, from=%s, to=%s" % (
                self.source, options.date_from, options.date_to)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "     dry-run=%s, calc_missing=%s, ignore_invalid_data=%s" % (
            self.dry_run, self.calc_missing, self.ignore_invalid_data)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "     tranche=%s, interval=%s, date/time_string_format=%s" % (
            self.tranche, self.interval, self.raw_datetime_format)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "     delimiter='%s', rain=%s, wind_direction=%s" % (
            self.delimiter, self.rain, self.wind_dir)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "     UV=%s, radiation=%s" % (self.UV_sensor, self.solar_sensor)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "Using database binding '%s', which is bound to database '%s'" % (
            self.db_binding_wx, self.dbm.database_name)
        print(_msg)
        log.info(_msg)
        _msg = "Destination table '%s' unit system is '%#04x' (%s)." % (
            self.dbm.table_name, self.archive_unit_sys,
            unit_nicknames[self.archive_unit_sys])
        print(_msg)
        log.info(_msg)
        if self.calc_missing:
            _msg = "Missing derived observations will be calculated."
            print(_msg)
            log.info(_msg)

        if not self.UV_sensor:
            _msg = "All WeeWX UV fields will be set to None."
            print(_msg)
            log.info(_msg)
        if not self.solar_sensor:
            _msg = "All WeeWX radiation fields will be set to None."
            print(_msg)
            log.info(_msg)
        if options.date or options.date_from:
            _msg = "Observations timestamped after %s and up to and" % timestamp_to_string(
                self.first_ts)
            print(_msg)
            log.info(_msg)
            _msg = "including %s will be imported." % timestamp_to_string(
                self.last_ts)
            print(_msg)
            log.info(_msg)
        if self.dry_run:
            _msg = "This is a dry run, imported data will not be saved to archive."
            print(_msg)
            log.info(_msg)
Пример #7
0
    def run(self):
        """Main entry point for calculating missing derived fields.

        Calculate the missing derived fields for the timespan concerned, save
        the calculated data to archive and recalculate the daily summaries.
        """

        # record the current time
        t1 = time.time()
        # obtain a wxservices.WXCalculate object to calculate the missing fields
        # first we need station altitude, latitude and longitude
        stn_dict = self.config_dict['Station']
        altitude_t = option_as_list(stn_dict.get('altitude', (None, None)))
        try:
            altitude_vt = weewx.units.ValueTuple(float(altitude_t[0]),
                                                 altitude_t[1],
                                                 "group_altitude")
        except KeyError as e:
            raise weewx.ViolatedPrecondition(
                "Value 'altitude' needs a unit (%s)" % e)
        latitude_f = float(stn_dict['latitude'])
        longitude_f = float(stn_dict['longitude'])

        # now we can create a WXCalculate object
        wxcalculate = weewx.wxservices.WXCalculate(self.config_dict,
                                                   altitude_vt,
                                                   latitude_f,
                                                   longitude_f)

        # initialise some counters so we know what we have processed
        days_updated = 0
        days_processed = 0
        total_records_processed = 0
        total_records_updated = 0

        # obtain gregorian days for our start and stop timestamps
        start_greg = weeutil.weeutil.toGregorianDay(self.start_ts)
        stop_greg = weeutil.weeutil.toGregorianDay(self.stop_ts)
        # start at the first day
        day = start_greg
        while day <= stop_greg:
            # get the start and stop timestamps for this tranche
            tr_start_ts = weeutil.weeutil.startOfGregorianDay(day)
            tr_stop_ts = min(weeutil.weeutil.startOfGregorianDay(stop_greg + 1),
                             weeutil.weeutil.startOfGregorianDay(day + self.trans_days))
            # start the transaction
            with weedb.Transaction(self.dbm.connection) as _cursor:
                # iterate over each day in the tranche we are to work in
                for tranche_day in weeutil.weeutil.genDaySpans(tr_start_ts, tr_stop_ts):
                    # initialise a counter for records processed on this day
                    records_updated = 0
                    # iterate over each record in this day
                    for record in self.dbm.genBatchRecords(startstamp=tranche_day.start,
                                                           stopstamp=tranche_day.stop):
                        # but we are only concerned with records after the
                        # start and before or equal to the stop timestamps
                        if self.start_ts < record['dateTime'] <= self.stop_ts:
                            # first obtain a list of the fields that may be calculated
                            extras_list = []
                            for obs in wxcalculate.svc_dict['Calculations']:
                                directive = wxcalculate.svc_dict['Calculations'][obs]
                                if directive == 'software' \
                                        or directive == 'prefer_hardware' and (
                                        obs not in record or record[obs] is None):
                                    extras_list.append(obs)

                            # calculate the missing derived fields for the record
                            wxcalculate.do_calculations(data_dict=record,
                                                        data_type='archive')
                            # Obtain a dict containing only those fields that
                            # WXCalculate calculated. We could do this as a
                            # dictionary comprehension but python2.6 does not
                            # support dictionary comprehensions.
                            extras_dict = {}
                            for k in extras_list:
                                if k in record.keys():
                                    extras_dict[k] = record[k]
                            # update the archive with the calculated data
                            records_updated += self.update_record_fields(record['dateTime'],
                                                                         extras_dict)
                            # update the total records processed
                            total_records_processed += 1
                        # Give the user some information on progress
                        if total_records_processed % 1000 == 0:
                            p_msg = "Processing record: %d; Last record: %s" % (total_records_processed,
                                                                                timestamp_to_string(record['dateTime']))
                            self._progress(p_msg)
                    # update the total records updated
                    total_records_updated += records_updated
                    # if we updated any records on this day increment the count
                    # of days updated
                    days_updated += 1 if records_updated > 0 else 0
                    days_processed += 1
            # advance to the next tranche
            day += self.trans_days
        # finished, so give the user some final information on progress, mainly
        # so the total tallies with the log
        p_msg = "Processing record: %d; Last record: %s" % (total_records_processed,
                                                            timestamp_to_string(tr_stop_ts))
        self._progress(p_msg, overprint=False)
        # now update the daily summaries, but only if this is not a dry run
        if not self.dry_run:
            print("Recalculating daily summaries...")
            # first we need a start and stop date object
            start_d = datetime.date.fromtimestamp(self.start_ts)
            # Since each daily summary is identified by the midnight timestamp
            # for that day we need to make sure we our stop timestamp is not on
            # a midnight boundary or we will rebuild the following days sumamry
            # as well. if it is on a midnight boundary just subtract 1 second
            # and use that.
            summary_stop_ts = self.stop_ts
            if weeutil.weeutil.isMidnight(self.stop_ts):
                summary_stop_ts -= 1
            stop_d = datetime.date.fromtimestamp(summary_stop_ts)
            # do the update
            self.dbm.backfill_day_summary(start_d=start_d, stop_d=stop_d)
            print(file=sys.stdout)
            print("Finished recalculating daily summaries")
        else:
            # it's a dry run so say the rebuild was skipped
            print("This is a dry run, recalculation of daily summaries was skipped")
        tdiff = time.time() - t1
        # we are done so log and inform the user
        _day_processed_str = "day" if days_processed == 1 else "days"
        _day_updated_str = "day" if days_updated == 1 else "days"
        if not self.dry_run:
            log.info("Processed %d %s consisting of %d records. "
                     "%d %s consisting of %d records were updated "
                     "in %0.2f seconds." % (days_processed,
                                            _day_processed_str,
                                            total_records_processed,
                                            days_updated,
                                            _day_updated_str,
                                            total_records_updated,
                                            tdiff))
        else:
            # this was a dry run
            log.info("Processed %d %s consisting of %d records. "
                     "%d %s consisting of %d records would have been updated "
                     "in %0.2f seconds." % (days_processed,
                                            _day_processed_str,
                                            total_records_processed,
                                            days_updated,
                                            _day_updated_str,
                                            total_records_updated,
                                            tdiff))
Пример #8
0
    def __init__(self, config_dict, skin_dict, gen_ts,
                 first_run, stn_info, record=None):
        # Initialise my superclass
        super(ImageStackedWindRoseGenerator, self).__init__(config_dict,
                                                            skin_dict,
                                                            gen_ts,
                                                            first_run,
                                                            stn_info,
                                                            record)

        # Get a manager for our archive
        _binding = self.config_dict['StdArchive'].get('data_binding',
                                                      'wx_binding')
        self.archive = self.db_binder.get_manager(_binding)

        self.log_success = to_bool(skin_dict.get('log_success', True))
        self.log_failure = to_bool(skin_dict.get('log_failure', True))

        # Set a few properties we will need
        self.image_dict = self.skin_dict['ImageStackedWindRoseGenerator']
        self.title_dict = self.skin_dict['Labels']['Generic']
        self.converter = Converter.fromSkinDict(self.skin_dict)

        # Set image attributes
        self.image_width = int(self.image_dict['image_width'])
        self.image_height = int(self.image_dict['image_height'])
        self.image_back_box_color = int(self.image_dict['image_background_box_color'], 0)
        self.image_back_circle_color = int(self.image_dict['image_background_circle_color'], 0)
        self.image_back_range_ring_color = int(self.image_dict['image_background_range_ring_color'], 0)
        self.image_back_image = self.image_dict['image_background_image']

        # Set compass point abbreviations

        _compass = option_as_list(self.skin_dict['Labels'].get('compass_points',
                                                               'N, S, E, W'))
        self.north = _compass[0]
        self.south = _compass[1]
        self.east = _compass[2]
        self.west = _compass[3]

        # Set windrose attributes
        self.plot_border = int(self.image_dict['windrose_plot_border'])
        self.legend_bar_width = int(self.image_dict['windrose_legend_bar_width'])
        self.font_path = self.image_dict['windrose_font_path']
        self.plot_font_size = int(self.image_dict['windrose_plot_font_size'])
        self.plot_font_color = int(self.image_dict['windrose_plot_font_color'], 0)
        self.legend_font_size = int(self.image_dict['windrose_legend_font_size'])
        self.legend_font_color = int(self.image_dict['windrose_legend_font_color'], 0)
        self.label_font_size = int(self.image_dict['windrose_label_font_size'])
        self.label_font_color = int(self.image_dict['windrose_label_font_color'], 0)
        # Look for petal colours, if not defined then set some defaults
        _colors = option_as_list(self.image_dict.get('windrose_plot_petal_colors',
                                                     DEFAULT_PETAL_COLORS))
        _colors = DEFAULT_PETAL_COLORS if len(_colors) < 7 else _colors
        self.petal_colors = []
        for _color in _colors:
            try:
                # Can it be converted to a number?
                self.petal_colors.append(int(_color, 0))
            except ValueError:  # Cannot convert to a number, assume it is
                                # a colour word so append it as is
                self.petal_colors.append(_color)
        # Get petal width, if not defined then set default to 16 (degrees)
        try:
            self.petal_width = int(self.image_dict['windrose_plot_petal_width'])
        except KeyError:
            self.petal_width = 16
        # Boundaries for speed range bands, these mark the colour boundaries
        # on the stacked bar in the legend. 7 elements only (ie 0, 10% of max,
        # 20% of max...100% of max)
        self.speedFactor = [0.0, 0.1, 0.2, 0.3, 0.5, 0.7, 1.0]

        self.period = None
        self.p_gen_ts = None
        self.label = None
        self.t_stamp = None
        self.t_stamp_loc = None
        self.obName = None
        self.dirName = None
        self.units = None
        self.max_ring_value = None
        self.label_dir = None
        self.draw = None
        self.plotFont = None
        self.legendFont = None
        self.labelFont = None
        self.roseMaxDiameter = None
        self.originX = None
        self.originY = None
        self.image = None
Пример #9
0
    def __init__(self, config_dict, config_path, csv_config_dict,
                 import_config_path, options, log):

        # call our parents __init__
        super(CSVSource, self).__init__(config_dict, csv_config_dict, options,
                                        log)

        # save our import config path
        self.import_config_path = import_config_path
        # save our import config dict
        self.csv_config_dict = csv_config_dict

        # get a few config settings from our CSV config dict
        # string format used to decode the imported field holding our dateTime
        self.raw_datetime_format = self.csv_config_dict.get(
            'raw_datetime_format', '%Y-%m-%d %H:%M:%S')
        # is our rain discrete or cumulative
        self.rain = self.csv_config_dict.get('rain', 'cumulative')
        # determine valid range for imported wind direction
        _wind_direction = option_as_list(
            self.csv_config_dict.get('wind_direction', '0,360'))
        try:
            if float(_wind_direction[0]) <= float(_wind_direction[1]):
                self.wind_dir = [
                    float(_wind_direction[0]),
                    float(_wind_direction[1])
                ]
            else:
                self.wind_dir = [-360, 360]
        except:
            self.wind_dir = [-360, 360]
        # get our source file path
        try:
            self.source = csv_config_dict['file']
        except KeyError:
            raise weewx.ViolatedPrecondition(
                "CSV source file not specified in '%s'." % import_config_path)
        # initialise our import field-to-weewx archive field map
        self.map = None
        # initialise some other properties we will need
        self.start = 1
        self.end = 1
        self.increment = 1

        # tell the user/log what we intend to do
        _msg = "A CSV import from source file '%s' has been requested." % self.source
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "The following options will be used:"
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     config=%s, import-config=%s" % (config_path,
                                                     self.import_config_path)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     source=%s, date=%s" % (self.source, options.date)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     dry-run=%s, calc-missing=%s" % (self.dry_run,
                                                     self.calc_missing)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     tranche=%s, interval=%s, date/time_string_format=%s" % (
            self.tranche, self.interval, self.raw_datetime_format)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     rain=%s, wind_direction=%s" % (self.rain, self.wind_dir)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     UV=%s, radiation=%s" % (self.UV_sensor, self.solar_sensor)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "Using database binding '%s', which is bound to database '%s'" % (
            self.db_binding_wx, self.dbm.database_name)
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "Destination table '%s' unit system is '%#04x' (%s)." % (
            self.dbm.table_name, self.archive_unit_sys,
            unit_nicknames[self.archive_unit_sys])
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        if self.calc_missing:
            print "Missing derived observations will be calculated."
        if not self.UV_sensor:
            print "All weewx UV fields will be set to None."
        if not self.solar_sensor:
            print "All weewx radiation fields will be set to None."
        if options.date:
            print "Observations timestamped after %s and up to and" % (
                timestamp_to_string(self.first_ts), )
            print "including %s will be imported." % (timestamp_to_string(
                self.last_ts), )
        if self.dry_run:
            print "This is a dry run, imported data will not be saved to archive."
Пример #10
0
    def __init__(self, config_dict, config_path, wu_config_dict, import_config_path, options, log):

        # call our parents __init__
        super(WUSource, self).__init__(config_dict,
                                       wu_config_dict,
                                       options,
                                       log)

        # save our import config path
        self.import_config_path = import_config_path
        # save our import config dict
        self.wu_config_dict = wu_config_dict

        # get our WU station ID
        try:
            self.station_id = wu_config_dict['station_id']
        except KeyError:
            raise weewx.ViolatedPrecondition("Weather Underground station ID not specified in '%s'." % import_config_path)

        # wind dir bounds
        _wind_direction = option_as_list(wu_config_dict.get('wind_direction',
                                                                '0,360'))
        try:
            if float(_wind_direction[0]) <= float(_wind_direction[1]):
                self.wind_dir = [float(_wind_direction[0]),
                                 float(_wind_direction[1])]
            else:
                self.wind_dir = [0, 360]
        except:
            self.wind_dir = [0, 360]

        # some properties we know because of the format of the returned WU data
        # WU returns a fixed format date-time string
        self.raw_datetime_format = '%Y-%m-%d %H:%M:%S'
        # WU only provides hourly rainfall and a daily cumulative rainfall.
        # We use the latter so force 'cumulative' for rain.
        self.rain = 'cumulative'

        # initialise our import field-to-weewx archive field map
        self.map = None
        # For a WU import we might have to import multiple days but we can only
        # get one day at a time from WU. So our start and end properties
        # (counters) are datetime objects and our increment is a timedelta.
        # Get datetime objects for any date or date range specified on the
        # command line, if there wasn't one then default to today.
        self.start = dt.fromtimestamp(startOfDay(self.first_ts))
        self.end = dt.fromtimestamp(startOfDay(self.last_ts))
        # set our increment
        self.increment = datetime.timedelta(days=1)

        # tell the user/log what we intend to do
        _msg = "Observation history for Weather Underground station '%s' will be imported." % self.station_id
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "The following options will be used:"
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     config=%s, import-config=%s" % (config_path,
                                                     self.import_config_path)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        if options.date:
            _msg = "     station=%s, date=%s" % (self.station_id, options.date)
        else:
            # we must have --from and --to
            _msg = "     station=%s, from=%s, to=%s" % (self.station_id,
                                                        options.date_from,
                                                        options.date_to)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     dry-run=%s, calc-missing=%s" % (self.dry_run,
                                                     self.calc_missing)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     tranche=%s, interval=%s, wind_direction=%s" % (self.tranche,
                                                                    self.interval,
                                                                    self.wind_dir)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "Using database binding '%s', which is bound to database '%s'" % (self.db_binding_wx,
                                                                                 self.dbm.database_name)
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "Destination table '%s' unit system is '%#04x' (%s)." % (self.dbm.table_name,
                                                                        self.archive_unit_sys,
                                                                        unit_nicknames[self.archive_unit_sys])
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        if self.calc_missing:
            print "Missing derived observations will be calculated."
        if options.date or options.date_from:
            print "Observations timestamped after %s and up to and" % (timestamp_to_string(self.first_ts), )
            print "including %s will be imported." % (timestamp_to_string(self.last_ts), )
        if self.dry_run:
            print "This is a dry run, imported data will not be saved to archive."
Пример #11
0
    def test_option_as_list(self):

        self.assertEqual(option_as_list("abc"), ['abc'])
        self.assertEqual(option_as_list(['a', 'b']), ['a', 'b'])
        self.assertEqual(option_as_list(None), None)
        self.assertEqual(option_as_list(''), [''])
Пример #12
0
    def __init__(self,
                 queue,
                 alert_dict,
                 server_url=OpsGenieAlerts.alert_url,
                 post_interval=3600,
                 max_backlog=0,
                 stale=None,
                 log_success=True,
                 log_failure=True,
                 timeout=60,
                 max_tries=1,
                 retry_wait=5):
        """Initialize an instance of OpsGenieAlertsThread.
        
        Required parameters:

          queue: An instance of Queue.Queue where the records will appear.

          alert: Name of the alert for tracking.
          
          config: Config dictionary for the alert.  

        Optional parameters:
        
          server_url: The OpsGenie alert URL. 
          Default is 'https://api.opsgenie.com/v1/json/alert'
          
          post_interval: How long to wait between alert notifications.
          Default is 3600 seconds (1 hour).
          
          max_backlog: How many records are allowed to accumulate in the queue
          before the queue is trimmed.
          Default is zero (no backlog at all).
          
          stale: How old a record can be and still considered useful.
          Default is None (never becomes too old).
          
          log_success: If True, log a successful post in the system log.
          Default is True.
          
          log_failure: If True, log an unsuccessful post in the system log.
          Default is True.
          
          timeout: How long to wait for the server to respond before giving up.
          Default is 10 seconds.        

          max_tries: How many times to try the post before giving up.
          Default is 3
          
          retry_wait: How long to wait between retries when failures.
          Default is 5 seconds.
        """

        super(OpsGenieAlertsThread,
              self).__init__(queue,
                             protocol_name='OpsGenieAlert',
                             post_interval=post_interval,
                             max_backlog=max_backlog,
                             stale=stale,
                             log_success=log_success,
                             log_failure=log_failure,
                             timeout=timeout,
                             max_tries=max_tries,
                             retry_wait=retry_wait)
        self.server_url = server_url
        self.apiKey = alert_dict['apiKey']
        self.Alias = alert_dict['Alias']
        self.Message = alert_dict['Message']
        self.Recipients = option_as_list(alert_dict.get('Recipients'))
        self.Entity = alert_dict.get('Entity')
        self.Source = alert_dict.get('Source')
        self.Tags = alert_dict.get('Tags')
        self.Actions = alert_dict.get('Actions')
        self.Description = alert_dict.get('Description')
        self.Details = option_as_list(alert_dict.get('Details'))
Пример #13
0
    def __init__(self, config_dict, config_path, wu_config_dict,
                 import_config_path, options, log):

        # call our parents __init__
        super(WUSource, self).__init__(config_dict, wu_config_dict, options,
                                       log)

        # save our import config path
        self.import_config_path = import_config_path
        # save our import config dict
        self.wu_config_dict = wu_config_dict

        # get our WU station ID
        try:
            self.station_id = wu_config_dict['station_id']
        except KeyError:
            raise weewx.ViolatedPrecondition(
                "Weather Underground station ID not specified in '%s'." %
                import_config_path)

        # wind dir bounds
        _wind_direction = option_as_list(
            wu_config_dict.get('wind_direction', '0,360'))
        try:
            if float(_wind_direction[0]) <= float(_wind_direction[1]):
                self.wind_dir = [
                    float(_wind_direction[0]),
                    float(_wind_direction[1])
                ]
            else:
                self.wind_dir = [0, 360]
        except:
            self.wind_dir = [0, 360]

        # some properties we know because of the format of the returned WU data
        # WU returns a fixed format date-time string
        self.raw_datetime_format = '%Y-%m-%d %H:%M:%S'
        # WU only provides hourly rainfall and a daily cumulative rainfall.
        # We use the latter so force 'cumulative' for rain.
        self.rain = 'cumulative'

        # initialise our import field-to-weewx archive field map
        self.map = None
        # For a WU import we might have to import multiple days but we can only
        # get one day at a time from WU. So our start and end properties
        # (counters) are datetime objects and our increment is a timedelta.
        # Get datetime objects for any date or date range specified on the
        # command line, if there wasn't one then default to today.
        self.start = dt.fromtimestamp(startOfDay(self.first_ts))
        self.end = dt.fromtimestamp(startOfDay(self.last_ts))
        # set our increment
        self.increment = datetime.timedelta(days=1)

        # tell the user/log what we intend to do
        _msg = "Observation history for Weather Underground station '%s' will be imported." % self.station_id
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "The following options will be used:"
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     config=%s, import-config=%s" % (config_path,
                                                     self.import_config_path)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     station=%s, date=%s" % (self.station_id, options.date)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     dry-run=%s, calc-missing=%s" % (self.dry_run,
                                                     self.calc_missing)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     tranche=%s, interval=%s, wind_direction=%s" % (
            self.tranche, self.interval, self.wind_dir)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "Using database binding '%s', which is bound to database '%s'" % (
            self.db_binding_wx, self.dbm.database_name)
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "Destination table '%s' unit system is '%#04x' (%s)." % (
            self.dbm.table_name, self.archive_unit_sys,
            unit_nicknames[self.archive_unit_sys])
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        if self.calc_missing:
            print "Missing derived observations will be calculated."
        if options.date:
            print "Observations timestamped after %s and up to and" % (
                timestamp_to_string(self.first_ts), )
            print "including %s will be imported." % (timestamp_to_string(
                self.last_ts), )
        if self.dry_run:
            print "This is a dry run, imported data will not be saved to archive."
Пример #14
0
    def run(self):
        """Main entry point for calculating missing derived fields.

        Calculate the missing derived fields for the timespan concerned, save
        the calculated data to archive and recalculate the daily summaries.
        """

        # record the current time
        t1 = time.time()
        # obtain a wxservices.WXCalculate object to calculate the missing
        # fields, first we need to get a DBBinder object ...
        db_binder = weewx.manager.DBBinder(self.config_dict)
        # ... then a database manager ...
        db_manager = db_binder.get_manager(
            data_binding=self.config_dict['StdWXCalculate']['data_binding'])
        # ... then station altitude, latitude and longitude
        stn_dict = self.config_dict['Station']
        altitude_t = option_as_list(stn_dict.get('altitude', (None, None)))
        try:
            altitude_vt = weewx.units.ValueTuple(float(altitude_t[0]),
                                                 altitude_t[1],
                                                 "group_altitude")
        except KeyError as e:
            raise weewx.ViolatedPrecondition(
                "Value 'altitude' needs a unit (%s)" % e)
        latitude_f = float(stn_dict['latitude'])
        longitude_f = float(stn_dict['longitude'])

        # now we can create a WXCalculate object
        wxcalculate = weewx.wxservices.WXCalculate(self.config_dict,
                                                   altitude_vt, latitude_f,
                                                   longitude_f, db_manager)

        # initialise some counters so we know what we have processed
        days_updated = 0
        days_processed = 0
        total_records_processed = 0
        total_records_updated = 0

        # obtain gregorian days for our start and stop timestamps
        start_greg = weeutil.weeutil.toGregorianDay(self.start_ts)
        stop_greg = weeutil.weeutil.toGregorianDay(self.stop_ts)
        # start at the first day
        day = start_greg
        while day <= stop_greg:
            # get the start and stop timestamps for this tranche
            tr_start_ts = weeutil.weeutil.startOfGregorianDay(day)
            tr_stop_ts = min(
                weeutil.weeutil.startOfGregorianDay(stop_greg + 1),
                weeutil.weeutil.startOfGregorianDay(day + self.trans_days))
            # start the transaction
            with weedb.Transaction(self.dbm.connection) as _cursor:
                # iterate over each day in the tranche we are to work in
                for tranche_day in weeutil.weeutil.genDaySpans(
                        tr_start_ts, tr_stop_ts):
                    # initialise a counter for records processed on this day
                    records_updated = 0
                    # iterate over each record in this day
                    for record in self.dbm.genBatchRecords(
                            startstamp=tranche_day.start,
                            stopstamp=tranche_day.stop):
                        # but we are only concerned with records after the
                        # start and before or equal to the stop timestamps
                        if self.start_ts < record['dateTime'] <= self.stop_ts:
                            # calculate the missing derived fields for the record
                            wxcalculate.do_calculations(data_dict=record,
                                                        data_type='archive')
                            # obtain a dict containing only those fields that
                            # WXCalculate calculated
                            extras_dict = {
                                k: record[k]
                                for k in record.keys()
                                if k in wxcalculate.calculations.keys()
                            }
                            # update the archive with the calculated data
                            records_updated += self.update_record_fields(
                                record['dateTime'], extras_dict)
                            # update the total records updated
                            total_records_updated += records_updated
                            total_records_processed += 1
                        # Give the user some information on progress
                        if total_records_processed % 1000 == 0:
                            p_msg = "Processing record: %d; Last date: %s" % (
                                total_records_processed,
                                timestamp_to_string(record['dateTime']))
                            self._progress(p_msg)
                    # if we updated any records on this day increment the count
                    # of days updated
                    days_updated += 1 if records_updated > 0 else 0
                    days_processed += 1
            # advance to the next tranche
            day += self.trans_days
        # finished, so give the user some final information on progress, mainly
        # so the total tallies with the log
        p_msg = "Processing record: %d; (%s)" % (
            total_records_processed, timestamp_to_string(tr_stop_ts))
        self._progress(p_msg, overprint=False)
        # now update the daily summaries
        print("Recalculating daily summaries...")
        # first we need a start and stop date object
        start_d = datetime.date.fromtimestamp(self.start_ts)
        stop_d = datetime.date.fromtimestamp(self.stop_ts)
        # do the update
        self.dbm.backfill_day_summary(start_d=start_d, stop_d=stop_d)
        print(file=sys.stdout)
        print("Finished recalculating daily summaries")
        tdiff = time.time() - t1
        # we are done so log and inform the user
        log.info(
            "calcmissing: Processed %d days consisting of %d records. "
            "%d days consisting of %d records were updated in %0.2f seconds." %
            (days_processed, total_records_processed, days_updated,
             total_records_updated, tdiff))

        if self.dry_run:
            log.info("calcmissing: "
                     "This was a dry run. %s was not applied." % self.name)
Пример #15
0
    def __init__(self, config_dict, config_path, csv_config_dict, import_config_path, options, log):

        # call our parents __init__
        super(CSVSource, self).__init__(config_dict,
                                        csv_config_dict,
                                        options,
                                        log)

        # save our import config path
        self.import_config_path = import_config_path
        # save our import config dict
        self.csv_config_dict = csv_config_dict

        # get a few config settings from our CSV config dict
        # string format used to decode the imported field holding our dateTime
        self.raw_datetime_format = self.csv_config_dict.get('raw_datetime_format',
                                                            '%Y-%m-%d %H:%M:%S')
        # is our rain discrete or cumulative
        self.rain = self.csv_config_dict.get('rain', 'cumulative')
        # determine valid range for imported wind direction
        _wind_direction = option_as_list(self.csv_config_dict.get('wind_direction',
                                                                  '0,360'))
        try:
            if float(_wind_direction[0]) <= float(_wind_direction[1]):
                self.wind_dir = [float(_wind_direction[0]),
                                 float(_wind_direction[1])]
            else:
                self.wind_dir = [-360, 360]
        except:
            self.wind_dir = [-360, 360]
        # get our source file path
        try:
            self.source = csv_config_dict['file']
        except KeyError:
            raise weewx.ViolatedPrecondition("CSV source file not specified in '%s'." % import_config_path)
        # initialise our import field-to-weewx archive field map
        self.map = None
        # initialise some other properties we will need
        self.start = 1
        self.end = 1
        self.increment = 1

        # tell the user/log what we intend to do
        _msg = "A CSV import from source file '%s' has been requested." % self.source
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "The following options will be used:"
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     config=%s, import-config=%s" % (config_path,
                                                     self.import_config_path)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        if options.date:
            _msg = "     source=%s, date=%s" % (self.source, options.date)
        else:
            # we must have --from and --to
            _msg = "     source=%s, from=%s, to=%s" % (self.source,
                                                       options.date_from,
                                                       options.date_to)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     dry-run=%s, calc-missing=%s" % (self.dry_run,
                                                     self.calc_missing)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     tranche=%s, interval=%s, date/time_string_format=%s" % (self.tranche,
                                                                             self.interval,
                                                                             self.raw_datetime_format)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     rain=%s, wind_direction=%s" % (self.rain, self.wind_dir)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "     UV=%s, radiation=%s" % (self.UV_sensor, self.solar_sensor)
        self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
        _msg = "Using database binding '%s', which is bound to database '%s'" % (self.db_binding_wx,
                                                                                 self.dbm.database_name)
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        _msg = "Destination table '%s' unit system is '%#04x' (%s)." % (self.dbm.table_name,
                                                                        self.archive_unit_sys,
                                                                        unit_nicknames[self.archive_unit_sys])
        self.wlog.printlog(syslog.LOG_INFO, _msg)
        if self.calc_missing:
            print "Missing derived observations will be calculated."
        if not self.UV_sensor:
            print "All weewx UV fields will be set to None."
        if not self.solar_sensor:
            print "All weewx radiation fields will be set to None."
        if options.date or options.date_from:
            print "Observations timestamped after %s and up to and" % (timestamp_to_string(self.first_ts), )
            print "including %s will be imported." % (timestamp_to_string(self.last_ts), )
        if self.dry_run:
            print "This is a dry run, imported data will not be saved to archive."
Пример #16
0
    def __init__(self, config_dict, config_path, wu_config_dict,
                 import_config_path, options):

        # call our parents __init__
        super(WUSource, self).__init__(config_dict, wu_config_dict, options)

        # save our import config path
        self.import_config_path = import_config_path
        # save our import config dict
        self.wu_config_dict = wu_config_dict

        # get our WU station ID
        try:
            self.station_id = wu_config_dict['station_id']
        except KeyError:
            _msg = "Weather Underground station ID not specified in '%s'." % import_config_path
            raise weewx.ViolatedPrecondition(_msg)

        # get our WU API key
        try:
            self.api_key = wu_config_dict['api_key']
        except KeyError:
            _msg = "Weather Underground API key not specified in '%s'." % import_config_path
            raise weewx.ViolatedPrecondition(_msg)

        # wind dir bounds
        _wind_direction = option_as_list(
            wu_config_dict.get('wind_direction', '0,360'))
        try:
            if float(_wind_direction[0]) <= float(_wind_direction[1]):
                self.wind_dir = [
                    float(_wind_direction[0]),
                    float(_wind_direction[1])
                ]
            else:
                self.wind_dir = [0, 360]
        except (IndexError, TypeError):
            self.wind_dir = [0, 360]

        # some properties we know because of the format of the returned WU data
        # WU returns a fixed format date-time string
        self.raw_datetime_format = '%Y-%m-%d %H:%M:%S'
        # WU only provides hourly rainfall and a daily cumulative rainfall.
        # We use the latter so force 'cumulative' for rain.
        self.rain = 'cumulative'

        # initialise our import field-to-WeeWX archive field map
        self.map = None
        # For a WU import we might have to import multiple days but we can only
        # get one day at a time from WU. So our start and end properties
        # (counters) are datetime objects and our increment is a timedelta.
        # Get datetime objects for any date or date range specified on the
        # command line, if there wasn't one then default to today.
        self.start = dt.fromtimestamp(startOfDay(self.first_ts))
        self.end = dt.fromtimestamp(startOfDay(self.last_ts))
        # set our increment
        self.increment = datetime.timedelta(days=1)

        # property holding the current period being processed
        self.period = None

        # tell the user/log what we intend to do
        _msg = "Observation history for Weather Underground station '%s' will be imported." % self.station_id
        print(_msg)
        log.info(_msg)
        _msg = "The following options will be used:"
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "     config=%s, import-config=%s" % (config_path,
                                                     self.import_config_path)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        if options.date:
            _msg = "     station=%s, date=%s" % (self.station_id, options.date)
        else:
            # we must have --from and --to
            _msg = "     station=%s, from=%s, to=%s" % (
                self.station_id, options.date_from, options.date_to)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _obf_api_key_msg = '='.join(
            ['     apiKey', '*' * (len(self.api_key) - 4) + self.api_key[-4:]])
        if self.verbose:
            print(_obf_api_key_msg)
        log.debug(_obf_api_key_msg)
        _msg = "     dry-run=%s, calc_missing=%s, ignore_invalid_data=%s" % (
            self.dry_run, self.calc_missing, self.ignore_invalid_data)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "     tranche=%s, interval=%s, wind_direction=%s" % (
            self.tranche, self.interval, self.wind_dir)
        if self.verbose:
            print(_msg)
        log.debug(_msg)
        _msg = "Using database binding '%s', which is bound to database '%s'" % (
            self.db_binding_wx, self.dbm.database_name)
        print(_msg)
        log.info(_msg)
        _msg = "Destination table '%s' unit system is '%#04x' (%s)." % (
            self.dbm.table_name, self.archive_unit_sys,
            unit_nicknames[self.archive_unit_sys])
        print(_msg)
        log.info(_msg)
        if self.calc_missing:
            print("Missing derived observations will be calculated.")
        if options.date or options.date_from:
            print("Observations timestamped after %s and up to and" %
                  timestamp_to_string(self.first_ts))
            print("including %s will be imported." %
                  timestamp_to_string(self.last_ts))
        if self.dry_run:
            print(
                "This is a dry run, imported data will not be saved to archive."
            )
Пример #17
0
    def test_option_as_list(self):

        self.assertEqual(option_as_list("abc"), ['abc'])
        self.assertEqual(option_as_list(['a', 'b']), ['a', 'b'])
        self.assertEqual(option_as_list(None), None)
        self.assertEqual(option_as_list(''), [''])
Пример #18
0
    def __init__(self, config_dict, skin_dict, gen_ts,
                 first_run, stn_info, record=None):
        # Initialise my superclass
        super(ImageStackedWindRoseGenerator, self).__init__(config_dict,
                                                            skin_dict,
                                                            gen_ts,
                                                            first_run,
                                                            stn_info,
                                                            record)

        # Get a manager for our archive
        _binding = self.config_dict['StdArchive'].get('data_binding',
                                                      'wx_binding')
        self.archive = self.db_binder.get_manager(_binding)

        # Set a few properties we will need
        self.image_dict = self.skin_dict['ImageStackedWindRoseGenerator']
        self.title_dict = self.skin_dict['Labels']['Generic']
        self.converter = Converter.fromSkinDict(self.skin_dict)

        # Set image attributes
        self.image_width = int(self.image_dict['image_width'])
        self.image_height = int(self.image_dict['image_height'])
        self.image_back_box_color = int(self.image_dict['image_background_box_color'], 0)
        self.image_back_circle_color = int(self.image_dict['image_background_circle_color'], 0)
        self.image_back_range_ring_color = int(self.image_dict['image_background_range_ring_color'], 0)
        self.image_back_image = self.image_dict['image_background_image']

        # Set compass point abbreviations

        _compass = option_as_list(self.skin_dict['Labels'].get('compass_points',
                                                               'N, S, E, W'))
        self.north = _compass[0]
        self.south = _compass[1]
        self.east = _compass[2]
        self.west = _compass[3]

        # Set windrose attributes
        self.plot_border = int(self.image_dict['windrose_plot_border'])
        self.legend_bar_width = int(self.image_dict['windrose_legend_bar_width'])
        self.font_path = self.image_dict['windrose_font_path']
        self.plot_font_size  = int(self.image_dict['windrose_plot_font_size'])
        self.plot_font_color = int(self.image_dict['windrose_plot_font_color'], 0)
        self.legend_font_size  = int(self.image_dict['windrose_legend_font_size'])
        self.legend_font_color = int(self.image_dict['windrose_legend_font_color'], 0)
        self.label_font_size  = int(self.image_dict['windrose_label_font_size'])
        self.label_font_color = int(self.image_dict['windrose_label_font_color'], 0)
        # Look for petal colours, if not defined then set some defaults
        _colors = option_as_list(self.image_dict.get('windrose_plot_petal_colors',
                                                     DEFAULT_PETAL_COLORS))
        _colors = DEFAULT_PETAL_COLORS if len(_colors) < 7 else _colors
        self.petal_colors=[]
        for _color in _colors:
            try:
                # Can it be converted to a number?
                self.petal_colors.append(int(_color, 0))
            except ValueError:  # Cannot convert to a number, assume it is
                                # a colour word so append it as is
                self.petal_colors.append(_color)
        # Get petal width, if not defined then set default to 16 (degrees)
        try:
            self.petal_width = int(self.image_dict['windrose_plot_petal_width'])
        except KeyError:
            self.petal_width = 16
        # Boundaries for speed range bands, these mark the colour boundaries
        # on the stacked bar in the legend. 7 elements only (ie 0, 10% of max,
        # 20% of max...100% of max)
        self.speedFactor = [0.0, 0.1, 0.2, 0.3, 0.5, 0.7, 1.0]