Touch up a few comments (typos, extraneous, etc.)
[infoex-autowx.git] / infoex-autowx.py
index 3d8762c81b58c97eeecc8af10583a835435801c9..598f24cd765d300b8d49f3e2b1884ebca63e8a41 100755 (executable)
@@ -2,14 +2,15 @@
 # -*- coding: utf-8 -*-
 
 """
 # -*- coding: utf-8 -*-
 
 """
-InfoEx <-> NRCS Auto Wx implementation
+InfoEx <-> NRCS/MesoWest Auto Wx implementation
 Alexander Vasarab
 Wylark Mountaineering LLC
 
 Alexander Vasarab
 Wylark Mountaineering LLC
 
-Version 1.0.0
+Version 2.0.0
 
 
-This program fetches data from an NRCS SNOTEL site and pushes it to
-InfoEx using the new automated weather system implementation.
+This program fetches data from either an NRCS SNOTEL site or MesoWest
+weather station and pushes it to InfoEx using the new automated weather
+system implementation.
 
 It is designed to be run hourly, and it asks for the last three hours
 of data of each desired type, and selects the most recent one. This
 
 It is designed to be run hourly, and it asks for the last three hours
 of data of each desired type, and selects the most recent one. This
@@ -41,8 +42,10 @@ import zeep
 import zeep.cache
 import zeep.transports
 
 import zeep.cache
 import zeep.transports
 
+__version__ = '2.0.0'
+
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
-log.setLevel(logging.DEBUG)
+log.setLevel(logging.NOTSET)
 
 try:
     from systemd.journal import JournalHandler
 
 try:
     from systemd.journal import JournalHandler
@@ -55,11 +58,18 @@ except:
     handler = logging.StreamHandler(sys.stdout)
     log.addHandler(handler)
 
     handler = logging.StreamHandler(sys.stdout)
     log.addHandler(handler)
 
-parser = OptionParser()
+parser = OptionParser(version=__version__)
+
 parser.add_option("--config",
     dest="config",
     metavar="FILE",
     help="location of config file")
 parser.add_option("--config",
     dest="config",
     metavar="FILE",
     help="location of config file")
