diff --git a/bin/wee_import b/bin/wee_import
index fe975667..09a50185 100755
--- a/bin/wee_import
+++ b/bin/wee_import
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright (c) 2009-2016 Tom Keffer and
+# Copyright (c) 2009-2019 Tom Keffer and
# Gary Roderick
#
# See the file LICENSE.txt for your rights.
@@ -9,47 +9,51 @@
Compatibility:
- wee_import can import from a Comma Separated Values (CSV) format file,
- directly from the historical records of a Weather Underground Personal
- Weather Station or from one or more Cumulus monthly log files. CSV format
- files must have a comma separated list of field names on the first line.
+ wee_import can import from:
+ - a Comma Separated Values (CSV) format file
+ - the historical records of a Weather Underground Personal
+ Weather Station
+ - one or more Cumulus monthly log files
+ - one or more Weather Display monthly log files
Design
- wee_import utilises a config file (the import config file) and a number of
- command line options to control the import. The config file defines the type
- of input to be performed and the import data source as well as more advanced
- options such as field maps etc. Details of the supported command line
- parameters/options can be viewed by entering wee_import --help at the command
- line. Details of the wee_import config file settings can be found in example
- import config files distributed in the weewx/util/import directory.
+ wee_import utilises an import config file and a number of command line
+ options to control the import. The import config file defines the type of
+ input to be performed and the import data source as well as more advanced
+ options such as field maps etc. Details of the supported command line
+ parameters/options can be viewed by entering wee_import --help at the
+ command line. Details of the wee_import import config file settings can be
+ found in the example import config files distributed in the
+ weewx/util/import directory.
- wee_import utilises an abstract base class Source that defines the majority
- of the wee_import functionality. The abstract base class and other supporting
- structures are in bin/weeimport/weeimport.py. Child classes are created from
- the base class for each different import type supported by wee_import. The
- child classes set a number of import type specific properties as well as
- defining a getData() method that reads the raw data to be imported and a
- period_generator() method that generates a sequence of objects to be imported
- (eg monthly log files). This way wee_import can be extended to support other
- sources by defining a new child class, its specific properties as well as
- getData() and period_generator() methods. The child class for a given import
- type are defined in the bin/weeimport/xxximport.py files.
+ wee_import utilises an abstract base class Source that defines the majority
+ of the wee_import functionality. The abstract base class and other
+ supporting structures are in bin/weeimport/weeimport.py. Child classes are
+ created from the base class for each different import type supported by
+ wee_import. The child classes set a number of import type specific
+ properties as well as defining getData() and period_generator() methods that
+ read the raw data to be imported and generates a sequence of objects to be
+ imported (eg monthly log files) respectively. This way wee_import can be
+ extended to support other sources by defining a new child class, its
+ specific properties as well as getData() and period_generator() methods. The
+ child class for a given import type are defined in the
+ bin/weeimport/xxximport.py files.
- As with other WeeWX utilities, wee_import advises the user of basic
- configuration, action taken and results via stdout. However, since
- wee_import can make substantial changes to the WeeWX archive, wee_import also
- logs to file by default. This functionality is controlled via a command
- line option.
+ As with other WeeWX utilities, wee_import advises the user of basic
+ configuration, action taken and results via the console. However, since
+ wee_import can make substantial changes to the WeeWX archive, wee_import
+ also logs to WeeWX log system file. Console and log output can be controlled
+ via a number of command line options.
Prerequisites
- wee_import uses a number of WeeWX API calls and therefore must have a
- functional WeeWX installation. wee_import requires WeeWX 3.6.0 or later.
+ wee_import uses a number of WeeWX API calls and therefore must have a
+ functional WeeWX installation. wee_import requires WeeWX 4.0.0 or later.
Configuration
- A number of parameters can be defined in the import config file as follows:
+ A number of parameters can be defined in the import config file as follows:
# EXAMPLE WEE_IMPORT CONFIGURATION FILE
#
@@ -63,7 +67,7 @@ Configuration
# WU - import obs from a Weather Underground PWS history
# Cumulus - import obs from a one or more Cumulus monthly log files
# Format is:
-# source = (CSV | WU | Cumulus)
+# source = (CSV | WU | Cumulus | WD)
source = CSV
##############################################################################
@@ -231,6 +235,9 @@ source = CSV
# WU PWS Station ID to be used for import.
station_id = XXXXXXXX123
+ # WU API key to be used for import.
+ api_key = XXXXXXXXXXXXXXXXXXXXXX1234567890
+
#
# When importing WU data the following WeeWX database fields will be
# populated directly by the imported data (provided the corresponding data
@@ -238,13 +245,17 @@ source = CSV
# barometer
# dateTime
# dewpoint
+ # heatindex
# outHumidity
# outTemp
# radiation
# rain
+ # rainRate
+ # windchill
# windDir
# windGust
# windSpeed
+ # UV
#
# The following WeeWX database fields will be populated from other
# settings/config files:
@@ -256,10 +267,7 @@ source = CSV
# used during import:
# altimeter
# ET
- # heatindex
# pressure
- # rainRate
- # windchill
#
# The following WeeWX fields will be populated with derived values from the
# imported data provided the --calc-missing command line option is used
@@ -511,10 +519,10 @@ source = CSV
# inTemp
# outHumidity
# outTemp
- # radiation (if Cumulus data available)
- # rain (requires Cumulus 1.9.4 or later)
+ # radiation (if WD radiation data available)
+ # rain
# rainRate
- # UV (if Cumulus data available)
+ # UV (if WD UV data available)
# windDir
# windGust
# windSpeed
@@ -557,8 +565,8 @@ source = CSV
# setting is best used if the records to be imported are
# equally based in time but there are some missing records.
# This setting is recommended for WU imports.
- # To import Cumulus records it is recommended that the interval setting
- # be set to the value used in Cumulus as the 'data log interval'.
+ # To import WD records it is recommended that the interval setting be set to
+ # the value used in WD as the 'data log interval'.
# Format is:
# interval = (derive | conf | x)
interval = x
@@ -580,17 +588,17 @@ source = CSV
# calc_missing = (True | False)
calc_missing = True
- # Specify the character used as the field delimiter as Cumulus monthly log
- # files may not always use a comma to delimit fields in the monthly log
- # files. The character must be enclosed in quotes. Must not be the same
- # as the decimal setting below. Format is:
+ # Specify the character used as the field delimiter as WD monthly log files
+ # may not always use a comma to delimit fields in the monthly log files. The
+ # character must be enclosed in quotes. Must not be the same as the decimal
+ # setting below. Format is:
# delimiter = ','
delimiter = ','
- # Specify the character used as the decimal point. Cumulus monthly log
- # files may not always use a fullstop character as the decimal point. The
- # character must be enclosed in quotes. Must not be the same as the
- # delimiter setting. Format is:
+ # Specify the character used as the decimal point. WD monthly log file may
+ # not always use a full stop character as the decimal point. The character
+ # must be enclosed in quotes. Must not be the same as the delimiter setting.
+ # Format is:
# decimal = '.'
decimal = '.'
@@ -705,20 +713,27 @@ Adding a New Import Source
# Python imports
from __future__ import absolute_import
from __future__ import print_function
+
+import logging
import optparse
-import syslog
from distutils.version import StrictVersion
# WeeWX imports
+import weecfg
import weewx
import weeimport
import weeimport.weeimport
+import weeutil.logger
+import weeutil.weeutil
+
+
+log = logging.getLogger(__name__)
# wee_import version number
-WEE_IMPORT_VERSION = '0.3'
+WEE_IMPORT_VERSION = '0.4'
# minimum WeeWX version required for this version of wee_import
-REQUIRED_WEEWX = "3.6.0"
+REQUIRED_WEEWX = "4.0.0a7"
description = """Import observation data into a WeeWX archive."""
@@ -730,7 +745,6 @@ usage = """wee_import --help
[--dry-run]
[--verbose]
[--suppress-warnings]
- [--log=-]
"""
epilog = """wee_import will import data from an external source into a WeeWX
@@ -742,6 +756,9 @@ epilog = """wee_import will import data from an external source into a WeeWX
def main():
"""The main routine that kicks everything off."""
+ # Set defaults for logging:
+ weeutil.logger.setup('wee_import', {})
+
# Create a command line parser:
parser = optparse.OptionParser(description=description,
usage=usage,
@@ -749,8 +766,8 @@ def main():
# Add the various options:
parser.add_option("--config", dest="config_path", type=str,
- metavar="CONFIG_FILE", default="weewx.conf",
- help="Use WeeWX configuration file CONFIG_FILE.")
+ metavar="CONFIG_FILE",
+ help="Use configuration file CONFIG_FILE.")
parser.add_option("--import-config", dest="import_config_path", type=str,
metavar="IMPORT_CONFIG_FILE",
help="Use import configuration file IMPORT_CONFIG_FILE.")
@@ -764,15 +781,8 @@ def main():
parser.add_option("--to", dest="date_to", type=str, metavar="YYYY-mm-dd[THH:MM]",
help="Import data up until this date or date-time. Format "
"is YYYY-mm-dd[THH:MM].")
- parser.add_option("--log", dest="logging", type=str, metavar="-",
- help="Control wee_import log output. By default log output "
- "is sent to the WeeWX log file. Log output may be "
- "disabled by using '--log=-'. Some WeeWX API log "
- "output cannot be controlled by wee_import and will "
- "be sent to the default log file irrespective of the "
- "'--log' option.")
parser.add_option("--verbose", action="store_true", dest="verbose",
- help="Print useful extra output.")
+ help="Print and log useful extra output.")
parser.add_option("--suppress-warnings", action="store_true", dest="suppress",
help="Suppress warnings to stdout. Warnings are still logged.")
parser.add_option("--version", dest="version", action="store_true",
@@ -787,74 +797,103 @@ def main():
weewx.__version__))
exit(1)
+ # get config_dict to use
+ config_path, config_dict = weecfg.read_config(options.config_path, args)
+ print("Using WeeWX configuration file %s" % config_path)
+
+ # Set weewx.debug as necessary:
+ weewx.debug = weeutil.weeutil.to_int(config_dict.get('debug', 0))
+
+ # Now we can set up the user customized logging:
+ weeutil.logger.setup('wee_import', config_dict)
+
# display wee_import version info
if options.version:
print("wee_import version: %s" % WEE_IMPORT_VERSION)
exit(0)
- # Set up logging
- wlog = weeimport.weeimport.WeeImportLog(options.logging,
- options.verbose,
- options.suppress,
- options.dry_run)
+ # to do anything more we need an import config file, check if one was
+ # provided
+ if options.import_config_path:
+ # we have something so try to start
- # advise the user we are starting up
- wlog.printlog(syslog.LOG_INFO, "Starting wee_import...")
+ # advise the user we are starting up
+ print("Starting wee_import...")
+ log.info("Starting wee_import...")
- # If we got this far we must want to import something so get a Source
- # object from our factory and try to import. Be prepared to catch any
- # errors though.
- try:
- source_obj = weeimport.weeimport.Source.sourceFactory(options,
- args,
- wlog)
- source_obj.run()
- except weeimport.weeimport.WeeImportOptionError as e:
- wlog.printlog(syslog.LOG_INFO, "**** Command line option error.")
- wlog.printlog(syslog.LOG_INFO, "**** %s" % e)
- print("**** Nothing done, exiting.")
- wlog.logonly(syslog.LOG_INFO, "**** Nothing done.")
- exit(1)
- except weeimport.weeimport.WeeImportIOError as e:
- wlog.printlog(syslog.LOG_INFO, "**** Unable to load source file.")
- wlog.printlog(syslog.LOG_INFO, "**** %s" % e)
- print("**** Nothing done, exiting.")
- wlog.logonly(syslog.LOG_INFO, "**** Nothing done.")
- exit(1)
- except weeimport.weeimport.WeeImportFieldError as e:
- wlog.printlog(syslog.LOG_INFO, "**** Unable to map source data.")
- wlog.printlog(syslog.LOG_INFO, "**** %s" % e)
- print("**** Nothing done, exiting.")
- wlog.logonly(syslog.LOG_INFO, "**** Nothing done.")
- exit(1)
- except weeimport.weeimport.WeeImportMapError as e:
- wlog.printlog(syslog.LOG_INFO,
- "**** Unable to parse source-to-WeeWX field map.")
- wlog.printlog(syslog.LOG_INFO, "**** %s" % e)
- print("**** Nothing done, exiting.")
- wlog.logonly(syslog.LOG_INFO, "**** Nothing done.")
- exit(1)
- except (weewx.ViolatedPrecondition, weewx.UnsupportedFeature) as e:
- wlog.printlog(syslog.LOG_INFO, "**** %s" % e)
- print("**** Nothing done, exiting.")
- wlog.logonly(syslog.LOG_INFO, "**** Nothing done.")
+ # If we got this far we must want to import something so get a Source
+ # object from our factory and try to import. Be prepared to catch any
+ # errors though.
+ try:
+ source_obj = weeimport.weeimport.Source.sourceFactory(options,
+ args)
+ source_obj.run()
+ except weeimport.weeimport.WeeImportOptionError as e:
+ print("**** Command line option error.")
+ log.info("**** Command line option error.")
+ print("**** %s" % e)
+ log.info("**** %s" % e)
+ print("**** Nothing done, exiting.")
+ log.info("**** Nothing done.")
+ exit(1)
+ except weeimport.weeimport.WeeImportIOError as e:
+ print("**** Unable to load source file.")
+ log.info("**** Unable to load source file.")
+ print("**** %s" % e)
+ log.info("**** %s" % e)
+ print("**** Nothing done, exiting.")
+ log.info("**** Nothing done.")
+ exit(1)
+ except weeimport.weeimport.WeeImportFieldError as e:
+ print("**** Unable to map source data.")
+ log.info("**** Unable to map source data.")
+ print("**** %s" % e)
+ log.info("**** %s" % e)
+ print("**** Nothing done, exiting.")
+ log.info("**** Nothing done.")
+ exit(1)
+ except weeimport.weeimport.WeeImportMapError as e:
+ print("**** Unable to parse source-to-WeeWX field map.")
+ log.info("**** Unable to parse source-to-WeeWX field map.")
+ print("**** %s" % e)
+ log.info("**** %s" % e)
+ print("**** Nothing done, exiting.")
+ log.info("**** Nothing done.")
+ exit(1)
+ except (weewx.ViolatedPrecondition, weewx.UnsupportedFeature) as e:
+ print("**** %s" % e)
+ log.info("**** %s" % e)
+ print("**** Nothing done, exiting.")
+ log.info("**** Nothing done.")
+ print()
+ parser.print_help()
+ exit(1)
+ except SystemExit as e:
+ print(e)
+ exit(0)
+ except (ValueError, weewx.UnitError) as e:
+ print("**** %s" % e)
+ log.info("**** %s" % e)
+ print("**** Nothing done, exiting.")
+ log.info("**** Nothing done.")
+ exit(1)
+ except IOError as e:
+ print("**** Unable to load config file.")
+ log.info("**** Unable to load config file.")
+ print("**** %s" % e)
+ log.info("**** %s" % e)
+ print("**** Nothing done, exiting.")
+ log.info("**** Nothing done.")
+ exit(1)
+ else:
+ # we have no import config file so display a suitable message followed
+ # by the help text then exit
+ print("**** No import config file specified.")
+ print("**** Nothing done.")
print()
parser.print_help()
exit(1)
- except SystemExit as e:
- print(e)
- exit(0)
- except (ValueError, weewx.UnitError) as e:
- wlog.printlog(syslog.LOG_INFO, "**** %s" % e)
- print("**** Nothing done, exiting.")
- wlog.logonly(syslog.LOG_INFO, "**** Nothing done.")
- exit(1)
- except IOError as e:
- wlog.printlog(syslog.LOG_INFO, "**** Unable to load config file.")
- wlog.printlog(syslog.LOG_INFO, "**** %s" % e)
- print("**** Nothing done, exiting.")
- wlog.logonly(syslog.LOG_INFO, "**** Nothing done.")
- exit(1)
+
# execute our main code
if __name__ == "__main__":
diff --git a/bin/weecfg/tests/expected/weewx40_user_expected.conf b/bin/weecfg/tests/expected/weewx40_user_expected.conf
index 5ffc73e3..05ef28a1 100644
--- a/bin/weecfg/tests/expected/weewx40_user_expected.conf
+++ b/bin/weecfg/tests/expected/weewx40_user_expected.conf
@@ -26,7 +26,7 @@ log_failure = True
socket_timeout = 20
# Do not modify this - it is used by setup.py when installing and updating.
-version = 4.0.0a8
+version = 4.0.0a9
##############################################################################
diff --git a/bin/weeimport/csvimport.py b/bin/weeimport/csvimport.py
index ef3bc9f6..8f999708 100644
--- a/bin/weeimport/csvimport.py
+++ b/bin/weeimport/csvimport.py
@@ -15,8 +15,8 @@ from __future__ import print_function
# Python imports
import csv
+import logging
import os
-import syslog
# WeeWX imports
from . import weeimport
@@ -25,6 +25,8 @@ import weewx
from weeutil.weeutil import timestamp_to_string, option_as_list
from weewx.units import unit_nicknames
+log = logging.getLogger(__name__)
+
# ============================================================================
# class CSVSource
@@ -42,13 +44,12 @@ class CSVSource(weeimport.Source):
# these details are specified by the user in the wee_import config file.
_header_map = None
- def __init__(self, config_dict, config_path, csv_config_dict, import_config_path, options, log):
+ 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,
- log)
+ options)
# save our import config path
self.import_config_path = import_config_path
@@ -86,12 +87,17 @@ class CSVSource(weeimport.Source):
# 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)
+ print(_msg)
+ log.info(_msg)
_msg = "The following options will be used:"
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " config=%s, import-config=%s" % (config_path,
self.import_config_path)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
if options.date:
_msg = " source=%s, date=%s" % (self.source, options.date)
else:
@@ -99,37 +105,62 @@ class CSVSource(weeimport.Source):
_msg = " source=%s, from=%s, to=%s" % (self.source,
options.date_from,
options.date_to)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " rain=%s, wind_direction=%s" % (self.rain, self.wind_dir)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " UV=%s, radiation=%s" % (self.UV_sensor, self.solar_sensor)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ 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])
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
if self.calc_missing:
- print("Missing derived observations will be calculated.")
+ _msg = "Missing derived observations will be calculated."
+ print(_msg)
+ log.info(_msg)
+
if not self.UV_sensor:
- print("All WeeWX UV fields will be set to None.")
+ _msg = "All WeeWX UV fields will be set to None."
+ print(_msg)
+ log.info(_msg)
if not self.solar_sensor:
- print("All WeeWX radiation fields will be set to None.")
+ _msg = "All WeeWX radiation fields will be set to None."
+ print(_msg)
+ log.info(_msg)
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))
+ _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:
- print("This is a dry run, imported data will not be saved to archive.")
+ _msg = "This is a dry run, imported data will not be saved to archive."
+ print(_msg)
+ log.info(_msg)
def getRawData(self, period):
"""Obtain an iterable containing the raw data to be imported.
diff --git a/bin/weeimport/cumulusimport.py b/bin/weeimport/cumulusimport.py
index e5f93518..4bce8969 100644
--- a/bin/weeimport/cumulusimport.py
+++ b/bin/weeimport/cumulusimport.py
@@ -15,8 +15,8 @@ from __future__ import print_function
# Python imports
import csv
import glob
+import logging
import os
-import syslog
import time
# WeeWX imports
@@ -26,6 +26,8 @@ import weewx
from weeutil.weeutil import timestamp_to_string
from weewx.units import unit_nicknames
+log = logging.getLogger(__name__)
+
# Dict to lookup rainRate units given rain units
rain_units_dict = {'inch': 'inch_per_hour', 'mm': 'mm_per_hour'}
@@ -89,13 +91,12 @@ class CumulusSource(weeimport.Source):
'cur_app_temp': {'map_to': 'appTemp'}
}
- def __init__(self, config_dict, config_path, cumulus_config_dict, import_config_path, options, log):
+ def __init__(self, config_dict, config_path, cumulus_config_dict, import_config_path, options):
# call our parents __init__
super(CumulusSource, self).__init__(config_dict,
cumulus_config_dict,
- options,
- log)
+ options)
# save our import config path
self.import_config_path = import_config_path
@@ -207,7 +208,8 @@ class CumulusSource(weeimport.Source):
try:
self.source = cumulus_config_dict['directory']
except KeyError:
- raise weewx.ViolatedPrecondition("Cumulus monthly logs directory not specified in '%s'." % import_config_path)
+ _msg = "Cumulus monthly logs directory not specified in '%s'." % import_config_path
+ raise weewx.ViolatedPrecondition(_msg)
# property holding the current log file name being processed
self.file_name = None
@@ -223,34 +225,49 @@ class CumulusSource(weeimport.Source):
# tell the user/log what we intend to do
_msg = "Cumulus monthly log files in the '%s' directory will be imported" % self.source
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
_msg = "The following options will be used:"
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " config=%s, import-config=%s" % (config_path,
self.import_config_path)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
if options.date:
_msg = " date=%s" % options.date
else:
# we must have --from and --to
_msg = " from=%s, to=%s" % (options.date_from, options.date_to)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " tranche=%s, interval=%s" % (self.tranche,
self.interval)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " UV=%s, radiation=%s" % (self.UV_sensor, self.solar_sensor)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ 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])
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
if self.calc_missing:
print("Missing derived observations will be calculated.")
if not self.UV_sensor:
diff --git a/bin/weeimport/wdimport.py b/bin/weeimport/wdimport.py
index bee7cff1..98f3e0ca 100644
--- a/bin/weeimport/wdimport.py
+++ b/bin/weeimport/wdimport.py
@@ -17,9 +17,9 @@ import collections
import csv
import datetime
import glob
+import logging
import operator
import os
-import syslog
import time
# WeeWX imports
@@ -30,6 +30,7 @@ import weewx
from weeutil.weeutil import timestamp_to_string
from weewx.units import unit_nicknames
+log = logging.getLogger(__name__)
# ============================================================================
# class WDSource
@@ -183,11 +184,10 @@ class WDSource(weeimport.Source):
'hum7': {'units': 'percent', 'map_to': 'extraHumid7'}
}
- def __init__(self, config_dict, config_path, wd_config_dict, import_config_path, options, log):
+ def __init__(self, config_dict, config_path, wd_config_dict, import_config_path, options):
# call our parents __init__
- super(WDSource, self).__init__(config_dict, wd_config_dict,
- options, log)
+ super(WDSource, self).__init__(config_dict, wd_config_dict, options)
# save the import config path
self.import_config_path = import_config_path
@@ -397,48 +397,71 @@ class WDSource(weeimport.Source):
# tell the user/log what we intend to do
_msg = "Weather Display monthly log files in the '%s' directory will be imported" % self.source
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
_msg = "The following options will be used:"
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " config=%s, import-config=%s" % (config_path,
self.import_config_path)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
if options.date:
_msg = " date=%s" % options.date
else:
# we must have --from and --to
_msg = " from=%s, to=%s" % (options.date_from, options.date_to)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
if log_unit_sys is not None and log_unit_sys.upper() in ['METRIC', 'US']:
# valid unit system specified
_msg = " monthly logs are in %s units" % log_unit_sys.upper()
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
else:
# group units specified
_msg = " monthly logs use the following units:"
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " temperature=%s pressure=%s" % (temp_u, press_u)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " rain=%s speed=%s" % (rain_u, speed_u)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " tranche=%s, interval=%s" % (self.tranche,
self.interval)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " UV=%s, radiation=%s ignore extreme temperature and humidity=%s" % (self.UV_sensor,
self.solar_sensor,
self.ignore_extreme_temp_hum)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ 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])
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
if self.calc_missing:
print("Missing derived observations will be calculated.")
if not self.UV_sensor:
@@ -512,7 +535,8 @@ class WDSource(weeimport.Source):
_msg = "Unexpected number of columns found in '%s': %s v %s" % (_fn,
len(_row.split(_del)),
len(self.logs[lg]['fields']))
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
# make sure we have full stops as decimal points
_clean_data.append(_row.replace(self.decimal, '.'))
diff --git a/bin/weeimport/weeimport.py b/bin/weeimport/weeimport.py
index 19dc5c7f..53b47f88 100644
--- a/bin/weeimport/weeimport.py
+++ b/bin/weeimport/weeimport.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2009-2016 Tom Keffer and
+# Copyright (c) 2009-2019 Tom Keffer and
# Gary Roderick
#
# See the file LICENSE.txt for your full rights.
@@ -15,9 +15,10 @@ from __future__ import absolute_import
# Python imports
import datetime
+import logging
+import numbers
import re
import sys
-import syslog
import time
from datetime import datetime as dt
@@ -32,10 +33,12 @@ import weewx.wxservices
from weewx.manager import open_manager_with_config
from weewx.units import unit_constants, unit_nicknames, convertStd, to_std_system, ValueTuple
-from weeutil.weeutil import timestamp_to_string, option_as_list, to_int, tobool, _get_object
+from weeutil.weeutil import timestamp_to_string, option_as_list, to_int, tobool, get_object
+
+log = logging.getLogger(__name__)
# List of sources we support
-SUPPORTED_SOURCES = ['CSV', 'WU', 'Cumulus']
+SUPPORTED_SOURCES = ['CSV', 'WU', 'Cumulus', 'WD']
# Minimum requirements in any explicit or implicit WeeWX field-to-import field
# map
@@ -114,7 +117,7 @@ class Source(object):
# reg expression to match any HTML tag of the form <...>
_tags = re.compile(r'\<.*\>')
- def __init__(self, config_dict, import_config_dict, options, log):
+ def __init__(self, config_dict, import_config_dict, options):
"""A generic initialisation.
Set some realistic default values for options read from the import
@@ -123,8 +126,8 @@ class Source(object):
know what records to import.
"""
- # give our source object some logging abilities
- self.wlog = log
+# # give our source object some logging abilities
+# self.wlog = log
# save our WeeWX config dict
self.config_dict = config_dict
@@ -198,6 +201,7 @@ class Source(object):
# Process our command line options
self.dry_run = options.dry_run
self.verbose = options.verbose
+ self.suppress = options.suppress
# By processing any --date, --from and --to options we need to derive
# self.first_ts and self.last_ts; the earliest and latest (inclusive)
@@ -216,7 +220,7 @@ class Source(object):
_msg = "Invalid --date option specified."
raise WeeImportOptionError(_msg)
else:
- # we have a valid date so do soem date arithmetic
+ # we have a valid date so do some date arithmetic
_last_dt = _first_dt + datetime.timedelta(days=1)
self.first_ts = time.mktime(_first_dt.timetuple())
self.last_ts = time.mktime(_last_dt.timetuple())
@@ -293,7 +297,7 @@ class Source(object):
self.period_duplicates = set()
@staticmethod
- def sourceFactory(options, args, log):
+ def sourceFactory(options, args):
"""Factory to produce a Source object.
Returns an appropriate object depending on the source type. Raises a
@@ -302,9 +306,7 @@ class Source(object):
# get some key WeeWX parameters
# first the config dict to use
- config_path, config_dict = weecfg.read_config(None,
- args,
- file_name=options.config_path)
+ config_path, config_dict = weecfg.read_config(options.config_path, args)
# get wee_import config dict if it exists
import_config_path, import_config_dict = weecfg.read_config(None,
args,
@@ -329,12 +331,11 @@ class Source(object):
module_class = '.'.join(['weeimport',
source.lower() + 'import',
source + 'Source'])
- return _get_object(module_class)(config_dict,
- config_path,
- import_config_dict.get(source, {}),
- import_config_path,
- options,
- log)
+ return get_object(module_class)(config_dict,
+ config_path,
+ import_config_dict.get(source, {}),
+ import_config_path,
+ options)
def run(self):
"""Main entry point for importing from an external source.
@@ -374,25 +375,40 @@ class Source(object):
# get the raw data
_msg = 'Obtaining raw import data for period %d...' % self.period_no
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
_raw_data = self.getRawData(period)
_msg = 'Raw import data read successfully for period %d.' % self.period_no
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
# map the raw data to a WeeWX archive compatible dictionary
_msg = 'Mapping raw import data for period %d...' % self.period_no
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
_mapped_data = self.mapRawData(_raw_data, self.archive_unit_sys)
_msg = 'Raw import data mapped successfully for period %d.' % self.period_no
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
# save the mapped data to archive
- _msg = 'Saving mapped data to archive for period %d...' % self.period_no
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
+ # first advise the user and log, but only if its not a dry run
+ if not self.dry_run:
+ _msg = 'Saving mapped data to archive for period %d...' % self.period_no
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
self.saveToArchive(archive, _mapped_data)
- _msg = 'Mapped data saved to archive successfully for period %d.' % self.period_no
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
-
+ # advise the user and log, but only if its not a dry run
+ if not self.dry_run:
+ _msg = 'Mapped data saved to archive successfully for period %d.' % self.period_no
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
# increment our period counter
self.period_no += 1
# Provide some summary info now that we have finished the import.
@@ -401,37 +417,48 @@ class Source(object):
if self.total_rec_proc == 0:
# nothing imported so say so
_msg = 'No records were identified for import. Exiting. Nothing done.'
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
else:
# we imported something
total_rec = self.total_rec_proc + self.total_duplicate_rec
if self.dry_run:
# but it was a dry run
- self.wlog.printlog(syslog.LOG_INFO, "Finished dry run import")
+ _msg = "Finished dry run import"
+ print(_msg)
+ log.info(_msg)
_msg = "%d records were processed and %d unique records would "\
"have been imported." % (total_rec,
self.total_rec_proc)
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
if self.total_duplicate_rec > 1:
_msg = "%d duplicate records were ignored." % self.total_duplicate_rec
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
elif self.total_duplicate_rec == 1:
- self.wlog.printlog(syslog.LOG_INFO,
- "1 duplicate record was ignored.")
+ _msg = "1 duplicate record was ignored."
+ print(_msg)
+ log.info(_msg)
else:
# something should have been saved to database
- self.wlog.printlog(syslog.LOG_INFO, "Finished import")
+ _msg = "Finished import"
+ print(_msg)
+ log.info(_msg)
_msg = "%d records were processed and %d unique records " \
"imported in %.2f seconds." % (total_rec,
self.total_rec_proc,
self.tdiff)
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
if self.total_duplicate_rec > 1:
_msg = "%d duplicate records were ignored." % self.total_duplicate_rec
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
elif self.total_duplicate_rec == 1:
- self.wlog.printlog(syslog.LOG_INFO,
- "1 duplicate record was ignored.")
+ _msg = "1 duplicate record was ignored."
+ print(_msg)
+ log.info(_msg)
print("Those records with a timestamp already in the archive will not have been")
print("imported. Confirm successful import in the WeeWX log file.")
@@ -573,9 +600,8 @@ class Source(object):
# will use
_msg = "The following imported field-to-WeeWX field map will be used:"
if self.verbose:
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
- else:
- self.wlog.logonly(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
for _key, _val in six.iteritems(_map):
if 'field_name' in _val:
_units_msg = ""
@@ -585,9 +611,8 @@ class Source(object):
_units_msg,
_key)
if self.verbose:
- self.wlog.verboselog(syslog.LOG_INFO, _msg)
- else:
- self.wlog.logonly(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
else:
# no [[FieldMap]] stanza and no _header_map so raise an error as we
# don't know what to map
@@ -635,7 +660,7 @@ class Source(object):
_msg = "Field '%s' not found in source data." % self.map['dateTime']['field_name']
raise WeeImportFieldError(_msg)
# now process the raw date time data
- if _raw_dateTime.isdigit():
+ if isinstance(_raw_dateTime, numbers.Number) or _raw_dateTime.isdigit():
# Our dateTime is a number, is it a timestamp already?
# Try to use it and catch the error if there is one and
# raise it higher.
@@ -647,8 +672,8 @@ class Source(object):
_raw_dateTime)
raise ValueError(_msg)
else:
- # it's a string so try to parse it and catch the error if
- # there is one and raise it higher
+ # it's a non-numeric string so try to parse it and catch
+ # the error if there is one and raise it higher
try:
_datetm = time.strptime(_raw_dateTime,
self.raw_datetime_format)
@@ -661,7 +686,7 @@ class Source(object):
# if we have a timeframe of concern does our record fall within
# it
if (self.first_ts is None and self.last_ts is None) or \
- self.first_ts <= _rec_dateTime <= self.last_ts:
+ self.first_ts < _rec_dateTime <= self.last_ts:
# we have no timeframe or if we do it falls within it so
# save the dateTime
_rec['dateTime'] = _rec_dateTime
@@ -735,6 +760,20 @@ class Source(object):
# can't catch the error
try:
_temp = float(_row[self.map[_field]['field_name']].strip())
+ except AttributeError:
+ # the data has not strip() attribute so chances are
+ # it's a number already
+ if isinstance(_row[self.map[_field]['field_name']], numbers.Number):
+ _temp = _row[self.map[_field]['field_name']]
+ elif _row[self.map[_field]['field_name']] is None:
+ _temp = None
+ else:
+ # we raise the error
+ _msg = "%s: cannot convert '%s' to float at " \
+ "timestamp '%s'." % (_field,
+ _row[self.map[_field]['field_name']],
+ timestamp_to_string(_rec['dateTime']))
+ raise ValueError(_msg)
except TypeError:
# perhaps we have a None, so return None for our field
_temp = None
@@ -813,19 +852,19 @@ class Source(object):
if self.map[_field]['field_name'] not in _warned:
_msg = "Warning: Import field '%s' is mapped to WeeWX " \
"field '%s' but the" % (self.map[_field]['field_name'],
- _field)
- self.wlog.printlog(syslog.LOG_INFO,
- _msg,
- can_suppress=True)
+ _field)
+ if not self.suppress:
+ print(_msg)
+ log.info(_msg)
_msg = " import field '%s' could not be found " \
"in one or more records." % self.map[_field]['field_name']
- self.wlog.printlog(syslog.LOG_INFO,
- _msg,
- can_suppress=True)
+ if not self.suppress:
+ print(_msg)
+ log.info(_msg)
_msg = " WeeWX field '%s' will be set to 'None' in these records." % _field
- self.wlog.printlog(syslog.LOG_INFO,
- _msg,
- can_suppress=True)
+ if not self.suppress:
+ print(_msg)
+ log.info(_msg)
# make sure we do this warning once only
_warned.append(self.map[_field]['field_name'])
# if we have a mapped field for a unit system with a valid value,
@@ -865,7 +904,8 @@ class Source(object):
# we had more than one unique value for interval, warn the user
_msg = "Warning: Records to be imported contain multiple " \
"different 'interval' values."
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
print(" This may mean the imported data is missing some records and it may lead")
print(" to data integrity issues. If the raw data has a known, fixed interval")
print(" value setting the relevant 'interval' setting in wee_import config to")
@@ -887,15 +927,20 @@ class Source(object):
print("Import aborted by user. No records saved to archive.")
_msg = "User chose to abort import. %d records were processed. " \
"Exiting." % self.total_rec_proc
- self.wlog.logonly(syslog.LOG_INFO, _msg)
+ log.info(_msg)
raise SystemExit('Exiting. Nothing done.')
- self.wlog.verboselog(syslog.LOG_INFO,
- "Mapped %d records." % len(_records))
+ _msg = "Mapped %d records." % len(_records)
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
# the user wants to continue or we have only one unique value for
# interval so return the records
return _records
else:
- self.wlog.verboselog(syslog.LOG_INFO, "Mapped 0 records.")
+ _msg = "Mapped 0 records."
+ if self.verbose:
+ print(_msg)
+ log.info(_msg)
# we have no records to return so return None
return None
@@ -945,16 +990,16 @@ class Source(object):
if _interval < 0:
# so raise an error
_msg = "Cannot derive 'interval' for record timestamp: %s." % timestamp_to_string(current_ts)
- self.wlog.printlog(syslog.LOG_INFO, _msg)
- raise ValueError(
- "Raw data is not in ascending date time order.")
+ print(_msg)
+ log.info(_msg)
+ raise ValueError("Raw data is not in ascending date time order.")
except TypeError:
_interval = None
return _interval
else:
# we don't know what to do so raise an error
- raise ValueError(
- "Cannot derive 'interval'. Unknown 'interval' setting in %s." % self.import_config_path)
+ _msg = "Cannot derive 'interval'. Unknown 'interval' setting in %s." % self.import_config_path
+ raise ValueError(_msg)
@staticmethod
def getRain(last_rain, current_rain):
@@ -1101,7 +1146,7 @@ class Source(object):
# tell the user what we have done
_msg = "Unique records processed: %d; Last timestamp: %s\r" % (nrecs,
timestamp_to_string(_final_rec['dateTime']))
- print(_msg, end=' ', file=sys.stdout)
+ print(_msg, end='', file=sys.stdout)
sys.stdout.flush()
_tranche = []
# we have processed all records but do we have any records left
@@ -1118,7 +1163,7 @@ class Source(object):
# tell the user what we have done
_msg = "Unique records processed: %d; Last timestamp: %s\r" % (nrecs,
timestamp_to_string(_final_rec['dateTime']))
- print(_msg, end=' ', file=sys.stdout)
+ print(_msg, end='', file=sys.stdout)
print()
sys.stdout.flush()
# update our counts
@@ -1133,11 +1178,14 @@ class Source(object):
else:
_msg = " %d duplicate records were identified in period %d:" % (num_duplicates,
self.period_no)
- self.wlog.printlog(syslog.LOG_INFO, _msg, can_suppress=True)
+ if not self.suppress:
+ print(_msg)
+ log.info(_msg)
for ts in sorted(self.period_duplicates):
_msg = " %s" % timestamp_to_string(ts)
- self.wlog.printlog(syslog.LOG_INFO, _msg,
- can_suppress=True)
+ if not self.suppress:
+ print(_msg)
+ log.info(_msg)
# add the period duplicates to the overall duplicates
self.duplicates |= self.period_duplicates
# reset the period duplicates
@@ -1145,8 +1193,9 @@ class Source(object):
elif self.ans == 'n':
# user does not want to import so display a message and then
# ask to exit
- self.wlog.printlog(syslog.LOG_INFO,
- 'User chose not to import records. Exiting. Nothing done.')
+ _msg = "User chose not to import records. Exiting. Nothing done."
+ print(_msg)
+ log.info(_msg)
raise SystemExit('Exiting. Nothing done.')
else:
# we have no records to import, advise the user but what we say
@@ -1163,74 +1212,6 @@ class Source(object):
self.tdiff = time.time() - self.t1
-# ============================================================================
-# class WeeImportLog
-# ============================================================================
-
-
-class WeeImportLog(object):
- """Class to handle wee_import logging.
-
- This class provides a wrapper around the python syslog module to handle
- wee_import logging requirements. The --log=- command line option disables
- log output otherwise log output is sent to the same log used by WeeWX.
- """
-
- def __init__(self, opt_logging, opt_verbose, opt_suppress, opt_dry_run):
- """Initialise our log environment."""
-
- # first check if we are turning off log to file or not
- if opt_logging:
- log_bool = opt_logging.strip() == '-'
- else:
- log_bool = False
- # Flag to indicate whether we are logging to file or not. Log to file
- # every time except when logging is explicitly turned off on the
- # command line or its a dry run.
- self.log = not (opt_dry_run or log_bool)
- # if we are logging then setup our syslog environment
- # if --verbose we log up to syslog.LOG_DEBUG
- # otherwise just log up to syslog.LOG_INFO
- if self.log:
- syslog.openlog('wee_import',
- logoption=syslog.LOG_PID | syslog.LOG_CONS)
- if opt_verbose:
- syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
- else:
- syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO))
- # logging by other modules (eg WxCalculate) does not use WeeImportLog
- # but we can disable most logging by raising the log priority if its a
- # dry run
- if opt_dry_run:
- syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_CRIT))
- # keep opt_verbose for later
- self.verbose = opt_verbose
- self.suppress = opt_suppress
-
- def logonly(self, level, message):
- """Log to file only."""
-
- # are we logging ?
- if self.log:
- # add a little preamble to say this is wee_import
- _message = 'wee_import: ' + message
- syslog.syslog(level, _message)
-
- def printlog(self, level, message, can_suppress=False):
- """Print to screen and log to file."""
-
- if not(can_suppress and self.suppress):
- print(message)
- self.logonly(level, message)
-
- def verboselog(self, level, message):
- """Print to screen if --verbose and log to file always."""
-
- if self.verbose:
- print(message)
- self.logonly(level, message)
-
-
# ============================================================================
# Utility functions
# ============================================================================
@@ -1247,4 +1228,3 @@ def get_binding(config_dict):
else:
db_binding_wx = None
return db_binding_wx
-
diff --git a/bin/weeimport/wuimport.py b/bin/weeimport/wuimport.py
index 8014d332..c2189c5b 100644
--- a/bin/weeimport/wuimport.py
+++ b/bin/weeimport/wuimport.py
@@ -1,24 +1,31 @@
#
-# Copyright (c) 2009-2016 Tom Keffer and
+# Copyright (c) 2009-2019 Tom Keffer and
# Gary Roderick
#
# See the file LICENSE.txt for your full rights.
#
-"""Module to interact with Weather Underground PWS history and import raw
-observational data for use with weeimport.
+"""Module to interact with the Weather Underground API and obtain raw
+observational data for use with wee_import.
"""
# Python imports
from __future__ import with_statement
from __future__ import absolute_import
from __future__ import print_function
-import csv
+
import datetime
+import gzip
+import json
+import logging
+import numbers
import socket
-import syslog
+import sys
+
from datetime import datetime as dt
+# python3 compatibility shims
+import six
from six.moves import urllib
# WeeWX imports
@@ -28,6 +35,7 @@ import weewx
from weeutil.weeutil import timestamp_to_string, option_as_list, startOfDay
from weewx.units import unit_nicknames
+log = logging.getLogger(__name__)
# ============================================================================
# class WUSource
@@ -35,53 +43,43 @@ from weewx.units import unit_nicknames
class WUSource(weeimport.Source):
- """Class to interact with the Weather Underground.
+ """Class to interact with the Weather Underground API.
- Uses WXDailyHistory.asp call via http to obtain historical daily weather
- observations for a given PWS. WU uses geolocation of the requester to
- determine the units to use when providing historical PWS records. Fields
- that can be provided with multiple possible units have the units in use
- appended to the returned field name. This means that a request for a user
- in a given location for historical data from a given station may well
- return different results to the same request being made from another
- location. This requires a mechanism to both determine the units in use from
- returned data as well as mapping a number of different possible field names
- to a given WeeWX archive field name.
+ Uses PWS history call via http to obtain historical daily weather
+ observations for a given PWS. Unlike the previous WU import module the use
+ of the API requires an API key.
"""
# Dict to map all possible WU field names to WeeWX archive field names and
# units
- _header_map = {'Time': {'units': 'unix_epoch', 'map_to': 'dateTime'},
- 'TemperatureC': {'units': 'degree_C', 'map_to': 'outTemp'},
- 'TemperatureF': {'units': 'degree_F', 'map_to': 'outTemp'},
- 'DewpointC': {'units': 'degree_C', 'map_to': 'dewpoint'},
- 'DewpointF': {'units': 'degree_F', 'map_to': 'dewpoint'},
- 'PressurehPa': {'units': 'hPa', 'map_to': 'barometer'},
- 'PressureIn': {'units': 'inHg', 'map_to': 'barometer'},
- 'WindDirectionDegrees': {'units': 'degree_compass',
- 'map_to': 'windDir'},
- 'WindSpeedKMH': {'units': 'km_per_hour',
+ _header_map = {'epoch': {'units': 'unix_epoch', 'map_to': 'dateTime'},
+ 'tempAvg': {'units': 'degree_F', 'map_to': 'outTemp'},
+ 'dewptAvg': {'units': 'degree_F', 'map_to': 'dewpoint'},
+ 'heatindexAvg': {'units': 'degree_F', 'map_to': 'heatindex'},
+ 'windchillAvg': {'units': 'degree_F', 'map_to': 'windchill'},
+ 'pressureAvg': {'units': 'inHg', 'map_to': 'barometer'},
+ 'winddirAvg': {'units': 'degree_compass',
+ 'map_to': 'windDir'},
+ 'windspeedAvg': {'units': 'mile_per_hour',
'map_to': 'windSpeed'},
- 'WindSpeedMPH': {'units': 'mile_per_hour',
- 'map_to': 'windSpeed'},
- 'WindSpeedGustKMH': {'units': 'km_per_hour',
- 'map_to': 'windGust'},
- 'WindSpeedGustMPH': {'units': 'mile_per_hour',
- 'map_to': 'windGust'},
- 'Humidity': {'units': 'percent', 'map_to': 'outHumidity'},
- 'dailyrainMM': {'units': 'mm', 'map_to': 'rain'},
- 'dailyrainin': {'units': 'inch', 'map_to': 'rain'},
- 'SolarRadiationWatts/m^2': {'units': 'watt_per_meter_squared',
- 'map_to': 'radiation'}
+ 'windgustHigh': {'units': 'mile_per_hour',
+ 'map_to': 'windGust'},
+ 'humidityAvg': {'units': 'percent', 'map_to': 'outHumidity'},
+ 'precipTotal': {'units': 'inch', 'map_to': 'rain'},
+ 'precipRate': {'units': 'inch_per_hour',
+ 'map_to': 'rainRate'},
+ 'solarRadiationHigh': {'units': 'watt_per_meter_squared',
+ 'map_to': 'radiation'},
+ 'uvHigh': {'units': 'uv_index', 'map_to': 'UV'}
}
+ _extras = ['pressureMin', 'pressureMax']
- def __init__(self, config_dict, config_path, wu_config_dict, import_config_path, options, log):
+ 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,
- log)
+ options)
# save our import config path
self.import_config_path = import_config_path
@@ -92,7 +90,15 @@ class WUSource(weeimport.Source):
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)
+ _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',
@@ -130,12 +136,17 @@ class WUSource(weeimport.Source):
# 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)
+ print(_msg)
+ log.info(_msg)
_msg = "The following options will be used:"
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
_msg = " config=%s, import-config=%s" % (config_path,
self.import_config_path)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ log.debug(_msg)
if options.date:
_msg = " station=%s, date=%s" % (self.station_id, options.date)
else:
@@ -143,22 +154,35 @@ class WUSource(weeimport.Source):
_msg = " station=%s, from=%s, to=%s" % (self.station_id,
options.date_from,
options.date_to)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ 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)
- self.wlog.verboselog(syslog.LOG_DEBUG, _msg)
+ if self.verbose:
+ print(_msg)
+ 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)
+ 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)
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ 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])
- self.wlog.printlog(syslog.LOG_INFO, _msg)
+ print(_msg)
+ log.info(_msg)
if self.calc_missing:
print("Missing derived observations will be calculated.")
if options.date or options.date_from:
@@ -171,16 +195,9 @@ class WUSource(weeimport.Source):
"""Get raw observation data and construct a map from WU to WeeWX
archive fields.
- Obtain raw observational data from WU using a http WXDailyHistory
- request. This raw data needs to be cleaned of unnecessary
- characters/codes and an iterable returned.
-
- Since WU geolocates any http request we do not know what units our WU
- data will use until we actually receive the data. A further
- complication is that WU appends the unit abbreviation to the end of the
- returned field name for fields that can have different units. So once
- we have the data have received the response we need to determine the
- units and create a dict to map the WU fields to WeeWX archive fields.
+ Obtain raw observational data from WU via the WU API. This raw data
+ needs some basic processing to place it in a format suitable for
+ wee_import to ingest.
Input parameters:
@@ -188,48 +205,129 @@ class WUSource(weeimport.Source):
which raw obs data will be read.
"""
- # the date for which we want the WU data is held in a datetime object, we need to convert it to a timetuple
- date_tt = period.timetuple()
- # construct our URL using station ID and day, month, year
- _url = "http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=%s&" \
- "month=%d&day=%d&year=%d&format=1" % (self.station_id,
- date_tt[1],
- date_tt[2],
- date_tt[0])
- # hit the WU site, wrap in a try..except so we can catch any errors
+ # the date for which we want the WU data is held in a datetime object,
+ # we need to convert it to a timetuple
+ day_tt = period.timetuple()
+ # and then format the date suitable for use in the WU API URL
+ day = "%4d%02d%02d" % (day_tt.tm_year,
+ day_tt.tm_mon,
+ day_tt.tm_mday)
+
+ # construct the URL to be used
+ url = "https://api.weather.com/v2/pws/history/all?" \
+ "stationId=%s&format=json&units=e&numericPrecision=decimal&date=%s&apiKey=%s" \
+ % (self.station_id, day, self.api_key)
+ # create a Request object using the constructed URL
+ request_obj = urllib.request.Request(url)
+ # add necessary headers
+ request_obj.add_header('Cache-Control', 'no-cache')
+ request_obj.add_header('Accept-Encoding', 'gzip')
+ # hit the API wrapping in a try..except to catch any errors
try:
- _wudata = urllib.request.urlopen(_url)
+ response = urllib.request.urlopen(request_obj)
except urllib.error.URLError as e:
- self.wlog.printlog(syslog.LOG_ERR,
- "Unable to open Weather Underground station %s" % self.station_id)
- self.wlog.printlog(syslog.LOG_ERR, " **** %s" % e)
+ print("Unable to open Weather Underground station " + self.station_id, " or ", e, file=sys.stderr)
+ log.error("Unable to open Weather Underground station %s or %s" % (self.station_id, e))
raise
except socket.timeout as e:
- self.wlog.printlog(syslog.LOG_ERR,
- "Socket timeout for Weather Underground station %s" % self.station_id)
- self.wlog.printlog(syslog.LOG_ERR, " **** %s" % e)
+ print("Socket timeout for Weather Underground station " + self.station_id, file=sys.stderr)
+ log.error("Socket timeout for Weather Underground station %s" % self.station_id)
+ print(" **** %s" % e, file=sys.stderr)
+ log.error(" **** %s" % e)
raise
+ # check the response code and raise an exception if there was an error
+ if hasattr(response, 'code') and response.code != 200:
+ if response.code == 204:
+ raise IOError("Probably a bad station ID or invalid date")
+ else:
+ raise IOError("Bad response code returned: %d" % response.code)
- # because the data comes back with lots of HTML tags and whitespace we
- # need a bit of logic to clean it up.
- _cleanWUdata = []
- for _row in _wudata:
- # Convert from byte-string to string
- _urow = _row.decode('ascii')
- # get rid of any HTML tags
- _line = ''.join(WUSource._tags.split(_urow))
- # get rid of any blank lines
- if _line != "\n":
- # save what's left
- _cleanWUdata.append(_line)
+ # The WU API says that compression is required, but let's be prepared
+ # if compression is not used
+ if response.info().get('Content-Encoding') == 'gzip':
+ buf = six.StringIO(response.read())
+ f = gzip.GzipFile(fileobj=buf)
+ _raw_data = f.read()
+ # decode the json data
+ _raw_decoded_data = json.loads(_raw_data)
+ else:
+ _raw_data = response
+ # decode the json data
+ _raw_decoded_data = json.load(_raw_data)
- # now create a dictionary CSV reader, the first line is used as keys to
- # the dictionary
- _reader = csv.DictReader(_cleanWUdata)
+ # The raw WU response is not suitable to return as is, we need to
+ # return an iterable that provides a dict of observational data for each
+ # available timestamp. In this case a list of dicts is appropriate.
+
+ # initialise a list of dicts
+ wu_data = []
+ # first check we have some observational data
+ if 'observations' in _raw_decoded_data:
+ # iterate over each record in the WU data
+ for record in _raw_decoded_data['observations']:
+ # initialise a dict to hold the resulting data for this record
+ _flat_record = {}
+ # iterate over each WU API response field that we can use
+ _fields = self._header_map.keys() + self._extras
+ for obs in _fields:
+ # The field may appear as a top level field in the WU data
+ # or it may be embedded in the dict in the WU data that
+ # contains variable unit data. Look in the top level record
+ # first. If its there uses it, otherwise look in the
+ # variable units dict. If it can't be fond then skip it.
+ if obs in record:
+ # it's in the top level record
+ _flat_record[obs] = record[obs]
+ else:
+ # it's not in the top level so look in the variable
+ # units dict
+ try:
+ _flat_record[obs] = record['imperial'][obs]
+ except KeyError:
+ # it's not there so skip it
+ pass
+ if obs == 'epoch':
+ try:
+ _date = datetime.date.fromtimestamp(_flat_record['epoch'])
+ except ValueError:
+ _flat_record['epoch'] = _flat_record['epoch'] // 1000
+ # WU in its wisdom provides min and max pressure but no average
+ # pressure (unlike other obs) so we need to calculate it. If
+ # both min and max are numeric use a simple average of the two
+ # (they will likely be the same anyway for non-RF stations).
+ # Otherwise use max if numeric, then use min if numeric
+ # otherwise skip.
+ self.calc_pressure(_flat_record)
+ # append the data dict for the current record to the list of
+ # dicts for this period
+ wu_data.append(_flat_record)
# finally, get our database-source mapping
- self.map = self.parseMap('WU', _reader, self.wu_config_dict)
- # return our dict reader
- return _reader
+ self.map = self.parseMap('WU', wu_data, self.wu_config_dict)
+ # return our dict
+ return wu_data
+
+ @staticmethod
+ def calc_pressure(record):
+ """Calculate pressureAvg field.
+
+ The WU API provides min and max pressure but no average pressure.
+ Calculate an average pressure to be used in the import using one of the
+ following (in order):
+
+ 1. simple average of min and max pressure
+ 2. max pressure
+ 3. min pressure
+ 4. None
+ """
+
+ if 'pressureMin' in record and 'pressureMax' in record and isinstance(record['pressureMin'], numbers.Number) and isinstance(record['pressureMax'], numbers.Number):
+ record['pressureAvg'] = (record['pressureMin'] + record['pressureMax'])/2.0
+ elif 'pressureMax' in record and isinstance(record['pressureMax'], numbers.Number):
+ record['pressureAvg'] = record['pressureMax']
+ elif 'pressureMin' in record and isinstance(record['pressureMin'], numbers.Number):
+ record['pressureAvg'] = record['pressureMin']
+ elif 'pressureMin' in record or 'pressureMax' in record:
+ record['pressureAvg'] = None
def period_generator(self):
"""Generator function yielding a sequence of datetime objects.
diff --git a/bin/weeutil/weeutil.py b/bin/weeutil/weeutil.py
index b93050ef..4301f40c 100644
--- a/bin/weeutil/weeutil.py
+++ b/bin/weeutil/weeutil.py
@@ -389,7 +389,9 @@ def archiveHoursAgoSpan(time_ts, hours_ago=0, grace=1):
def archiveSpanSpan(time_ts, time_delta=0, hour_delta=0, day_delta=0, week_delta=0, month_delta=0, year_delta=0):
""" Returns a TimeSpan for the last xxx seconds where xxx equals
time_delta sec + hour_delta hours + day_delta days + week_delta weeks + month_delta months + year_delta years
- Note: For month_delta, 1 month = 30 days, For year_delta, 1 year = 365 days
+
+ NOTE: Use of month_delta and year_delta is deprecated.
+ See issue #436 (https://github.com/weewx/weewx/issues/436)
Example:
>>> os.environ['TZ'] = 'Australia/Brisbane'
@@ -1095,7 +1097,7 @@ def latlon_string(ll, hemi, which, format_list=None):
hemi[0] if ll >= 0 else hemi[1])
-def _get_object(module_class):
+def get_object(module_class):
"""Given a string with a module class name, it imports and returns the class."""
# Split the path into its parts
parts = module_class.split('.')
@@ -1115,6 +1117,10 @@ def _get_object(module_class):
return mod
+# For backwards compatibility:
+_get_object = get_object
+
+
class GenWithPeek(object):
"""Generator object which allows a peek at the next object to be returned.
diff --git a/bin/weewx/__init__.py b/bin/weewx/__init__.py
index 8c302308..90cacc7d 100644
--- a/bin/weewx/__init__.py
+++ b/bin/weewx/__init__.py
@@ -7,7 +7,7 @@
from __future__ import absolute_import
import time
-__version__="4.0.0a8"
+__version__="4.0.0a9"
# Holds the program launch time in unix epoch seconds:
# Useful for calculating 'uptime.'
diff --git a/bin/weewx/cheetahgenerator.py b/bin/weewx/cheetahgenerator.py
index ce39cabe..81f3e8e9 100644
--- a/bin/weewx/cheetahgenerator.py
+++ b/bin/weewx/cheetahgenerator.py
@@ -186,7 +186,7 @@ class CheetahGenerator(weewx.reportengine.ReportGenerator):
x = c.strip()
if x:
# Get the class
- class_ = weeutil.weeutil._get_object(x)
+ class_ = weeutil.weeutil.get_object(x)
# Then instantiate the class, passing self as the sole argument
self.search_list_objs.append(class_(self))
diff --git a/bin/weewx/engine.py b/bin/weewx/engine.py
index 97b300db..b1d31c92 100644
--- a/bin/weewx/engine.py
+++ b/bin/weewx/engine.py
@@ -142,7 +142,7 @@ class StdEngine(object):
# passing self and the configuration dictionary as the
# arguments:
log.debug("Loading service %s", svc)
- self.service_obj.append(weeutil.weeutil._get_object(svc)(self, config_dict))
+ self.service_obj.append(weeutil.weeutil.get_object(svc)(self, config_dict))
log.debug("Finished loading service %s", svc)
except Exception:
# An exception occurred. Shut down any running services, then
@@ -949,8 +949,8 @@ def main(options, args, engine_class=StdEngine):
except Terminate:
log.info("Terminating weewx version %s", weewx.__version__)
weeutil.logger.log_traceback(log.info, " **** ")
- # Reraise the exception (this should cause the program to exit)
- raise
+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
+ os.kill(0, signal.SIGTERM)
# Catch any keyboard interrupts and log them
except KeyboardInterrupt:
diff --git a/bin/weewx/manager.py b/bin/weewx/manager.py
index ee6f2b04..187bbf09 100644
--- a/bin/weewx/manager.py
+++ b/bin/weewx/manager.py
@@ -1015,7 +1015,7 @@ def get_manager_dict_from_config(config_dict, data_binding,
manager_dict['schema'] = [(col_name, manager_dict['schema'][col_name]) for col_name in manager_dict['schema']]
else:
# Schema is a string, with the name of the schema object
- manager_dict['schema'] = weeutil.weeutil._get_object(schema_name)
+ manager_dict['schema'] = weeutil.weeutil.get_object(schema_name)
return manager_dict
@@ -1031,7 +1031,7 @@ def get_manager_dict(bindings_dict, databases_dict, data_binding,
def open_manager(manager_dict, initialize=False):
- manager_cls = weeutil.weeutil._get_object(manager_dict['manager'])
+ manager_cls = weeutil.weeutil.get_object(manager_dict['manager'])
if initialize:
return manager_cls.open_with_create(manager_dict['database_dict'],
manager_dict['table_name'],
@@ -1093,7 +1093,7 @@ def drop_database_with_config(config_dict, data_binding,
def show_progress(nrec, last_time):
"""Utility function to show our progress while backfilling"""
print("Records processed: %d; Last date: %s\r" %
- (nrec, weeutil.weeutil.timestamp_to_string(last_time)), end=' ', file=sys.stdout)
+ (nrec, weeutil.weeutil.timestamp_to_string(last_time)), end='', file=sys.stdout)
sys.stdout.flush()
diff --git a/bin/weewx/reportengine.py b/bin/weewx/reportengine.py
index bb1d1381..5f7cff7d 100644
--- a/bin/weewx/reportengine.py
+++ b/bin/weewx/reportengine.py
@@ -182,7 +182,7 @@ class StdReportEngine(threading.Thread):
try:
# Instantiate an instance of the class.
- obj = weeutil.weeutil._get_object(generator)(
+ obj = weeutil.weeutil.get_object(generator)(
self.config_dict,
skin_dict,
self.gen_ts,
diff --git a/bin/weewx/restx.py b/bin/weewx/restx.py
index 1926f319..9d378d9b 100644
--- a/bin/weewx/restx.py
+++ b/bin/weewx/restx.py
@@ -270,7 +270,7 @@ class RESTThread(threading.Thread):
if dbmanager is None:
# If we don't have a database, we can't do anything
- if self.log_failure:
+ if self.log_failure and weewx.debug >= 2:
log.debug("No database specified. Augmentation from database skipped.")
return record
@@ -1041,8 +1041,8 @@ class WOWThread(AmbientThread):
# WOW signals a bad login with a HTML Error 403 code:
if e.code == 403:
raise BadLogin(e)
- elif e.code == 429:
- raise FailedPost("Too many requests; data already seen; or too out of date.")
+ elif e.code >= 400:
+ raise FailedPost(e)
else:
raise
else:
diff --git a/docs/changes.txt b/docs/changes.txt
index bac39f76..7f84462e 100644
--- a/docs/changes.txt
+++ b/docs/changes.txt
@@ -43,10 +43,11 @@ Ported OS uptime to OpenBSD. Fixes issue #428. Thanks to user Jeff Ross!
Catch SSL certificate errors in uploaders. Retry after an hour. Fixes
issue #413.
-Wunderfixer has been ported to the new WU API. This API requires an
-API key, which you can get from the WU. Put it in weewx.conf.
-Unfortunately, WU is very unreliable whether reposts "stick".
-Fixes issue #414
+Wunderfixer has been ported to the new WU API. This API requires an API key,
+which you can get from the WU. Put it in weewx.conf. Unfortunately, WU is
+very unreliable whether reposts "stick". Fixes issues #414 and #445.
+
+Wee_import can now import Weather Display monthly log files.
Fixed problem where sub-sections DegreeDays and Trend were located under the
wrong weewx.conf section. Fixes issue #432. Thanks to user mph for spotting
@@ -134,6 +135,9 @@ Fixes issue #431. Thanks to user ls4096!
Fixed problem that can cause an exception with restx services that do not use
the database manager. See commit 459ccb1.
+Sending a SIGTERM signal to weewxd now causes it to exit with status
+128 + signal#. PR #442. Thanks to user sshambar!
+
3.9.1 02/06/2019
diff --git a/docs/customizing.htm b/docs/customizing.htm
index 7356c580..7ae05413 100644
--- a/docs/customizing.htm
+++ b/docs/customizing.htm
@@ -2436,18 +2436,6 @@ or in foobar units: $day.barometer.min.foobar
| The maximum barometric pressure over the last immediate 2 weeks.
|
-
- | $month_delta=months |
- $span($month_delta=3).outTemp.min |
- The minimum temperture over the last immediate 3 months (90 days).
- |
-
-
- | $year_delta=years |
- $span($year_delta=1).windchill.min |
- The minimum wind chill over the last immediate 1 year (365 days).
- |
-
@@ -2838,11 +2826,16 @@ Sunrise, sunset: $almanac.sunrise $almanac.sunset
$almanac(pressure=0, horizon=-6).sun(use_center=1).rise
The general syntax is:
- $almanac(pressure=pressure, horizon=horizon,
- temperature=temperature_C).heavenly_body(use_center=[01]).attribute
+ $almanac(almanac_time=time, ## Unix epoch time
+ lat=latitude, lon=longitude, ## degrees
+ altitude=altitude, ## meters
+ pressure=pressure, ## mbars
+ horizon=horizon, ## degrees
+ temperature=temperature_C ## degrees C
+ ).heavenly_body(use_center=[01]).attribute
- As you can see, in addition to the horizon angle, you can also override atmospheric pressure and temperature
- (degrees Celsius).
+
+ As you can see, many other properties can be overridden besides pressure and the horizon angle.
diff --git a/docs/usersguide.htm b/docs/usersguide.htm
index a5125297..d7063fcd 100644
--- a/docs/usersguide.htm
+++ b/docs/usersguide.htm
@@ -107,9 +107,11 @@ Version: 4.0
consumes about 5% of the CPU, 100 MB of virtual memory, and 20 MB of real memory.
- WeeWX also runs great on a Raspberry Pi, although report generation will take longer. For example, the 12 "To
- Date" templates take about 5.1 seconds on the RPi, compared to 3.0 seconds on my Fit-PC, and a mere 0.9
- seconds on my vintage Dell Optiplex 745.
+
+ WeeWX also runs great on a Raspberry Pi, although report generation will take longer. For example,
+ the 12 "To Date" templates of the "Standard" report take about 5.1 seconds on a RPi B+,
+ compared to 3.0 seconds on my Fit-PC, 0.9 seconds on my vintage Dell Optiplex 745, and 0.3 seconds on
+ a NUC with a 4th gen i5 processor.
Time
diff --git a/docs/utilities.htm b/docs/utilities.htm
index 8c871a64..4f832450 100644
--- a/docs/utilities.htm
+++ b/docs/utilities.htm
@@ -1013,14 +1013,13 @@ No extensions installed
[--dry-run]
[--verbose]
[--suppress-warnings]
- [--log=-]
Import observation data into a WeeWX archive.
Options:
-h, --help show this help message and exit
- --config=CONFIG_FILE Use WeeWX configuration file CONFIG_FILE.
+ --config=CONFIG_FILE Use configuration file CONFIG_FILE.
--import-config=IMPORT_CONFIG_FILE
Use import configuration file IMPORT_CONFIG_FILE.
--dry-run Print what would happen but do not do it.
@@ -1031,13 +1030,7 @@ Options:
--to=YYYY-mm-dd[THH:MM]
Import data up until this date or date-time. Format is
YYYY-mm-dd[THH:MM].
- --log=- Control wee_import log output. By default log output
- is sent to the WeeWX log file. Log output may be
- disabled by using '--log=-'. Some WeeWX API log output
- cannot be controlled by wee_import and will be sent to
- the default log file irrespective of the '--log'
- option.
- --verbose Print useful extra output.
+ --verbose Print and log useful extra output.
--suppress-warnings Suppress warnings to stdout. Warnings are still
logged.
--version Display wee_import version number.
@@ -1191,19 +1184,6 @@ summaries using the wee_database utility.
importing from Weather Underground or to import all available records when importing from any other source.
- Option --log
-
- The --log option controls the wee_import log output.
- Omitting the option will result in wee_import log output being sent to the WeeWX
- log file (nominally the system log, refer to Monitoring
- WeeWX and Where to find things to
- find it). wee_import log output can be disabled by using --log=-.
- The --log option is used as follows:
-
-
- wee_import --import-config=/directory/import.conf --log=-
-
-
Option --verbose
Inclusion of the --verbose option will cause additional information to be printed
@@ -1221,7 +1201,7 @@ summaries using the wee_database utility.
a given timestamp or there being no data found for a mapped import field. These warnings do not necessarily
require action, but they can consist of extensive output and thus make it difficult to follow the import
progress. Irrespective of whether --suppress-warnings is used all warnings are sent
- to log unless the --log=- option is used.
+ to log.
wee_import --import-config=/directory/import.conf --suppress-warnings
@@ -1255,7 +1235,7 @@ summaries using the wee_database utility.
- CSV to import from a single CSV format file.
- - WU to import from a Weather Underground PWS daily history.
+
- WU to import from a Weather Underground PWS history.
- Cumulus to import from one or more Cumulus monthly log files.
@@ -1581,22 +1561,33 @@ summaries using the wee_database utility.
[WU]
The [WU] section contains the options relating to the import of
- observational data from a Weather Underground PWS daily history.
+ observational data from a Weather Underground PWS history.
station_id
- The Weather Underground weather station ID of the PWS from which the daily history will be imported. There is
+
The Weather Underground weather station ID of the PWS from which the historical data will be imported. There is
no default.
+ api_key
+
+ The Weather Underground API key to be used to obtain the PWS history data. There is no default.
+
+
+ Note
The API key is a seemingly random string of 32 characters used to access the new
+ (2019) Weather Underground API. PWS contributors can obtain an API key by logging onto the Weather
+ Underground internet site and accessing Member Settings. 16 character API keys used with the previous Weather
+ Underground API are not supported.
+
+
interval
Determines how the time interval (WeeWX database field interval) between successive
observations is determined. This option is identical in operation to the CSV interval
- option but applies to Weather Underground imports only. As Weather Underground often skips observation
- records when responding to a daily history query, the use of interval = derive may
- give incorrect or inconsistent interval values. Better results may be obtained by using interval = conf
+ option but applies to Weather Underground imports only. As a Weather Underground PWS history sometimes has
+ missing records, the use of interval = derive may give incorrect or inconsistent
+ interval values. Better results may be obtained by using interval = conf
if the current WeeWX installation has the same archive_interval as the Weather
Underground data, or by using interval = x where x is
the time interval in minutes used to upload the Weather Underground data. The most appropriate setting will
@@ -2295,7 +2286,8 @@ summaries using the wee_database utility.
The output should be something like this:
-
Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
A CSV import from source file '/var/tmp/data.csv' has been requested.
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -2312,14 +2304,6 @@ Finished dry run import
data will be processed. The import will then be performed but no data will be written to the WeeWX
database. Upon completion a brief summary of the records processed is provided.
-
-
- Note
As the WeeWX database is not altered when the --dry-run option is used, wee_import log output is
- suspended during a dry run import. In effect, the use of --dry-run is
- equivalent to --dry-run --log=-. During a dry run import the only wee_import output is that displayed on stdout.
-
Once the dry run results are satisfactory the data can be imported using the following command:
@@ -2330,7 +2314,8 @@ Finished dry run import
there will be a prompt:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
A CSV import from source file '/var/tmp/data.csv' has been requested.
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -2385,12 +2370,12 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
Importing from Weather Underground
- wee_import can import data from the daily history of a Weather Undeground PWS. A
- Weather Underground daily history provides weather station observations received by Weather Underground for
- the PWS concerned on a day by day basis. As such, the data is analogous to the WeeWX archive table. When
- wee_import imports data from a Weather Underground daily history each day is
- considered a 'period'. wee_import processes one period at a time in chronological
- order (oldest to newest) and provides import summary data on a per period basis.
+
wee_import can import historical observation data for a Weather Underground PWS via
+ the Weather Underground API. The Weather Underground API provides historical weather station observations
+ received by Weather Underground for the PWS concerned on a day by day basis. As such, the data is analogous
+ to the WeeWX archive table. When wee_import imports data from the Weather
+ Underground API each day is considered a 'period'. wee_import processes one period
+ at a time in chronological order (oldest to newest) and provides import summary data on a per period basis.
Mapping data to archive fields
@@ -2398,28 +2383,34 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
A Weather Underground import will populate WeeWX archive fields as follows:
- - Provided data exists for each field in the Weather Underground PWS daily history, the following WeeWX
- archive fields will be directly populated by imported data:
+
- Provided data exists for each field returned by the Weather Underground API, the following WeeWX archive
+ fields will be directly populated by imported data:
- dateTime
- barometer
- dewpoint
+ - heatindex
- outHumidity
- outTemp
- radiation
- rain
+ - rainRate
+ - UV
+ - windchill
- windDir
- windGust
- windSpeed
- Note
If an appropriate field does not exist in the Weather Underground daily
- history then the corresponding WeeWX archive field will be set to None/null. For example, if there is no solar radiation sensor then radiation will be null, or if outHumidity was never
- uploaded to Weather Undeground then outHumidity will be null.
+ Note
If an appropriate field is not returned by the Weather Underground API then
+ the corresponding WeeWX archive field will contain no data. If the API returns an appropriate field but
+ with no data, the corresponding WeeWX archive field will be set to None/null.
+ For example, if the API response has no solar radiation field the WeeWX
+ radiation archive field will have no data stored. However, if the API
+ response has a solar radiation field but contains no data, the WeeWX
+ radiation archive field will be None/null.
@@ -2437,10 +2428,7 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
- altimeter
- ET
- - heatindex
- pressure
- - rainRate
- - windchill
@@ -2454,7 +2442,7 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
Step-by-step instructions
- To import observations from the daily history of a Weather Underground PWS:
+ To import observations from a Weather Underground PWS history:
- Obtain the weather station ID of the Weather Underground PWS from which data is to be imported. The
@@ -2463,6 +2451,11 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
of ISTATION123 will be used.
+ - Obtain the API key to be used to access the Weather Underground API. This will be a seemingly random
+ alphanumeric sequence of 32 characters. API keys are available to Weather Underground PWS contributors
+ by logging on to their Weather Underground account and accessing Member Settings.
+
+
- Make a backup of the WeeWX database in case the import should go awry.
@@ -2477,7 +2470,6 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
source = WU
-
Confirm that the following options in the [WU] section are correctly set:
@@ -2485,6 +2477,9 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
character weather station ID of the Weather Underground PWS that will be the source of the
imported data.
+ api_key. The 32 character
+ API key to be used to access the Weather Underground API.
+
interval. Determines how
the WeeWX interval field is derived.
@@ -2556,7 +2551,8 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
The output should be similar to:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Observation history for Weather Underground station 'ISTATION123' will be imported.
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -2577,14 +2573,6 @@ Unique records processed: 71; Last timestamp: 2016-01-23 06:00:00 AEST (14534928
Finished dry run import
657 records were processed and 657 unique records would have been imported.
-
-
- Note
As the WeeWX database is not altered when the --dry-run option is used, wee_import log output is
- suspended during a dry run import. In effect, the use of --dry-run is
- equivalent to --dry-run --log=-. During a dry run import the only wee_import output is that displayed on stdout(console).
-
Once the dry run results are satisfactory the source data can be imported using the following command:
@@ -2596,7 +2584,8 @@ Finished dry run import
will be a prompt:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Observation history for Weather Underground station 'ISTATION123' will be imported.
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -2611,9 +2600,9 @@ Are you sure you want to proceed (y/n)?
- Note
wee_import obtains Weather Underground daily
- history data one day at a time via a HTTP request and as such the import of large time spans of data
- may take some time. Such imports may be best handled as a series of imports of smaller time spans.
+ Note
wee_import obtains Weather Underground data
+ one day at a time via a HTTP request and as such the import of large time spans of data may take some
+ time. Such imports may be best handled as a series of imports of smaller time spans.
@@ -2643,15 +2632,17 @@ imported. Confirm successful import in the WeeWX log file.
- Note
It is not unusual to see a Weather Underground import return a different
- number of records for the same import performed at different times. If importing the current day
- this could be because an additional record may have been added between wee_import runs. For periods before today, this behaviour appears to be a vagary
- of Weather Underground. The only solution appears to be to repeat the import with the same --date option setting and observe whether the missing records are imported.
- Repeating the import will not adversely affect any existing data as records with timestamps that are
- already in the WeeWX archive will be ignored. It may; however, generated many UNIQUE constraint failed: archive.dateTime
- messages in the WeeWX log.
+ Note
The new (2019) Weather Underground API appears to have an issue when
+ obtaining historical data for the current day. The first time the API is queried the API returns all
+ historical data up to and including the most recent record. However, subsequent later API queries
+ during the same day return the same set of records rather than all records up to and including the
+ time of the latest API query. Users importing Weather Underground data that includes data from the
+ current day are advised to carefully check the WeeWX log to ensure that all expected records were
+ imported. If some records are missing from the current day try running an import for the current day
+ again using the --date option setting. If this fails then wait until the
+ following day and perform another import for the day concerned again using the
+ --date option setting. In all cases confirm what data has been imported by
+ referring to the WeeWX log.
@@ -2831,7 +2822,8 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
The output should be similar to:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Cumulus monthly log files in the '/var/tmp/cumulus' directory will be imported
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -2876,7 +2868,8 @@ Finished dry run import
a prompt:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Cumulus monthly log files in the '/var/tmp/cumulus' directory will be imported
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -2895,7 +2888,8 @@ Are you sure you want to proceed (y/n)?
preamble may look similar to:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Cumulus monthly log files in the '/var/tmp/cumulus' directory will be imported
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -3143,7 +3137,8 @@ Aug 22 14:38:28 stretch12 weewx[863]: manager: unable to add record 2018-09-04 0
The output should be similar to:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Weather Display monthly log files in the '/var/tmp/WD' directory will be imported
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -3185,7 +3180,8 @@ Finished dry run import
The output should be similar to:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Weather Display monthly log files in the '/var/tmp/WD' directory will be imported
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -3288,7 +3284,8 @@ Finished dry run import
a prompt:
- Starting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Weather Display monthly log files in the '/var/tmp/WD' directory will be imported
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -3307,7 +3304,8 @@ Are you sure you want to proceed (y/n)?
is ignored. In such cases the preamble may look similar to:
- SStarting wee_import...
+ Using WeeWX configuration file /home/weewx/weewx.conf
+Starting wee_import...
Weather Display monthly log files in the '/var/tmp/WD' directory will be imported
Using database binding 'wx_binding', which is bound to database 'weewx.sdb'
Destination table 'archive' unit system is '0x01' (US).
@@ -3545,10 +3543,7 @@ Options:
Socket timeout in seconds. Default is 10.
-v, --verbose Print useful extra output.
-l LOG_FACILITY, --log=LOG_FACILITY
- Log selected output to syslog. If omitted no syslog
- logging occurs. If LOG_FACILITY is 'weewx' then logs
- are written to the same log used by weewx. Any other
- parameter will log to syslog.
+ OBSOLETE. Logging will always occur.
-t, --test Test what would happen, but don't do anything.
-q, --query For each record, query the user before making a
change.
@@ -3646,14 +3641,11 @@ command line, or in the configuration file.
Option --log
- Control the wunderfixer log output. The default is no logging. If --log=weewx is used then wunderfixer logs to the same log file
- as used by WeeWX. Any other setting will result in wunderfixer logs being written
- to syslog. The --log option is used as follows:
+
The --log option is obsolete and no longer performs any function. The
+ --log option has been retained for backwards compatibility with users existing
+ scripts. All wunderfixer log output is always logged via the WeeWX log facility.
- wunderfixer --log=weewx
-
Option --test
The --test option will cause wunderfixer to do everything
@@ -3693,6 +3685,11 @@ command line, or in the configuration file.
options.
+
+ Note
The data fields shown in the example outputs below are indicative only and the
+ actual output may or may not include other fields.
+
+
To run wunderfixer directly:
@@ -3703,10 +3700,10 @@ command line, or in the configuration file.
Using configuration file /home/weewx/weewx.conf.
Using database binding 'wx_binding', which is bound to database 'archive_sqlite'
-2016-09-22 06:30:00 AEST (1474489800); 29.920"; 58.9F; 79%; 1.0 mph; 248 deg; 6.0 mph gust; 52.4F; 0.00" rain; 0.01" daily rain ... skipped.
-2016-09-22 07:35:00 AEST (1474493700); 29.931"; 64.9F; 65%; 2.0 mph; 180 deg; 7.0 mph gust; 52.8F; 0.00" rain; 0.01" daily rain ... skipped.
-2016-09-22 07:55:00 AEST (1474494900); 29.934"; 65.8F; 63%; 2.0 mph; 180 deg;10.0 mph gust; 52.8F; 0.00" rain; 0.01" daily rain ... skipped.
-2016-09-22 08:20:00 AEST (1474496400); 29.938"; 66.5F; 59%; 5.0 mph; 180 deg;12.0 mph gust; 51.7F; 0.00" rain; 0.01" daily rain ... skipped.
+2016-09-22 06:30:00 AEST (1474489800); 29.920"; 58.9F; 79%; 1.0 mph; 248 deg; 6.0 mph gust; 52.4F; 0.00" rain; 0.01" ... skipped.
+2016-09-22 07:35:00 AEST (1474493700); 29.931"; 64.9F; 65%; 2.0 mph; 180 deg; 7.0 mph gust; 52.8F; 0.00" rain; 0.01" ... skipped.
+2016-09-22 07:55:00 AEST (1474494900); 29.934"; 65.8F; 63%; 2.0 mph; 180 deg;10.0 mph gust; 52.8F; 0.00" rain; 0.01" ... skipped.
+2016-09-22 08:20:00 AEST (1474496400); 29.938"; 66.5F; 59%; 5.0 mph; 180 deg;12.0 mph gust; 51.7F; 0.00" rain; 0.01" ... skipped.
This output indicates that four records were found to be missing. The word 'skipped' at the end of each
line indicates that whilst wunderfixer detected the record as missing, the
@@ -3728,10 +3725,10 @@ Using database binding 'wx_binding', which is bound to database 'archive_sqlite'
Using configuration file /home/weewx/weewx.conf.
Using database binding 'wx_binding', which is bound to database 'archive_sqlite'
-2016-09-22 06:30:00 AEST (1474489800); 29.920"; 58.9F; 79%; 1.0 mph; 248 deg; 6.0 mph gust; 52.4F; 0.00" rain; 0.01" daily rain ... published.
-2016-09-22 07:35:00 AEST (1474493700); 29.931"; 64.9F; 65%; 2.0 mph; 180 deg; 7.0 mph gust; 52.8F; 0.00" rain; 0.01" daily rain ... published.
-2016-09-22 07:55:00 AEST (1474494900); 29.934"; 65.8F; 63%; 2.0 mph; 180 deg;10.0 mph gust; 52.8F; 0.00" rain; 0.01" daily rain ... published.
-2016-09-22 08:20:00 AEST (1474496400); 29.938"; 66.5F; 59%; 5.0 mph; 180 deg;12.0 mph gust; 51.7F; 0.00" rain; 0.01" daily rain ... published.
+2016-09-22 06:30:00 AEST (1474489800); 29.920"; 58.9F; 79%; 1.0 mph; 248 deg; 6.0 mph gust; 52.4F; 0.00" rain; 0.01" ... published.
+2016-09-22 07:35:00 AEST (1474493700); 29.931"; 64.9F; 65%; 2.0 mph; 180 deg; 7.0 mph gust; 52.8F; 0.00" rain; 0.01" ... published.
+2016-09-22 07:55:00 AEST (1474494900); 29.934"; 65.8F; 63%; 2.0 mph; 180 deg;10.0 mph gust; 52.8F; 0.00" rain; 0.01" ... published.
+2016-09-22 08:20:00 AEST (1474496400); 29.938"; 66.5F; 59%; 5.0 mph; 180 deg;12.0 mph gust; 51.7F; 0.00" rain; 0.01" ... published.
This output indicates that four records were found to be missing. This time word 'skipped' at the end of
@@ -3752,13 +3749,13 @@ Number of WU records: 284
Number of missing records: 4
Missing records:
-2016-09-22 06:30:00 AEST (1474489800); 29.920"; 58.9F; 79%; 1.0 mph; 248 deg; 6.0 mph gust; 52.4F; 0.00" rain; 0.01" daily rain ...fix? (y/n/a/q):y
+2016-09-22 06:30:00 AEST (1474489800); 29.920"; 58.9F; 79%; 1.0 mph; 248 deg; 6.0 mph gust; 52.4F; 0.00" rain; 0.01" ...fix? (y/n/a/q):y
...published.
-2016-09-22 07:35:00 AEST (1474493700); 29.931"; 64.9F; 65%; 2.0 mph; 180 deg; 7.0 mph gust; 52.8F; 0.00" rain; 0.01" daily rain ...fix? (y/n/a/q):y
+2016-09-22 07:35:00 AEST (1474493700); 29.931"; 64.9F; 65%; 2.0 mph; 180 deg; 7.0 mph gust; 52.8F; 0.00" rain; 0.01" ...fix? (y/n/a/q):y
...published.
-2016-09-22 07:55:00 AEST (1474494900); 29.934"; 65.8F; 63%; 2.0 mph; 180 deg;10.0 mph gust; 52.8F; 0.00" rain; 0.01" daily rain ...fix? (y/n/a/q):y
+2016-09-22 07:55:00 AEST (1474494900); 29.934"; 65.8F; 63%; 2.0 mph; 180 deg;10.0 mph gust; 52.8F; 0.00" rain; 0.01" ...fix? (y/n/a/q):y
...published.
-2016-09-22 08:20:00 AEST (1474496400); 29.938"; 66.5F; 59%; 5.0 mph; 180 deg;12.0 mph gust; 51.7F; 0.00" rain; 0.01" daily rain ...fix? (y/n/a/q):
+2016-09-22 08:20:00 AEST (1474496400); 29.938"; 66.5F; 59%; 5.0 mph; 180 deg;12.0 mph gust; 51.7F; 0.00" rain; 0.01" ...fix? (y/n/a/q):
@@ -3875,7 +3872,7 @@ Missing records:
will depend on the --log option used and your version of Linux.
-
Check the Weather Undeground daily history for the station concerned to ensure that any missing
+ Check the Weather Underground Weather History for the station concerned to ensure that any missing
data was accepted by Weather Underground.
diff --git a/util/import/csv-example.conf b/util/import/csv-example.conf
index 6d20b6cd..21a727d3 100644
--- a/util/import/csv-example.conf
+++ b/util/import/csv-example.conf
@@ -1,6 +1,6 @@
# EXAMPLE CONFIGURATION FILE FOR IMPORTING FROM CSV FILES
#
-# Copyright (c) 2009-2016 Tom Keffer and Gary Roderick.
+# Copyright (c) 2009-2019 Tom Keffer and Gary Roderick.
# See the file LICENSE.txt for your rights.
##############################################################################
diff --git a/util/import/cumulus-example.conf b/util/import/cumulus-example.conf
index 14074b9c..ecae3a4f 100644
--- a/util/import/cumulus-example.conf
+++ b/util/import/cumulus-example.conf
@@ -1,6 +1,6 @@
# EXAMPLE CONFIGURATION FILE FOR IMPORTING FROM CUMULUS
#
-# Copyright (c) 2009-2016 Tom Keffer and Gary Roderick.
+# Copyright (c) 2009-2019 Tom Keffer and Gary Roderick.
# See the file LICENSE.txt for your rights.
##############################################################################
diff --git a/util/import/wu-example.conf b/util/import/wu-example.conf
index 623b2125..2a6d2aa6 100644
--- a/util/import/wu-example.conf
+++ b/util/import/wu-example.conf
@@ -1,6 +1,6 @@
# EXAMPLE CONFIGURATION FILE FOR IMPORTING FROM THE WEATHER UNDERGROUND
#
-# Copyright (c) 2009-2016 Tom Keffer and Gary Roderick.
+# Copyright (c) 2009-2019 Tom Keffer and Gary Roderick.
# See the file LICENSE.txt for your rights.
##############################################################################
@@ -22,6 +22,9 @@ source = WU
# WU PWS Station ID to be used for import.
station_id = XXXXXXXX123
+ # WU API key to be used for import.
+ api_key = XXXXXXXXXXXXXXXXXXXXXX1234567890
+
#
# When importing WU data the following WeeWX database fields will be
# populated directly by the imported data (provided the corresponding data
@@ -29,13 +32,17 @@ source = WU
# barometer
# dateTime
# dewpoint
+ # heatindex
# outHumidity
# outTemp
# radiation
# rain
+ # rainRate
+ # windchill
# windDir
# windGust
# windSpeed
+ # UV
#
# The following WeeWX database fields will be populated from other
# settings/config files:
@@ -47,10 +54,7 @@ source = WU
# used during import:
# altimeter
# ET
- # heatindex
# pressure
- # rainRate
- # windchill
#
# The following WeeWX fields will be populated with derived values from the
# imported data provided the --calc-missing command line option is used
diff --git a/weewx.conf b/weewx.conf
index 22b8c3dc..aef33669 100644
--- a/weewx.conf
+++ b/weewx.conf
@@ -23,7 +23,7 @@ log_failure = True
socket_timeout = 20
# Do not modify this. It is used when installing and updating weewx.
-version = 4.0.0a8
+version = 4.0.0a9
##############################################################################
@@ -118,6 +118,10 @@ version = 4.0.0a8
station = replace_me
password = "replace_me"
+ # If you plan on using wunderfixer, set the following
+ # to your API key:
+ api_key = replace_me
+
# Set the following to True to have weewx use the WU "Rapidfire"
# protocol. Not all hardware can support it. See the User's Guide.
rapidfire = False