diff --git a/bin/config_util.py b/bin/config_util.py index 7ac9eff9..025e0430 100644 --- a/bin/config_util.py +++ b/bin/config_util.py @@ -17,6 +17,7 @@ import tempfile import configobj import weeutil.weeutil +from weewx.engine import all_service_groups minor_comment_block = [""] major_comment_block = ["", "##############################################################################", ""] @@ -48,6 +49,15 @@ metricwx_group = {'group_altitude': 'meter', 'group_speed2': 'meter_per_second2', 'group_temperature': 'degree_C'} +class Logger(object): + def __init__(self, verbosity=0): + self.verbosity = verbosity + def log(self, msg, level=0): + if self.verbosity >= level: + print "%s%s" % (' ' * (level - 1), msg) + def set_verbosity(self, verbosity): + self.verbosity = verbosity + #============================================================================== # Utilities that find and save ConfigObj objects #============================================================================== @@ -852,6 +862,13 @@ def prompt_with_limits(prompt, default=None, low_limit=None, high_limit=None): return value +#============================================================================== +# Classes and utilities for installing extensions +#============================================================================== + +class ExtensionInstaller(dict): + """Base class for extension installers.""" + def extract_roots(config_path, config_dict): """Get the location of the various root directories used by weewx.""" @@ -873,3 +890,4 @@ def extract_roots(config_path, config_dict): return root_dict + diff --git a/bin/wee_extension b/bin/wee_extension index 1c17d5b9..c2ea58b2 100755 --- a/bin/wee_extension +++ b/bin/wee_extension @@ -8,15 +8,13 @@ """Install and remove extensions.""" from __future__ import with_statement -import os.path -import optparse import sys -import shutil +import os +import optparse from subprocess import Popen, PIPE -import configobj - import config_util +from config_util import Logger usage = """wee_extension --help wee_extension --list @@ -59,28 +57,19 @@ def main(): # Now we are ready to parse the command line: (options, _args) = parser.parse_args() - ext = Extension(_args, - config=options.config, - tmpdir=options.tmpdir, - bin_root=options.bin_root, - dry_run=options.dry_run, - logger=Logger(verbosity=options.verbosity)) + ext = ExtensionEngine(_args, + config=options.config, + tmpdir=options.tmpdir, + bin_root=options.bin_root, + dry_run=options.dry_run, + logger=Logger(verbosity=options.verbosity)) if options.list_extensions: ext.enumerate_extensions() return 0 -class Logger(object): - def __init__(self, verbosity=0): - self.verbosity = verbosity - def log(self, msg, level=0): - if self.verbosity >= level: - print "%s%s" % (' ' * (level - 1), msg) - def set_verbosity(self, verbosity): - self.verbosity = verbosity - -class Extension(object): +class ExtensionEngine(object): """Engine that manages extensions.""" def __init__(self, args=None, config=None, tmpdir=None, bin_root=None, dry_run=None, logger=Logger()): @@ -107,6 +96,56 @@ class Extension(object): except OSError: self.logger.log("No extension cache '%s'" % ext_root, level=2) self.logger.log("No extensions installed", level=0) + + def install_extension(self, extension_path): + self.logger.log("Request to install %s" % extension_path) + ifile = 'install' + __import__(ifile) + module = sys.modules[ifile] + loader = getattr(module, 'loader') + installer = loader() + + self.logger.log("Request to install extension %s" % extension_path) + + + self.log("install_files", level=1) + for t in installer['files']: + dstdir = self.prepend_layout_path(t[0]) + try: + self.log("mkdir %s" % dstdir, level=2) + if self.doit: + mkdir(dstdir) + except os.error: + pass + for f in t[1]: + src = os.path.join(self.layout['EXTRACT_ROOT'], f) + dst = self.prepend_layout_path(f) + if os.path.exists(dst): + self.log("save existing file %s" % dst, level=2) + if self.doit: + save_path(dst) + self.log("copy %s to %s" % (src, dst), level=2) + if self.doit: + distutils.file_util.copy_file(src, dst) + + + +# self.log("request to install %s" % self.filename) +# self.layout_type = self.guess_type(self.layout_type) +# self.layout = self.verify_layout(self.layout_type) +# (self.basename, self.extdir, self.delete_extdir) = \ +# self.verify_installer(self.filename, self.tmpdir) +# self.verify_src(self.extdir) +# # everything is ok, so use the extdir +# self.layout['EXTRACT_ROOT'] = self.extdir +# self.load_installer(self.extdir, self.basename, self.layout) +# self.installer.install() +# self.cleanup() + +# self.install_files() +# self.merge_config_options() +# self.install_history() + if __name__=="__main__" : main() diff --git a/bin/weewx/drivers/acurite.py b/bin/weewx/drivers/acurite.py index 0a41d5bf..5e5cc8b3 100644 --- a/bin/weewx/drivers/acurite.py +++ b/bin/weewx/drivers/acurite.py @@ -18,7 +18,7 @@ # Thanks to Weather Guy and Andrew Daviel (2015) # decoding of the R3 messages # -# Slow-clap thanks to Michael Walsh +# golf clap to Michael Walsh # http://forum1.valleyinfosys.com/index.php # # No thanks to AcuRite or Chaney instruments. They refused to provide any @@ -288,6 +288,7 @@ X1 - 2 bytes # FIXME: decode inside humidity # FIXME: decode historical records +# FIXME: perhaps retry read when dodgey data or short read? from __future__ import with_statement import syslog @@ -295,6 +296,7 @@ import time import usb import weewx.drivers +import weewx.wxformulas DRIVER_NAME = 'AcuRite' DRIVER_VERSION = '0.15' @@ -402,7 +404,8 @@ class AcuRiteDriver(weewx.drivers.AbstractDevice): ntries = 0 yield packet next_read = min(self.r1_next_read, self.r2_next_read) - delay = max(next_read - time.time(), self.polling_interval) + delay = max(int(next_read - time.time() + 1), + self.polling_interval) logdbg("next read in %s seconds" % delay) time.sleep(delay) except (usb.USBError, weewx.WeeWxIOError), e: @@ -417,11 +420,13 @@ class AcuRiteDriver(weewx.drivers.AbstractDevice): def _augment_packet(self, packet): # calculate the rain delta from the total if 'rain_total' in packet: - if self.last_rain is not None: - packet['rain'] = packet['rain_total'] - self.last_rain - else: - packet['rain'] = None - self.last_rain = packet['rain_total'] + total = packet['rain_total'] + if (total is not None and self.last_rain is not None and + total < self.last_rain): + loginf("rain counter decrement ignored:" + " new: %s old: %s" % (total, self.last_rain)) + packet['rain'] = wxformulas.calculate_rain(total, self.last_rain) + self.last_rain = total # no wind direction when wind speed is zero if 'windSpeed' in packet and not packet['windSpeed']: diff --git a/setup2.py b/setup2.py deleted file mode 100755 index f67d761a..00000000 --- a/setup2.py +++ /dev/null @@ -1,570 +0,0 @@ -#!/usr/bin/env python -# -# weewx --- A simple, high-performance weather station server -# -# Copyright (c) 2009-2015 Tom Keffer -# -# See the file LICENSE.txt for your full rights. -# -"""Customized distutils setup file for weewx.""" - -from __future__ import with_statement - -import os.path -import sys -import re -import tempfile -import shutil - -import configobj - -from distutils.core import setup -from distutils.command.install import install -from distutils.command.install_data import install_data -from distutils.command.install_lib import install_lib -from distutils.command.install_scripts import install_scripts -from distutils.command.sdist import sdist -import distutils.dir_util - -# Useful for debugging setup.py. Set the environment variable -# DISTUTILS_DEBUG to get more debug info. -from distutils.debug import DEBUG - -# Find the install bin subdirectory: -this_file = os.path.join(os.getcwd(), __file__) -this_dir = os.path.abspath(os.path.dirname(this_file)) -bin_dir = os.path.abspath(os.path.join(this_dir, 'bin')) - -# Now that we've found the bin subdirectory, inject it into the path: -sys.path.insert(0, bin_dir) - -# Now we can import some weewx modules -import weewx -VERSION = weewx.__version__ -import config_util -import weeutil.weeutil - -start_scripts = ['util/init.d/weewx.bsd', - 'util/init.d/weewx.debian', - 'util/init.d/weewx.lsb', - 'util/init.d/weewx.redhat', - 'util/init.d/weewx.suse'] - -# The default station information: -stn_info = {'station_type' : 'Simulator', - 'driver' : 'weewx.drivers.simulator'} - -#============================================================================== -# install -#============================================================================== - -class weewx_install(install): - """Specialized version of install, which adds a --no-prompt option to - the 'install' command.""" - - # Add an option for --no-prompt: - user_options = install.user_options + [('no-prompt', None, 'Do not prompt for station info')] - - def initialize_options(self, *args, **kwargs): - install.initialize_options(self, *args, **kwargs) - self.no_prompt = None - - def finalize_options(self): - install.finalize_options(self) - if self.no_prompt is None: - self.no_prompt = False - -#============================================================================== -# install_lib -#============================================================================== - -class weewx_install_lib(install_lib): - """Specialized version of install_lib, which backs up old bin subdirectories.""" - - def run(self): - # Determine whether the user is still using an old-style schema - schema_type = get_schema_type(self.install_dir) - - # Save any existing 'bin' subdirectory: - if os.path.exists(self.install_dir): - bin_savedir = weeutil.weeutil.move_with_timestamp(self.install_dir) - print "Saved bin subdirectory as %s" % bin_savedir - else: - bin_savedir = None - - # Run the superclass's version. This will install all incoming files. - install_lib.run(self) - - # If the bin subdirectory previously existed, and if it included - # a 'user' subsubdirectory, then restore it - if bin_savedir: - user_backupdir = os.path.join(bin_savedir, 'user') - if os.path.exists(user_backupdir): - user_dir = os.path.join(self.install_dir, 'user') - distutils.dir_util.copy_tree(user_backupdir, user_dir) - - # But, there is one exception: if the old user subdirectory included an - # old-style schema, then it should be overwritten with the new version. - if schema_type == 'old': - incoming_schema_path = os.path.join(bin_dir, 'user/schemas.py') - target_path = os.path.join(self.install_dir, 'user/schemas.py') - distutils.file_util.copy_file(incoming_schema_path, target_path) - -#============================================================================== -# install_data -#============================================================================== - -class weewx_install_data(install_data): - """Specialized version of install_data. Mostly, it deals with upgrading - and merging any old weewx.conf configuration files.""" - - def initialize_options(self): - # Initialize my superclass's options: - install_data.initialize_options(self) - # Set to None so we inherit whatever setting comes from weewx_install: - self.no_prompt = None - - def finalize_options(self): - # Finalize my superclass's options: - install_data.finalize_options(self) - # This will set no_prompt to whatever is in weewx_install: - self.set_undefined_options('install', ('no_prompt', 'no_prompt')) - - def copy_file(self, f, install_dir, **kwargs): - # If this is the configuration file, then merge it instead - # of copying it - if f == 'weewx.conf': - rv = self.process_config_file(f, install_dir, **kwargs) - elif f in start_scripts: - rv = self.massage_start_file(f, install_dir, **kwargs) - else: - rv = install_data.copy_file(self, f, install_dir, **kwargs) - return rv - - def run(self): - # If there is an existing skins subdirectory, do not overwrite it. - if os.path.exists(os.path.join(self.install_dir, 'skins')): - # Do this by filtering it out of the list of subdirectories to - # be installed: - self.data_files = filter(lambda dat : not dat[0].startswith('skins/'), self.data_files) - - remove_obsolete_files(self.install_dir) - - # Run the superclass's run(): - install_data.run(self) - - def process_config_file(self, f, install_dir, **kwargs): - global stn_info - - # Open up and parse the distribution config file: - try: - dist_config_dict = configobj.ConfigObj(f, file_error=True) - except IOError, e: - sys.exit(str(e)) - except SyntaxError, e: - sys.exit("Syntax error in distribution configuration file '%s': %s" % - (f, e)) - - # The path where the weewx.conf configuration file will be installed - install_path = os.path.join(install_dir, os.path.basename(f)) - - # Do we have an old config file? - if os.path.isfile(install_path): - # Yes. Read it - config_path, config_dict = config_util.read_config(install_path, None) - if DEBUG: - print "Old configuration file found at", config_path - - # Update the old configuration file to the current version: - config_util.update_config(config_dict) - - # Then merge it into the distribution file - config_util.merge_config(config_dict, dist_config_dict) - else: - # No old config file. Use the distribution file, then, if we can, - # prompt the user for station specific info - config_dict = dist_config_dict - if not self.no_prompt: - # Prompt the user for the station information: - stn_info = config_util.prompt_for_info() - driver = config_util.prompt_for_driver(stn_info.get('driver')) - stn_info['driver'] = driver - stn_info.update(config_util.prompt_for_driver_settings(driver)) - if DEBUG: - print "Station info =", stn_info - config_util.modify_config(config_dict, stn_info, DEBUG) - - # Time to write it out. Get a temporary file: - with tempfile.NamedTemporaryFile("w") as tmpfile: - # Write the finished configuration file to it: - config_dict.write(tmpfile) - tmpfile.flush() - - # Save the old config file if it exists: - if not self.dry_run and os.path.exists(install_path): - backup_path = weeutil.weeutil.move_with_timestamp(install_path) - print "Saved old configuration file as %s" % backup_path - - # Now install the temporary file (holding the merged config data) - # into the proper place: - rv = install_data.copy_file(self, tmpfile.name, install_path, **kwargs) - - # Set the permission bits unless this is a dry run: - if not self.dry_run: - shutil.copymode(f, install_path) - - return rv - - def massage_start_file(self, f, install_dir, **kwargs): - - outname = os.path.join(install_dir, os.path.basename(f)) - sre = re.compile(r"WEEWX_ROOT\s*=") - - with open(f, 'r') as infile: - with tempfile.NamedTemporaryFile("w") as tmpfile: - for line in infile: - if sre.match(line): - tmpfile.writelines("WEEWX_ROOT=%s\n" % self.install_dir) - else: - tmpfile.writelines(line) - tmpfile.flush() - rv = install_data.copy_file(self, tmpfile.name, outname, **kwargs) - - # Set the permission bits unless this is a dry run: - if not self.dry_run: - shutil.copymode(f, outname) - - return rv - -#============================================================================== -# install_scripts -#============================================================================== - -class weewx_install_scripts(install_scripts): - - def run(self): - # Run the superclass's version: - install_scripts.run(self) - - try: - # Put in a symbolic link for weewxd.py - os.symlink('./weewxd', os.path.join(self.install_dir, 'weewxd.py')) - except OSError: - pass - -#============================================================================== -# sdist -#============================================================================== - -class weewx_sdist(sdist): - """Specialized version of sdist which checks for password information in - the configuration file before creating the distribution. - - For other sdist methods, see: - http://epydoc.sourceforge.net/stdlib/distutils.command.sdist.sdist-class.html - """ - - def copy_file(self, f, install_dir, **kwargs): - """Specialized version of copy_file that checks for stray passwords.""" - - # If this is the configuration file, then check it for passwords - if f == 'weewx.conf': - import configobj - config = configobj.ConfigObj(f) - - try: - password = config['StdReport']['FTP']['password'] - sys.exit("\n*** FTP password found in configuration file. Aborting ***\n\n") - except KeyError: - pass - - try: - password = config['StdRESTful']['Wunderground']['password'] - sys.exit("\n*** Wunderground password found in configuration file. Aborting ***\n\n") - except KeyError: - pass - - try: - password = config['StdRESTful']['Wunderground']['password'] - sys.exit("\n*** PWSweather password found in configuration file. Aborting ***\n\n") - except KeyError: - pass - - # Pass on to my superclass: - return sdist.copy_file(self, f, install_dir, **kwargs) - -#============================================================================== -# utility functions -#============================================================================== - -def remove_obsolete_files(install_dir): - """Remove no longer needed files from the installation - directory, nominally /home/weewx.""" - - # If the file #upstream.last exists, delete it, as it is no longer used. - try: - os.remove(os.path.join(install_dir, 'public_html/#upstream.last')) - except OSError: - pass - - # If the file $WEEWX_INSTALL/readme.htm exists, delete it. It's - # the old readme (since replaced with README) - try: - os.remove(os.path.join(install_dir, 'readme.htm')) - except OSError: - pass - - # If the file $WEEWX_INSTALL/CHANGES.txt exists, delete it. It's - # been moved to the docs subdirectory and renamed - try: - os.remove(os.path.join(install_dir, 'CHANGES.txt')) - except OSError: - pass - - # The directory start_scripts is no longer used - shutil.rmtree(os.path.join(install_dir, 'start_scripts'), True) - - # The file docs/README.txt is now gone - try: - os.remove(os.path.join(install_dir, 'docs/README.txt')) - except OSError: - pass - - # If the file docs/CHANGES.txt exists, delete it. It's been renamed - # to docs/changes.txt - try: - os.remove(os.path.join(install_dir, 'docs/CHANGES.txt')) - except OSError: - pass - - # setup.py is no longer left in WEEWX_ROOT. - try: - os.remove(os.path.join(install_dir, 'setup.py')) - except OSError: - pass - -def get_schema_type(bin_dir): - """Checks whether the schema in user.schemas is a new style or old style - schema. - - bin_dir: The directory to be checked. This is nominally /home/weewx/bin. - - Returns: - 'none': There is no schema at all. - 'old' : It is an old-style schema. - 'new' : It is a new-style schema - """ - tmp_path = list(sys.path) - sys.path.insert(0, bin_dir) - - try: - import user.schemas - except ImportError: - # There is no existing schema at all. - result = 'none' - else: - # There is a schema. Determine if it is old-style or new-style - try: - # Try the old style 'drop_list'. If it fails, it must be - # a new-style schema - _ = user.schemas.drop_list # @UnusedVariable @UndefinedVariable - except AttributeError: - # New style schema - result = 'new' - else: - # It did not fail. Must be an old-style schema - result = 'old' - finally: - del user.schemas - - # Restore the path - sys.path = tmp_path - - return result - -#============================================================================== -# main entry point -#============================================================================== - -if __name__ == "__main__": - - setup(name='weewx', - version=VERSION, - description='weather software', - long_description="weewx interacts with a weather station to produce graphs, " - "reports, and HTML pages. weewx can upload data to services such as the " - "WeatherUnderground, PWSweather.com, or CWOP.", - author='Tom Keffer', - author_email='tkeffer@gmail.com', - url='http://www.weewx.com', - license='GPLv3', - classifiers=['Development Status :: 5 - Production/Stable', - 'Intended Audience :: End Users/Desktop', - 'License :: GPLv3', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - ], - requires=['configobj(>=4.5)', - 'serial(>=2.3)', - 'Cheetah(>=2.0)', - 'sqlite3(>=2.5)', - 'PIL(>=1.1.6)'], - provides=['weedb', - 'weeplot', - 'weeutil', - 'weewx'], - cmdclass={"sdist" : weewx_sdist, - "install" : weewx_install, - "install_scripts": weewx_install_scripts, - "install_data" : weewx_install_data, - "install_lib" : weewx_install_lib}, - platforms=['any'], - package_dir={'': 'bin'}, - packages=['examples', - 'schemas', - 'user', - 'weedb', - 'weeplot', - 'weeutil', - 'weewx', - 'weewx.drivers'], - py_modules=['daemon', - 'config_util'], - scripts=['bin/wee_config_database', - 'bin/wee_config_device', - 'bin/weewxd', - 'bin/wee_reports'], - data_files=[ - ('', - ['LICENSE.txt', - 'README', - 'weewx.conf']), - ('docs', - ['docs/changes.txt', - 'docs/copyright.htm', - 'docs/customizing.htm', - 'docs/debian.htm', - 'docs/readme.htm', - 'docs/redhat.htm', - 'docs/setup.htm', - 'docs/suse.htm', - 'docs/upgrading.htm', - 'docs/usersguide.htm']), - ('docs/css', - ['docs/css/jquery.tocify.css', - 'docs/css/weewx_docs.css']), - ('docs/css/ui-lightness', - ['docs/css/ui-lightness/jquery-ui-1.10.4.custom.css', - 'docs/css/ui-lightness/jquery-ui-1.10.4.custom.min.css']), - ('docs/css/ui-lightness/images', - ['docs/css/ui-lightness/images/animated-overlay.gif', - 'docs/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png', - 'docs/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png', - 'docs/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png', - 'docs/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png', - 'docs/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png', - 'docs/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png', - 'docs/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png', - 'docs/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png', - 'docs/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png', - 'docs/css/ui-lightness/images/ui-icons_222222_256x240.png', - 'docs/css/ui-lightness/images/ui-icons_228ef1_256x240.png', - 'docs/css/ui-lightness/images/ui-icons_ef8c08_256x240.png', - 'docs/css/ui-lightness/images/ui-icons_ffd27a_256x240.png', - 'docs/css/ui-lightness/images/ui-icons_ffffff_256x240.png']), - ('docs/images', - ['docs/images/day-gap-not-shown.png', - 'docs/images/day-gap-showing.png', - 'docs/images/daycompare.png', - 'docs/images/daytemp_with_avg.png', - 'docs/images/daywindvec.png', - 'docs/images/ferrites.jpg', - 'docs/images/funky_degree.png', - 'docs/images/image_parts.png', - 'docs/images/image_parts.xcf', - 'docs/images/logo-apple.png', - 'docs/images/logo-centos.png', - 'docs/images/logo-debian.png', - 'docs/images/logo-fedora.png', - 'docs/images/logo-linux.png', - 'docs/images/logo-mint.png', - 'docs/images/logo-opensuse.png', - 'docs/images/logo-redhat.png', - 'docs/images/logo-suse.png', - 'docs/images/logo-ubuntu.png', - 'docs/images/logo-weewx.png', - 'docs/images/sample_monthrain.png', - 'docs/images/weekgustoverlay.png', - 'docs/images/weektempdew.png', - 'docs/images/yearhilow.png']), - ('docs/js', - ['docs/js/jquery-1.11.1.min.js', - 'docs/js/jquery-ui-1.10.4.custom.min.js', - 'docs/js/jquery.tocify-1.9.0.js', - 'docs/js/jquery.tocify-1.9.0.min.js', - 'docs/js/weewx.js']), - ('skins/Ftp', - ['skins/Ftp/skin.conf']), - ('skins/Rsync', - ['skins/Rsync/skin.conf']), - ('skins/Standard', - ['skins/Standard/favicon.ico', - 'skins/Standard/index.html.tmpl', - 'skins/Standard/mobile.css', - 'skins/Standard/mobile.html.tmpl', - 'skins/Standard/month.html.tmpl', - 'skins/Standard/skin.conf', - 'skins/Standard/week.html.tmpl', - 'skins/Standard/weewx.css', - 'skins/Standard/year.html.tmpl']), - ('skins/Standard/NOAA', - ['skins/Standard/NOAA/NOAA-YYYY-MM.txt.tmpl', - 'skins/Standard/NOAA/NOAA-YYYY.txt.tmpl']), - ('skins/Standard/RSS', - ['skins/Standard/RSS/weewx_rss.xml.tmpl']), - ('skins/Standard/backgrounds', - ['skins/Standard/backgrounds/band.gif', - 'skins/Standard/backgrounds/butterfly.jpg', - 'skins/Standard/backgrounds/drops.gif', - 'skins/Standard/backgrounds/flower.jpg', - 'skins/Standard/backgrounds/leaf.jpg', - 'skins/Standard/backgrounds/night.gif']), - ('skins/Standard/smartphone', - ['skins/Standard/smartphone/barometer.html.tmpl', - 'skins/Standard/smartphone/custom.js', - 'skins/Standard/smartphone/humidity.html.tmpl', - 'skins/Standard/smartphone/index.html.tmpl', - 'skins/Standard/smartphone/radar.html.tmpl', - 'skins/Standard/smartphone/rain.html.tmpl', - 'skins/Standard/smartphone/temp_outside.html.tmpl', - 'skins/Standard/smartphone/wind.html.tmpl']), - ('skins/Standard/smartphone/icons', - ['skins/Standard/smartphone/icons/icon_ipad_x1.png', - 'skins/Standard/smartphone/icons/icon_ipad_x2.png', - 'skins/Standard/smartphone/icons/icon_iphone_x1.png', - 'skins/Standard/smartphone/icons/icon_iphone_x2.png']), - ('util/apache/conf.d', - ['util/apache/conf.d/weewx.conf']), - ('util/init.d', - ['util/init.d/weewx.bsd', - 'util/init.d/weewx.debian', - 'util/init.d/weewx.lsb', - 'util/init.d/weewx.redhat', - 'util/init.d/weewx.suse']), - ('util/launchd', - ['util/launchd/com.weewx.weewxd.plist']), - ('util/logrotate.d', - ['util/logrotate.d/weewx']), - ('util/logwatch/conf/logfiles', - ['util/logwatch/conf/logfiles/weewx.conf']), - ('util/logwatch/conf/services', - ['util/logwatch/conf/services/weewx.conf']), - ('util/logwatch/scripts/services', - ['util/logwatch/scripts/services/weewx']), - ('util/rsyslog.d', - ['util/rsyslog.d/weewx.conf']), - ('util/systemd', - ['util/systemd/weewx.service']) - ] - )