+
+parser.add_option("--log-level",
+    dest="log_level",
+    default=None,
+    help="set the log level (debug, info, warning)")
+
 parser.add_option("--dry-run",
     action="store_true",
     dest="dry_run",
 parser.add_option("--dry-run",
     action="store_true",
     dest="dry_run",
@@ -71,11 +81,27 @@ parser.add_option("--dry-run",
 config = configparser.ConfigParser(allow_no_value=False)
 
 if not options.config:
 config = configparser.ConfigParser(allow_no_value=False)
 
 if not options.config:
-    print("Please specify a configuration file via --config.")
+    parser.print_help()
+    print("\nPlease specify a configuration file via --config.")
     sys.exit(1)
 
 config.read(options.config)
 
     sys.exit(1)
 
 config.read(options.config)
 
+# ugly, but passable
+if options.log_level in [None, 'debug', 'info', 'warning']:
+    if options.log_level == 'debug':
+        log.setLevel(logging.DEBUG)
+    elif options.log_level == 'info':
+        log.setLevel(logging.INFO)
+    elif options.log_level == 'warning':
+        log.setLevel(logging.WARNING)
+    else:
+        log.setLevel(logging.NOTSET)
+else:
+    parser.print_help()
+    print("\nPlease select an appropriate log level or remove the switch (--log-level).")
+    sys.exit(1)
+
 log.debug('STARTING UP')
 
 try:
 log.debug('STARTING UP')
 
 try:
@@ -109,6 +135,11 @@ try:
                     'PREC'  # PRECIPITATION ACCUMULATION (in)
                     ]
 
                     'PREC'  # PRECIPITATION ACCUMULATION (in)
                     ]
 
+        # XXX: For NRCS, we're manually overriding units for now! Once
+        #      unit conversion is supported for NRCS, REMOVE THIS!
+        if 'units' not in data:
+            data['units'] = 'imperial'
+
     if data['provider'] == 'mesowest':
         data['source'] = 'https://api.synopticdata.com/v2/stations/timeseries'
         data['station_id'] = config['station']['station_id']
     if data['provider'] == 'mesowest':
         data['source'] = 'https://api.synopticdata.com/v2/stations/timeseries'
         data['station_id'] = config['station']['station_id']
@@ -149,7 +180,7 @@ except ValueError as exc:
 #
 # Also note that the current Auto Wx InfoEx documentation shows these
 # keys in a graphical table with the "index" beginning at 1, but here we
 #
 # Also note that the current Auto Wx InfoEx documentation shows these
 # keys in a graphical table with the "index" beginning at 1, but here we
-# are sanely indexing beginning at 0.
+# sanely index beginning at 0.
 fmap = {}                           ; final_data     = [None] * 29
 fmap['Location UUID'] = 0           ; final_data[0]  = infoex['location_uuid']
 fmap['obDate'] = 1                  ; final_data[1]  = None
 fmap = {}                           ; final_data     = [None] * 29
 fmap['Location UUID'] = 0           ; final_data[0]  = infoex['location_uuid']
 fmap['obDate'] = 1                  ; final_data[1]  = None
@@ -189,10 +220,35 @@ if data['provider'] == 'nrcs':
     iemap['PREC'] = 'precipitationGauge'
     iemap['TOBS'] = 'tempPres'
     iemap['SNWD'] = 'hS'
     iemap['PREC'] = 'precipitationGauge'
     iemap['TOBS'] = 'tempPres'
     iemap['SNWD'] = 'hS'
+    iemap['PRES'] = 'baro'
+    iemap['RHUM'] = 'rH'
+    iemap['WSPD'] = 'windSpeedNum'
+    iemap['WDIR'] = 'windDirectionNum'
+    # unsupported by NRCS:
+    # windGustSpeedNum
 elif data['provider'] == 'mesowest':
     iemap['precip_accum'] = 'precipitationGauge'
     iemap['air_temp'] = 'tempPres'
     iemap['snow_depth'] = 'hS'
 elif data['provider'] == 'mesowest':
     iemap['precip_accum'] = 'precipitationGauge'
     iemap['air_temp'] = 'tempPres'
     iemap['snow_depth'] = 'hS'
+    iemap['pressure'] = 'baro'
+    iemap['relative_humidity'] = 'rH'
+    iemap['wind_speed'] = 'windSpeedNum'
+    iemap['wind_direction'] = 'windDirectionNum'
+    iemap['wind_gust'] = 'windGustSpeedNum'
+
+# override units if user selected metric
+#
+# NOTE: to update this, use the fmap<->final_data mapping laid out above
+#
+# NOTE: this only 'works' with MesoWest for now, as the MesoWest API
+#       itself handles the unit conversion; in the future, we will also
+#       support NRCS unit conversion, but this must be done by this
+#       program.
+if data['units'] == 'metric':
+    final_data[fmap['tempPresUnit']] = 'C'
+    final_data[fmap['hsUnit']] = 'm'
+    final_data[fmap['windSpeedUnit']] = 'm/s'
+    final_data[fmap['windGustSpeedNumUnit']] = 'm/s'
 
 # floor time to nearest hour
 dt = datetime.datetime.now()
 
 # floor time to nearest hour
 dt = datetime.datetime.now()
@@ -267,9 +323,9 @@ elif data['provider'] == 'mesowest':
         # sort and isolate the most recent, see note above in NRCS for how and
         # why this is done
         #
         # sort and isolate the most recent, see note above in NRCS for how and
         # why this is done
         #
-        # NOTE: Unlike in the NRCS case, the MesoWest API respones contains all
+        # NOTE: Unlike in the NRCS case, the MesoWest API response contains all
         #       data (whereas with NRCS, we have to make a separate request for
         #       data (whereas with NRCS, we have to make a separate request for
-        #       each element we want. This is nice for network efficiency but
+        #       each element we want). This is nice for network efficiency but
         #       it means we have to handle this part differently for each.
         #
         # NOTE: Also unlike NRCS, MesoWest provides more granular data; NRCS
         #       it means we have to handle this part differently for each.
         #
         # NOTE: Also unlike NRCS, MesoWest provides more granular data; NRCS
@@ -307,7 +363,7 @@ for elementCd in infoex['wx_data']:
     #           any possible elementCds we may want are any other data
     #           type than float.
     #
     #           any possible elementCds we may want are any other data
     #           type than float.
     #
-    #           Another possibility is to query the API  with
+    #           Another possibility is to query the API with
     #           getStationElements and temporarily store the
     #           storedUnitCd. But that's pretty network-intensive and
     #           may not even be worth it if there's only e.g. one or two
     #           getStationElements and temporarily store the
     #           storedUnitCd. But that's pretty network-intensive and
     #           may not even be worth it if there's only e.g. one or two
@@ -325,7 +381,6 @@ with open(infoex['csv_filename'], 'w') as f:
     f.close()
 
 if not options.dry_run:
     f.close()
 
 if not options.dry_run:
-    # not a dry run
     with open(infoex['csv_filename'], 'rb') as f:
         log.debug("uploading FTP file '%s'" % (infoex['host']))
         ftp = FTP(infoex['host'], infoex['uuid'], infoex['api_key'])
     with open(infoex['csv_filename'], 'rb') as f:
         log.debug("uploading FTP file '%s'" % (infoex['host']))
         ftp = FTP(infoex['host'], infoex['uuid'], infoex['api_key'])