#!/usr/bin/env python # # Copyright (c) 2009-2020 Tom Keffer # # See the file LICENSE.txt for your rights. # """Entry point to the weewx weather system.""" from __future__ import absolute_import from __future__ import print_function import locale import logging import os import platform import signal import sys import time from optparse import OptionParser import daemon import weecfg import weedb from weeutil.weeutil import to_bool import weeutil.logger # First import any user extensions... # noinspection PyUnresolvedReferences import user.extensions # ...then import the engine import weewx.engine log = logging.getLogger(__name__) usagestr = """Usage: weewxd --help weewxd --version weewxd [CONFIG_FILE|--config=CONFIG_FILE] [--daemon] [--pidfile=PIDFILE] [--exit] [--loop-on-init] [--log-label=LABEL] Entry point to the weewx weather program. Can be run directly, or as a daemon by specifying the '--daemon' option. Arguments: CONFIG_FILE: The weewx configuration file to be used. Optional. """ # =============================================================================== # Main entry point # =============================================================================== def main(): parser = OptionParser(usage=usagestr) parser.add_option("--config", dest="config_path", type=str, metavar="CONFIG_FILE", help="Use configuration file CONFIG_FILE.") parser.add_option("-d", "--daemon", action="store_true", dest="daemon", help="Run as a daemon") parser.add_option("-p", "--pidfile", type="string", dest="pidfile", help="Store the process ID in PIDFILE", default="/var/run/weewx.pid", metavar="PIDFILE") parser.add_option("-v", "--version", action="store_true", dest="version", help="Display version number then exit") parser.add_option("-x", "--exit", action="store_true", dest="exit", help="Exit on I/O and database errors instead of restarting") parser.add_option("-r", "--loop-on-init", action="store_true", dest="loop_on_init", help="Retry forever if device is not ready on startup") parser.add_option("-n", "--log-label", type="string", dest="log_label", help="Label to use in syslog entries", default="weewx", metavar="LABEL") # Get the command line options and arguments: (options, args) = parser.parse_args() if options.version: print(weewx.__version__) sys.exit(0) # Set up a minimal logging facility to be used until we read the config file. weeutil.logger.setup(options.log_label, {}) if args and options.config_path: print("Specify CONFIG_PATH as an argument, or by using --config, not both", file=sys.stderr) sys.exit(weewx.CMD_ERROR) log.info("Initializing weewx version %s", weewx.__version__) log.info("Using Python %s", sys.version) log.info("Platform %s", platform.platform()) log.info("Locale is '%s'", locale.setlocale(locale.LC_ALL)) # Save the current working directory. A service might # change it. In case of a restart, we need to change it back. cwd = os.getcwd() # Make sure the system time is not out of date (a common problem with the Raspberry Pi). # Do this by making sure the system time is later than the creation time of this file. sane = os.stat(__file__).st_ctime n = 0 while weewx.launchtime_ts < sane: # Log any problems every minute. if n % 120 == 0: log.info("Waiting for sane time. Current time is %s", weeutil.weeutil.timestamp_to_string(weewx.launchtime_ts)) n += 1 time.sleep(0.5) weewx.launchtime_ts = time.time() # Set up the signal handlers. signal.signal(signal.SIGHUP, sigHUPhandler) signal.signal(signal.SIGTERM, sigTERMhandler) if options.daemon: log.info("PID file is %s", options.pidfile) daemon.daemonize(pidfile=options.pidfile) # Main restart loop while True: os.chdir(cwd) # Read (or, possibly, re-read) the configuration file try: # Pass in a copy of the command line arguments. read_config() will change it. config_path, config_dict = weecfg.read_config(options.config_path, list(args)) except IOError as e: print(e, file=sys.stderr) sys.exit(weewx.CMD_ERROR) log.info("Using configuration file %s", config_path) weewx.debug = int(config_dict.get('debug', 0)) log.info("Debug is %s", weewx.debug) # Now that we have the config_dict and debug setting, we can customize the # logging with user additions weeutil.logger.setup(options.log_label, config_dict) # If no command line --loop-on-init was specified, look in the config file. if options.loop_on_init is None: loop_on_init = to_bool(config_dict.get('loop_on_init', False)) else: loop_on_init = options.loop_on_init try: log.debug("Initializing engine") # Create and initialize the engine engine = weewx.engine.StdEngine(config_dict) log.info("Starting up weewx version %s", weewx.__version__) # Start the engine. It should run forever unless an exception # occurs. Log it if the function returns. engine.run() log.critical("Unexpected exit from main loop. Program exiting.") # Catch any console initialization error: except weewx.engine.InitializationError as e: # Log it: log.critical("Unable to load driver: %s", e) # See if we should loop, waiting for the console to be ready. # Otherwise, just exit. if loop_on_init: log.critical(" **** Waiting 60 seconds then retrying...") time.sleep(60) log.info("retrying...") else: log.critical(" **** Exiting...") sys.exit(weewx.IO_ERROR) # Catch any recoverable weewx I/O errors: except weewx.WeeWxIOError as e: # Caught an I/O error. Log it, wait 60 seconds, then try again log.critical("Caught WeeWxIOError: %s", e) if options.exit: log.critical(" **** Exiting...") sys.exit(weewx.IO_ERROR) log.critical(" **** Waiting 60 seconds then retrying...") time.sleep(60) log.info("retrying...") # Catch any database connection errors: except (weedb.CannotConnectError, weedb.DisconnectError) as e: # No connection to the database server. Log it, wait 60 seconds, then try again log.critical("Database connection exception: %s", e) if options.exit: log.critical(" **** Exiting...") sys.exit(weewx.DB_ERROR) log.critical(" **** Waiting 60 seconds then retrying...") time.sleep(60) log.info("retrying...") except weedb.OperationalError as e: # Caught a database error. Log it, wait 120 seconds, then try again log.critical("Database OperationalError exception: %s", e) if options.exit: log.critical(" **** Exiting...") sys.exit(weewx.DB_ERROR) log.critical(" **** Waiting 2 minutes then retrying...") time.sleep(120) log.info("retrying...") except OSError as e: # Caught an OS error. Log it, wait 10 seconds, then try again log.critical("Caught OSError: %s", e) weeutil.logger.log_traceback(log.critical, " **** ") log.critical(" **** Waiting 10 seconds then retrying...") time.sleep(10) log.info("retrying...") except Restart: log.info("Received signal HUP. Restarting.") except Terminate: log.info("Terminating weewx version %s", weewx.__version__) weeutil.logger.log_traceback(log.debug, " **** ") signal.signal(signal.SIGTERM, signal.SIG_DFL) os.kill(0, signal.SIGTERM) # Catch any keyboard interrupts and log them except KeyboardInterrupt: log.critical("Keyboard interrupt.") # Reraise the exception (this should cause the program to exit) raise # Catch any non-recoverable errors. Log them, exit except Exception as ex: # Caught unrecoverable error. Log it, exit log.critical("Caught unrecoverable exception:") log.critical(" **** %s" % ex) # Include a stack traceback in the log: weeutil.logger.log_traceback(log.critical, " **** ") log.critical(" **** Exiting.") # Reraise the exception (this should cause the program to exit) raise # ============================================================================== # Signal handlers # ============================================================================== class Restart(Exception): """Exception raised when restarting the engine is desired.""" def sigHUPhandler(_signum, _frame): log.info("Received signal HUP. Initiating restart.") raise Restart class Terminate(Exception): """Exception raised when terminating the engine.""" def sigTERMhandler(signum, _frame): log.info("Received signal TERM (%s).", signum) raise Terminate # Start up the program main()