Files
weewx/bin/weewxd
2020-05-09 16:32:05 -07:00

262 lines
9.6 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright (c) 2009-2020 Tom Keffer <tkeffer@gmail.com>
#
# 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()