summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Schantl2024-01-19 12:27:33 +0000
committerMichael Schantl2024-01-19 13:56:18 +0000
commitaabfcddf481bb334c832af4f5bcba3bb273edbba (patch)
treeb3af4182d63a24b6ec9ecba33222332792ede6ab
parent546d0b8165011a7be52e9fee3249524a7b6a54c1 (diff)
downloadaur-aabfcddf481bb334c832af4f5bcba3bb273edbba.tar.gz
upgpkg: weewx 5.0.0-1
upstream release
-rw-r--r--.SRCINFO56
-rw-r--r--.gitignore14
-rw-r--r--PKGBUILD225
-rw-r--r--genplot.py737
-rw-r--r--pillow-rect.patch31
-rw-r--r--wee_config6
-rw-r--r--wee_database6
-rw-r--r--wee_debug6
-rw-r--r--wee_device6
-rw-r--r--wee_extension6
-rw-r--r--wee_import6
-rw-r--r--wee_reports6
-rw-r--r--weewx-version.patch39
-rw-r--r--weewx.service9
-rwxr-xr-xweewx.sysusers3
-rwxr-xr-xweewx.tmpfiles4
-rw-r--r--weewxd6
-rw-r--r--wunderfixer6
18 files changed, 133 insertions, 1039 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 8eacc733bb07..3489abf89ebe 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,49 +1,29 @@
pkgbase = weewx
pkgdesc = Software for logging data from weather stations
- pkgver = 4.10.2
- pkgrel = 7
+ pkgver = 5.0.0
+ pkgrel = 1
url = http://www.weewx.com/
arch = any
- license = GPL3
+ license = GPL-3.0-or-later
+ makedepends = mkdocs
+ makedepends = mkdocs-material
+ makedepends = mkdocs-material-extensions
depends = python
- depends = python-daemon
- depends = python-six
+ depends = python-cheetah3
depends = python-configobj
- depends = python-pyserial
depends = python-pillow
- depends = python-pyusb
- depends = python-cheetah3
- optdepends = python-pyephem: extended almanac information
- optdepends = mariadb-clients: MariaDB support
- optdepends = python-mysqlclient: MariaDB support
+ optdepends = python-pyserial: serial port support
+ optdepends = python-pyusb: USB port support
+ optdepends = python-pyephem: extended celestial information
+ optdepends = python-pymysql: MySQL or MariaDB support
backup = etc/weewx/weewx.conf
- source = weewx-4.10.2.tar.xz::https://github.com/weewx/weewx/archive/refs/tags/v4.10.2.tar.gz
- source = wee_config
- source = wee_database
- source = wee_debug
- source = wee_device
- source = wee_extension
- source = wee_import
- source = wee_reports
- source = weewxd
- source = wunderfixer
+ source = weewx-5.0.0.tar.xz::https://github.com/weewx/weewx/archive/refs/tags/v5.0.0.tar.gz
+ source = weewx.sysusers
+ source = weewx.tmpfiles
source = weewx.service
- source = genplot.py
- source = weewx-version.patch
- source = pillow-rect.patch
- sha512sums = 8fca9cd7720a29687a0d900e4d89ec2ce5ca5d2aa36bc5b5909ea14ecb849cdbdb6e699cf1c3a0d5505c89ad8c309517db32fd8dc4a0ae4704dfd5ed0cc5747f
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
- sha512sums = SKIP
+ sha512sums = c3eb23048012f2bab7eac0a25f6dfad4da4ff1591601f7242cf7fec91343e88f3014b7281c021c75cfdbb5b4e73285ab8605325a3c7771e90a5016e3d574dc3b
+ sha512sums = 6015b870143f6b8ae094b3f94ad53323be8a083f11c177dc508315fb3bbc20dd318124e6ccd41ba9d0388828e18c4b4ae6ce7c4a35ac0cab442eca9e8bbbca2d
+ sha512sums = 9875bf209439f62fc2e6773b1283a6ed0a2adbe33e7b6461678a5f036679570a98393814eeadcb20b9d9160b79aca0b1a1769eab39be2d1913b64439abd76f62
+ sha512sums = 4744fec4faf63b36f9c82a7404e4ecc749eb07e8d90640465ff7a2ae20cdb2560348b0a125467fad215d0e51d88d27a991dc0196899e53d19479dec942d6e52d
pkgname = weewx
diff --git a/.gitignore b/.gitignore
index c40c2f1392b3..40f01e7946ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,16 +5,6 @@
!.gitignore
!PKGBUILD
!.SRCINFO
-!wee_config
-!wee_database
-!wee_debug
-!wee_device
-!wee_extension
-!wee_import
-!wee_reports
-!weewx.install
!weewx.service
-!weewxd
-!wunderfixer
-!weewx-version.patch
-!genplot.py
+!weewx.sysusers
+!weewx.tmpfiles
diff --git a/PKGBUILD b/PKGBUILD
index 40a6e47a4714..d3489dec6e19 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -4,72 +4,50 @@
# Supports automatic update checking
pkgname=weewx
-_MAJOR=4
-_MINOR=10
-_PATCH=2
+_MAJOR=5
+_MINOR=0
+_PATCH=0
pkgver=$_MAJOR.$_MINOR.$_PATCH
function _dl_url {
echo "https://github.com/weewx/weewx/archive/refs/tags/v$1.$2.$3.tar.gz"
}
-pkgrel=7
+pkgrel=1
pkgdesc="Software for logging data from weather stations"
arch=("any")
url="http://www.weewx.com/"
-license=("GPL3")
+license=("GPL-3.0-or-later")
depends=("python"
- "python-daemon"
- "python-six"
+ "python-cheetah3"
"python-configobj"
- "python-pyserial"
- "python-pillow"
- "python-pyusb"
- "python-cheetah3")
-optdepends=("python-pyephem: extended almanac information"
- "mariadb-clients: MariaDB support"
- "python-mysqlclient: MariaDB support")
+ "python-pillow")
+optdepends=("python-pyserial: serial port support"
+ "python-pyusb: USB port support"
+ "python-pyephem: extended celestial information"
+ "python-pymysql: MySQL or MariaDB support")
+makedepends=("mkdocs"
+ "mkdocs-material"
+ "mkdocs-material-extensions")
backup=("etc/weewx/weewx.conf")
source=("$pkgname-$pkgver.tar.xz::$(_dl_url $_MAJOR $_MINOR $_PATCH)"
- "wee_config"
- "wee_database"
- "wee_debug"
- "wee_device"
- "wee_extension"
- "wee_import"
- "wee_reports"
- "weewxd"
- "wunderfixer"
- "weewx.service"
- "genplot.py"
- "weewx-version.patch"
- "pillow-rect.patch")
-sha512sums=('8fca9cd7720a29687a0d900e4d89ec2ce5ca5d2aa36bc5b5909ea14ecb849cdbdb6e699cf1c3a0d5505c89ad8c309517db32fd8dc4a0ae4704dfd5ed0cc5747f'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP'
- 'SKIP')
+ "weewx.sysusers"
+ "weewx.tmpfiles"
+ "weewx.service")
+sha512sums=('c3eb23048012f2bab7eac0a25f6dfad4da4ff1591601f7242cf7fec91343e88f3014b7281c021c75cfdbb5b4e73285ab8605325a3c7771e90a5016e3d574dc3b'
+ '6015b870143f6b8ae094b3f94ad53323be8a083f11c177dc508315fb3bbc20dd318124e6ccd41ba9d0388828e18c4b4ae6ce7c4a35ac0cab442eca9e8bbbca2d'
+ '9875bf209439f62fc2e6773b1283a6ed0a2adbe33e7b6461678a5f036679570a98393814eeadcb20b9d9160b79aca0b1a1769eab39be2d1913b64439abd76f62'
+ '4744fec4faf63b36f9c82a7404e4ecc749eb07e8d90640465ff7a2ae20cdb2560348b0a125467fad215d0e51d88d27a991dc0196899e53d19479dec942d6e52d')
_watch="http://www.weewx.com/downloads/"
-prepare() {
- cd "$srcdir/${pkgname}-${pkgver}"
- patch --strip=1 --input="$srcdir/weewx-version.patch"
- patch --strip=1 --input="$srcdir/pillow-rect.patch"
- cp -v "$srcdir/genplot.py" bin/weeplot
-}
-
build() {
cd "$srcdir/${pkgname}-${pkgver}"
- python setup.py build
+
+ echo "Compiling Python bytecode"
+ python -m compileall -q "src"
+
+ echo "Building documentation"
+ make --quiet build-docs
}
_install() {
@@ -88,79 +66,78 @@ _install() {
package() {
cd "$srcdir/${pkgname}-${pkgver}"
- echo "Renning install using setup.py script"
- local setup_base="$pkgdir/${pkgname}"
- python setup.py install --root="$setup_base" --optimize=1 --skip-build --no-prompt
-
- local target_bin_dir="$pkgdir/usr/bin"
- local target_doc_dir="$pkgdir/usr/share/doc/$pkgname"
- local target_etc_dir="$pkgdir/etc/$pkgname"
- local target_lib_dir="$pkgdir/usr/lib/$pkgname"
- local target_share_dir="$pkgdir/usr/share/$pkgname"
-
- local target_license_file="$pkgdir/usr/share/licenses/$pkgname/LICENSE"
- local target_unit="${pkgname}.service"
- local target_unit_file="$pkgdir/usr/lib/systemd/system/${target_unit}"
- local target_conf_file="$target_etc_dir/weewx.conf"
-
- local source_bin=("${srcdir}/wee_config"
- "${srcdir}/wee_database"
- "${srcdir}/wee_debug"
- "${srcdir}/wee_device"
- "${srcdir}/wee_extension"
- "${srcdir}/wee_import"
- "${srcdir}/wee_reports"
- "${srcdir}/weewxd"
- "${srcdir}/wunderfixer")
- local source_unit_file=("${srcdir}/${target_unit}")
- local source_doc=("${setup_base}/home/weewx/docs"/*)
- local source_conf_file="${setup_base}/home/weewx/weewx.conf"
- local source_etc=("${setup_base}/home/weewx/skins" "$source_conf_file")
- local source_lib=("${setup_base}/home/weewx/bin"/*)
- local source_license_file=("${setup_base}/home/weewx/LICENSE.txt")
- local source_share=("${setup_base}/home/weewx/examples"
- "${setup_base}/home/weewx/util/apache"
- "${setup_base}/home/weewx/util/import"
- "${setup_base}/home/weewx/util/logrotate.d"
- "${setup_base}/home/weewx/util/logwatch"
- "${setup_base}/home/weewx/util/newsyslog.d"
- "${setup_base}/home/weewx/util/rsyslog.d"
- "${setup_base}/home/weewx/util/udev")
-
- echo "Installing binaries"
- _install "$target_bin_dir" 755 "${source_bin[@]}"
-
- echo "Installing library files"
- _install "$target_lib_dir" 644 "${source_lib[@]}"
-
- echo "Installing configuration"
- _install "$target_etc_dir" 644 "${source_etc[@]}"
-
- echo "Installing documentation"
- _install "$target_doc_dir" 644 "${source_doc[@]}"
-
- echo "Installing shared files"
- _install "$target_share_dir" 644 "${source_share[@]}"
-
- echo "Installing license"
- install -Dm644 "$source_license_file" "$target_license_file"
-
- echo "Installing unit file"
- install -Dm644 "$source_unit_file" "$target_unit_file"
-
- echo "Removing temp setup"
- rm -r "$setup_base"
-
- echo "Removing unnecessary files"
- find "$pkgdir" -name "*.egg-info" -type f -delete
-
- echo "Fixing ownership"
- chown -R root:root "$pkgdir"
-
- echo "Adapting configuration file"
- sed -i "$target_conf_file" \
- -r -e "s@(.*WEEWX_ROOT).*@\1 = /@g" \
- -r -e "s@(.*SKIN_ROOT).*@\1 = /etc/$pkgname/skins@g" \
- -r -e "s@(.*HTML_ROOT).*@\1 = /srv/http/html/$pkgname@g" \
- -r -e "s@(.*SQLITE_ROOT).*@\1 = /var/lib/$pkgname@g"
+ local PYTHON='python'
+ local weewx_bindir='/usr/lib/weewx'
+ local weewx_sharedir="/usr/share/weewx"
+ local weewx_etcdir="/etc/weewx"
+ local weewx_vardir="/var/lib/weewx"
+ local entrypoints=('weewxd' 'weectl')
+
+ local bindir="$pkgdir/usr/bin"
+ local libdir="${pkgdir}${weewx_bindir}"
+ local sharedir="${pkgdir}${weewx_sharedir}"
+ local licensedir="$pkgdir/usr/share/licenses/weewx"
+ local docdir="$pkgdir/usr/share/doc/weewx"
+ local etcdir="${pkgdir}${weewx_etcdir}"
+ local unitdir="$pkgdir/usr/lib/systemd/system"
+ local sysusersdir="$pkgdir/usr/lib/sysusers.d"
+ local tmpfilesdir="$pkgdir/usr/lib/tmpfiles.d"
+ local udevdir="$pkgdir/usr/lib/udev/rules.d"
+
+ echo "Copying source files"
+ [[ -d "$libdir" ]] || mkdir -p "$libdir"
+ _install "$libdir" 644 "src"/*
+ _install "$libdir" 755 "src/weewx_data/bin"
+
+ echo "Creating entrypoints"
+ [[ -d "$bindir" ]] || mkdir -p "$bindir"
+ for f in ${entrypoints[@]}; do
+ echo "Creating entrypoint $f"
+ sed \
+ -e "s%WEEWX_BINDIR=.*%WEEWX_BINDIR=\"${weewx_bindir}\"%" \
+ -e "s%WEEWX_PYTHON=.*%WEEWX_PYTHON=\"${PYTHON}\"%" \
+ "bin/$f" > "$bindir/$f"
+ chmod 755 "$bindir/$f"
+ done
+
+ echo "Copying license and copyright"
+ install -Dm644 "docs_src/copyright.md" "$licensedir/copyright.md"
+ install -Dm644 "LICENSE.txt" "$licensedir/LICENSE.txt"
+
+ echo "Copying documentation"
+ [[ -d "$docdir" ]] || mkdir -p "$docdir"
+ _install "$docdir" 644 "build/docs"/*
+
+ echo "Copying skins"
+ [[ -d "$sharedir" ]] || mkdir -p "$sharedir"
+ _install "$sharedir" 644 "src/weewx_data/skins"
+
+ echo "Copying ancillary files"
+ _install "$sharedir" 644 "src/weewx_data/examples"
+ _install "$sharedir" 644 "src/weewx_data/util/import"
+ _install "$sharedir" 644 "src/weewx_data/util/logwatch"
+ _install "$sharedir" 644 "src/weewx_data/util/systemd"
+ _install "$sharedir" 644 "src/weewx_data/util/udev"
+
+ echo "Creating configuration file"
+ [[ -d "$etcdir" ]] || mkdir -p "$etcdir"
+ sed \
+ -e "/log_failure =.*/aWEEWX_ROOT = $weewx_bindir" \
+ -e "s%SKIN_ROOT =.*%SKIN_ROOT = $weewx_sharedir/skins%" \
+ -e "s%HTML_ROOT = public_html%HTML_ROOT = $weewx_vardir/www%" \
+ -e "s%SQLITE_ROOT = .*%SQLITE_ROOT = $weewx_vardir%" \
+ "src/weewx_data/weewx.conf" > "$etcdir/weewx.conf"
+ chmod 640 "$etcdir/weewx.conf"
+
+ echo "Creating systemd unit"
+ install -Dm640 "$srcdir/weewx.service" "$unitdir/weewx.service"
+
+ echo "Creating system user definition"
+ install -Dm640 "$srcdir/weewx.sysusers" "$sysusersdir/weewx.conf"
+
+ echo "Creating tmpfiles definition"
+ install -Dm640 "$srcdir/weewx.tmpfiles" "$tmpfilesdir/weewx.conf"
+
+ echo "Creating udev ruleset"
+ install -Dm640 "src/weewx_data/util/udev/rules.d/weewx.rules" "$udevdir/weewx.rules"
}
diff --git a/genplot.py b/genplot.py
deleted file mode 100644
index 7094d770e052..000000000000
--- a/genplot.py
+++ /dev/null
@@ -1,737 +0,0 @@
-#
-# Copyright (c) 2009-2023 Tom Keffer <tkeffer@gmail.com>
-#
-# See the file LICENSE.txt for your full rights.
-#
-"""Routines for generating image plots."""
-
-import colorsys
-import locale
-import os
-import time
-
-from PIL import Image, ImageDraw, ImageFont
-
-import weeplot.utilities
-import weeutil.weeutil
-from weeplot.utilities import tobgr
-from weeutil.weeutil import max_with_none, min_with_none, to_bool
-
-# Test if this version of Pillow has ImageFont.getbbox. If not, we will activate a workaround.
-try:
- ImageFont.ImageFont.getbbox
-except AttributeError:
- PIL_HAS_BBOX = False
-else:
- PIL_HAS_BBOX = True
-
-
-class GeneralPlot(object):
- """Holds various parameters necessary for a plot. It should be specialized by the type of plot.
- """
- def __init__(self, plot_dict):
- """Initialize an instance of GeneralPlot.
-
- plot_dict: an instance of ConfigObj, or something that looks like it.
- """
-
- self.line_list = []
-
- self.xscale = (None, None, None)
- self.yscale = (None, None, None)
-
- self.anti_alias = int(plot_dict.get('anti_alias', 1))
-
- self.image_width = int(plot_dict.get('image_width', 300)) * self.anti_alias
- self.image_height = int(plot_dict.get('image_height', 180)) * self.anti_alias
- self.image_background_color = tobgr(plot_dict.get('image_background_color', '0xf5f5f5'))
-
- self.chart_background_color = tobgr(plot_dict.get('chart_background_color', '0xd8d8d8'))
- self.chart_gridline_color = tobgr(plot_dict.get('chart_gridline_color', '0xa0a0a0'))
- color_list = plot_dict.get('chart_line_colors', ['0xff0000', '0x00ff00', '0x0000ff'])
- fill_color_list = plot_dict.get('chart_fill_colors', color_list)
- width_list = plot_dict.get('chart_line_width', [1, 1, 1])
- self.chart_line_colors = [tobgr(v) for v in color_list]
- self.chart_fill_colors = [tobgr(v) for v in fill_color_list]
- self.chart_line_widths = [int(v) for v in width_list]
-
-
- self.top_label_font_path = plot_dict.get('top_label_font_path')
- self.top_label_font_size = int(plot_dict.get('top_label_font_size', 10)) * self.anti_alias
-
- self.unit_label = None
- self.unit_label_font_path = plot_dict.get('unit_label_font_path')
- self.unit_label_font_color = tobgr(plot_dict.get('unit_label_font_color', '0x000000'))
- self.unit_label_font_size = int(plot_dict.get('unit_label_font_size', 10)) * self.anti_alias
- self.unit_label_position = (10 * self.anti_alias, 0)
-
- self.bottom_label = u""
- self.bottom_label_font_path = plot_dict.get('bottom_label_font_path')
- self.bottom_label_font_color= tobgr(plot_dict.get('bottom_label_font_color', '0x000000'))
- self.bottom_label_font_size = int(plot_dict.get('bottom_label_font_size', 10)) * self.anti_alias
- self.bottom_label_offset = int(plot_dict.get('bottom_label_offset', 3))
-
- self.axis_label_font_path = plot_dict.get('axis_label_font_path')
- self.axis_label_font_color = tobgr(plot_dict.get('axis_label_font_color', '0x000000'))
- self.axis_label_font_size = int(plot_dict.get('axis_label_font_size', 10)) * self.anti_alias
-
- # Make sure the formats used for the x- and y-axes are in unicode.
- self.x_label_format = plot_dict.get('x_label_format')
- self.y_label_format = plot_dict.get('y_label_format')
-
- self.x_nticks = int(plot_dict.get('x_nticks', 10))
- self.y_nticks = int(plot_dict.get('y_nticks', 10))
-
- self.x_label_spacing = int(plot_dict.get('x_label_spacing', 2))
- self.y_label_spacing = int(plot_dict.get('y_label_spacing', 2))
-
- # Calculate sensible margins for the given image and font sizes.
- self.y_label_side = plot_dict.get('y_label_side', 'left')
- if self.y_label_side == 'left' or self.y_label_side == 'both':
- self.lmargin = int(4.0 * self.axis_label_font_size)
- else:
- self.lmargin = 20 * self.anti_alias
- if self.y_label_side == 'right' or self.y_label_side == 'both':
- self.rmargin = int(4.0 * self.axis_label_font_size)
- else:
- self.rmargin = 20 * self.anti_alias
- self.bmargin = int(1.5 * (self.bottom_label_font_size + self.axis_label_font_size) + 0.5)
- self.tmargin = int(1.5 * self.top_label_font_size + 0.5)
- self.tbandht = int(1.2 * self.top_label_font_size + 0.5)
- self.padding = 3 * self.anti_alias
-
- self.render_rose = False
- self.rose_width = int(plot_dict.get('rose_width', 21))
- self.rose_height = int(plot_dict.get('rose_height', 21))
- self.rose_diameter = int(plot_dict.get('rose_diameter', 10))
- self.rose_position = (self.lmargin + self.padding + 5, self.image_height - self.bmargin - self.padding - self.rose_height)
- self.rose_rotation = None
- self.rose_label = plot_dict.get('rose_label', 'N')
- self.rose_label_font_path = plot_dict.get('rose_label_font_path', self.bottom_label_font_path)
- self.rose_label_font_size = int(plot_dict.get('rose_label_font_size', 10))
- self.rose_label_font_color = tobgr(plot_dict.get('rose_label_font_color', '0x000000'))
- self.rose_line_width = int(plot_dict.get('rose_line_width', 1))
- self.rose_color = plot_dict.get('rose_color')
- if self.rose_color is not None:
- self.rose_color = tobgr(self.rose_color)
-
- # Show day/night transitions
- self.show_daynight = to_bool(plot_dict.get('show_daynight', False))
- self.daynight_day_color = tobgr(plot_dict.get('daynight_day_color', '0xffffff'))
- self.daynight_night_color = tobgr(plot_dict.get('daynight_night_color', '0xf0f0f0'))
- self.daynight_edge_color = tobgr(plot_dict.get('daynight_edge_color', '0xefefef'))
- self.daynight_gradient = int(plot_dict.get('daynight_gradient', 20))
-
- # initialize the location
- self.latitude = None
- self.longitude = None
-
- # normalize the font paths relative to the skin directory
- skin_dir = plot_dict.get('skin_dir', '')
- self.top_label_font_path = self.normalize_path(skin_dir, self.top_label_font_path)
- self.bottom_label_font_path = self.normalize_path(skin_dir, self.bottom_label_font_path)
- self.unit_label_font_path = self.normalize_path(skin_dir, self.unit_label_font_path)
- self.axis_label_font_path = self.normalize_path(skin_dir, self.axis_label_font_path)
- self.rose_label_font_path = self.normalize_path(skin_dir, self.rose_label_font_path)
-
- @staticmethod
- def normalize_path(skin_dir, path):
- if path is None:
- return None
- return os.path.join(skin_dir, path)
-
- def setBottomLabel(self, bottom_label):
- """Set the label to be put at the bottom of the plot. """
- self.bottom_label = bottom_label
-
- def setUnitLabel(self, unit_label):
- """Set the label to be used to show the units of the plot. """
- self.unit_label = unit_label
-
- def setXScaling(self, xscale):
- """Set the X scaling.
-
- xscale: A 3-way tuple (xmin, xmax, xinc)
- """
- self.xscale = xscale
-
- def setYScaling(self, yscale):
- """Set the Y scaling.
-
- yscale: A 3-way tuple (ymin, ymax, yinc)
- """
- self.yscale = yscale
-
- def addLine(self, line):
- """Add a line to be plotted.
-
- line: an instance of PlotLine
- """
- if None in line.x:
- raise weeplot.ViolatedPrecondition("X vector cannot have any values 'None' ")
- self.line_list.append(line)
-
- def setLocation(self, lat, lon):
- self.latitude = lat
- self.longitude = lon
-
- def setDayNight(self, showdaynight, daycolor, nightcolor, edgecolor):
- """Configure day/night bands.
-
- showdaynight: Boolean flag indicating whether to draw day/night bands
-
- daycolor: color for day bands
-
- nightcolor: color for night bands
-
- edgecolor: color for transition between day and night
- """
- self.show_daynight = showdaynight
- self.daynight_day_color = daycolor
- self.daynight_night_color = nightcolor
- self.daynight_edge_color = edgecolor
-
- def render(self):
- """Traverses the universe of things that have to be plotted in this image, rendering
- them and returning the results as a new Image object.
- """
-
- # NB: In what follows the variable 'draw' is an instance of an ImageDraw object and is in pixel units.
- # The variable 'sdraw' is an instance of ScaledDraw and its units are in the "scaled" units of the plot
- # (e.g., the horizontal scaling might be for seconds, the vertical for degrees Fahrenheit.)
- image = Image.new("RGB", (self.image_width, self.image_height), self.image_background_color)
- draw = ImageDraw.ImageDraw(image)
- draw.rectangle(((self.lmargin,self.tmargin),
- (self.image_width - self.rmargin, self.image_height - self.bmargin)),
- fill=self.chart_background_color)
-
- self._renderBottom(draw)
- self._renderTopBand(draw)
-
- self._calcXScaling()
- self._calcYScaling()
- self._calcXLabelFormat()
- self._calcYLabelFormat()
-
- sdraw = self._getScaledDraw(draw)
- if self.show_daynight:
- self._renderDayNight(sdraw)
- self._renderXAxes(sdraw)
- self._renderYAxes(sdraw)
- self._renderPlotLines(sdraw)
- if self.render_rose:
- self._renderRose(image, draw)
-
- if self.anti_alias != 1:
- image.thumbnail((self.image_width / self.anti_alias,
- self.image_height / self.anti_alias),
- Image.LANCZOS)
-
- return image
-
- def _getScaledDraw(self, draw):
- """Returns an instance of ScaledDraw, with the appropriate scaling.
-
- draw: An instance of ImageDraw
- """
- sdraw = weeplot.utilities.ScaledDraw(
- draw,
- (
- (self.lmargin + self.padding, self.tmargin + self.padding),
- (self.image_width - self.rmargin - self.padding, self.image_height - self.bmargin - self.padding)
- ),
- (
- (self.xscale[0], self.yscale[0]),
- (self.xscale[1], self.yscale[1])
- )
- )
- return sdraw
-
- def _renderDayNight(self, sdraw):
- """Draw vertical bands for day/night."""
- (first, transitions) = weeutil.weeutil.getDayNightTransitions(
- self.xscale[0], self.xscale[1], self.latitude, self.longitude)
- color = self.daynight_day_color \
- if first == 'day' else self.daynight_night_color
- xleft = self.xscale[0]
- for x in transitions:
- sdraw.rectangle(((xleft,self.yscale[0]),
- (x,self.yscale[1])), fill=color)
- xleft = x
- color = self.daynight_night_color \
- if color == self.daynight_day_color else self.daynight_day_color
- sdraw.rectangle(((xleft,self.yscale[0]),
- (self.xscale[1],self.yscale[1])), fill=color)
- if self.daynight_gradient:
- if first == 'day':
- color1 = self.daynight_day_color
- color2 = self.daynight_night_color
- else:
- color1 = self.daynight_night_color
- color2 = self.daynight_day_color
- nfade = self.daynight_gradient
- # gradient is longer at the poles than the equator
- d = 120 + 300 * (1 - (90.0 - abs(self.latitude)) / 90.0)
- for i in range(len(transitions)):
- last_ = self.xscale[0] if i == 0 else transitions[i-1]
- next_ = transitions[i+1] if i < len(transitions)-1 else self.xscale[1]
- for z in range(1,nfade):
- c = blend_hls(color2, color1, float(z)/float(nfade))
- rgbc = int2rgbstr(c)
- x1 = transitions[i]-d*(nfade+1)/2+d*z
- if last_ < x1 < next_:
- sdraw.rectangle(((x1, self.yscale[0]),
- (x1+d, self.yscale[1])),
- fill=rgbc)
- if color1 == self.daynight_day_color:
- color1 = self.daynight_night_color
- color2 = self.daynight_day_color
- else:
- color1 = self.daynight_day_color
- color2 = self.daynight_night_color
- # draw a line at the actual sunrise/sunset
- for x in transitions:
- sdraw.line((x,x),(self.yscale[0],self.yscale[1]),
- fill=self.daynight_edge_color)
-
- def _renderXAxes(self, sdraw):
- """Draws the x axis and vertical constant-x lines, as well as the labels. """
-
- axis_label_font = weeplot.utilities.get_font_handle(self.axis_label_font_path,
- self.axis_label_font_size)
-
- drawlabelcount = 0
- for x in weeutil.weeutil.stampgen(self.xscale[0], self.xscale[1], self.xscale[2]) :
- sdraw.line((x, x),
- (self.yscale[0], self.yscale[1]),
- fill=self.chart_gridline_color,
- width=self.anti_alias)
- if drawlabelcount % self.x_label_spacing == 0 :
- xlabel = self._genXLabel(x)
- if PIL_HAS_BBOX:
- axis_label_width = sdraw.draw.textlength(xlabel, font=axis_label_font)
- else:
- axis_label_width, _ = sdraw.draw.textsize(xlabel, font=axis_label_font)
- xpos = sdraw.xtranslate(x)
- sdraw.draw.text((xpos - axis_label_width/2, self.image_height - self.bmargin + 2),
- xlabel, fill=self.axis_label_font_color, font=axis_label_font)
- drawlabelcount += 1
-
- def _renderYAxes(self, sdraw):
- """Draws the y axis and horizontal constant-y lines, as well as the labels.
- Should be sufficient for most purposes.
- """
- nygridlines = int((self.yscale[1] - self.yscale[0]) / self.yscale[2] + 1.5)
- axis_label_font = weeplot.utilities.get_font_handle(self.axis_label_font_path,
- self.axis_label_font_size)
-
- # Draw the (constant y) grid lines
- for i in range(nygridlines) :
- y = self.yscale[0] + i * self.yscale[2]
- sdraw.line((self.xscale[0], self.xscale[1]), (y, y), fill=self.chart_gridline_color,
- width=self.anti_alias)
- # Draw a label on every other line:
- if i % self.y_label_spacing == 0 :
- ylabel = self._genYLabel(y)
- if PIL_HAS_BBOX:
- left, top, right, bottom = axis_label_font.getbbox(ylabel)
- axis_label_width, axis_label_height = right - left, bottom - top
- else:
- axis_label_width, axis_label_height = sdraw.draw.textsize(ylabel,
- font=axis_label_font)
- ypos = sdraw.ytranslate(y)
- # We want to treat Truetype and bitmapped fonts the same. By default, Truetype
- # measures the top of the bounding box at the top of the ascender, while it's
- # the top of the text for bitmapped. Specify an anchor of "lt" (left, top) for
- # both. NB: argument "anchor" has been around at least as early as
- # Pillow V5.0 (2018), but was not implemented until V8.0.0.
- if self.y_label_side == 'left' or self.y_label_side == 'both':
- sdraw.draw.text((self.lmargin - axis_label_width - 2, ypos - axis_label_height/2),
- ylabel, fill=self.axis_label_font_color, font=axis_label_font,
- anchor="lt")
- if self.y_label_side == 'right' or self.y_label_side == 'both':
- sdraw.draw.text((self.image_width - self.rmargin + 4, ypos - axis_label_height/2),
- ylabel, fill=self.axis_label_font_color, font=axis_label_font,
- anchor="lt")
-
- def _renderPlotLines(self, sdraw):
- """Draw the collection of lines, using a different color for each one. Because there is
- a limited set of colors, they need to be recycled if there are very many lines.
- """
- nlines = len(self.line_list)
- ncolors = len(self.chart_line_colors)
- nfcolors = len(self.chart_fill_colors)
- nwidths = len(self.chart_line_widths)
-
- # Draw them in reverse order, so the first line comes out on top of the image
- for j, this_line in enumerate(self.line_list[::-1]):
-
- iline=nlines-j-1
- color = self.chart_line_colors[iline%ncolors] if this_line.color is None else this_line.color
- fill_color = self.chart_fill_colors[iline%nfcolors] if this_line.fill_color is None else this_line.fill_color
- width = (self.chart_line_widths[iline%nwidths] if this_line.width is None else this_line.width) * self.anti_alias
-
- # Calculate the size of a gap in data
- maxdx = None
- if this_line.line_gap_fraction is not None:
- maxdx = this_line.line_gap_fraction * (self.xscale[1] - self.xscale[0])
-
- if this_line.plot_type == 'line':
- ms = this_line.marker_size
- if ms is not None:
- ms *= self.anti_alias
- sdraw.line(this_line.x,
- this_line.y,
- line_type=this_line.line_type,
- marker_type=this_line.marker_type,
- marker_size=ms,
- fill = color,
- width = width,
- maxdx = maxdx)
- elif this_line.plot_type == 'bar' :
- for x, y, bar_width in zip(this_line.x, this_line.y, this_line.bar_width):
- if y is None:
- continue
- sdraw.rectangle(((x - bar_width, self.yscale[0]), (x, y)), fill=fill_color, outline=color)
- elif this_line.plot_type == 'vector' :
- for (x, vec) in zip(this_line.x, this_line.y):
- sdraw.vector(x, vec,
- vector_rotate = this_line.vector_rotate,
- fill = color,
- width = width)
- self.render_rose = True
- self.rose_rotation = this_line.vector_rotate
- if self.rose_color is None:
- self.rose_color = color
-
- def _renderBottom(self, draw):
- """Draw anything at the bottom (just some text right now). """
- bottom_label_font = weeplot.utilities.get_font_handle(self.bottom_label_font_path,
- self.bottom_label_font_size)
- if PIL_HAS_BBOX:
- left, top, right, bottom = bottom_label_font.getbbox(self.bottom_label)
- bottom_label_width, bottom_label_height = right - left, bottom - top
- else:
- bottom_label_width, bottom_label_height = draw.textsize(self.bottom_label,
- font=bottom_label_font)
- draw.text(((self.image_width - bottom_label_width)/2,
- self.image_height - bottom_label_height - self.bottom_label_offset),
- self.bottom_label,
- fill=self.bottom_label_font_color,
- font=bottom_label_font,
- anchor="lt")
-
- def _renderTopBand(self, draw):
- """Draw the top band and any text in it. """
- # Draw the top band rectangle
- draw.rectangle(((0,0),
- (self.image_width, self.tbandht)),
- fill = self.chart_background_color)
-
- # Put the units in the upper left corner
- unit_label_font = weeplot.utilities.get_font_handle(self.unit_label_font_path,
- self.unit_label_font_size)
- if self.unit_label:
- if self.y_label_side == 'left' or self.y_label_side == 'both':
- draw.text(self.unit_label_position,
- self.unit_label,
- fill=self.unit_label_font_color,
- font=unit_label_font)
- if self.y_label_side == 'right' or self.y_label_side == 'both':
- unit_label_position_right = (self.image_width - self.rmargin + 4, 0)
- draw.text(unit_label_position_right,
- self.unit_label,
- fill=self.unit_label_font_color,
- font=unit_label_font)
-
- top_label_font = weeplot.utilities.get_font_handle(self.top_label_font_path,
- self.top_label_font_size)
-
- # The top label is the appended label_list. However, it has to be drawn in segments
- # because each label may be in a different color. For now, append them together to get
- # the total width
- top_label = u' '.join([line.label for line in self.line_list])
- if PIL_HAS_BBOX:
- top_label_width= draw.textlength(top_label, font=top_label_font)
- else:
- top_label_width, _ = draw.textsize(top_label, font=top_label_font)
-
- x = (self.image_width - top_label_width)/2
- y = 0
-
- ncolors = len(self.chart_line_colors)
- for i, this_line in enumerate(self.line_list):
- color = self.chart_line_colors[i%ncolors] if this_line.color is None else this_line.color
- # Draw a label
- draw.text( (x,y), this_line.label, fill = color, font = top_label_font)
- # Now advance the width of the label we just drew, plus a space:
- if PIL_HAS_BBOX:
- label_width = draw.textlength(this_line.label + u' ', font= top_label_font)
- else:
- label_width, _ = draw.textsize(this_line.label + u' ', font= top_label_font)
- x += label_width
-
- def _renderRose(self, image, draw):
- """Draw a compass rose."""
-
- rose_center_x = self.rose_width/2 + 1
- rose_center_y = self.rose_height/2 + 1
- barb_width = 3
- barb_height = 3
- # The background is all white with a zero alpha (totally transparent)
- rose_image = Image.new("RGBA", (self.rose_width, self.rose_height), (0x00, 0x00, 0x00, 0x00))
- rose_draw = ImageDraw.Draw(rose_image)
-
- fill_color = add_alpha(self.rose_color)
- # Draw the arrow straight up (North). First the shaft:
- rose_draw.line( ((rose_center_x, 0), (rose_center_x, self.rose_height)),
- width = self.rose_line_width,
- fill = fill_color)
- # Now the left barb:
- rose_draw.line( ((rose_center_x - barb_width, barb_height), (rose_center_x, 0)),
- width = self.rose_line_width,
- fill = fill_color)
- # And the right barb:
- rose_draw.line( ((rose_center_x, 0), (rose_center_x + barb_width, barb_height)),
- width = self.rose_line_width,
- fill = fill_color)
-
- rose_draw.ellipse(((rose_center_x - self.rose_diameter/2,
- rose_center_y - self.rose_diameter/2),
- (rose_center_x + self.rose_diameter/2,
- rose_center_y + self.rose_diameter/2)),
- outline = fill_color)
-
- # Rotate if necessary:
- if self.rose_rotation:
- rose_image = rose_image.rotate(self.rose_rotation)
- rose_draw = ImageDraw.Draw(rose_image)
-
- # Calculate the position of the "N" label:
- rose_label_font = weeplot.utilities.get_font_handle(self.rose_label_font_path,
- self.rose_label_font_size)
- if PIL_HAS_BBOX:
- left, top, right, bottom = rose_label_font.getbbox(self.rose_label)
- rose_label_width, rose_label_height = right - left, bottom - top
- else:
- rose_label_width, rose_label_height = draw.textsize(self.rose_label,
- font=rose_label_font)
-
- # Draw the label in the middle of the (possibly) rotated arrow
- rose_draw.text((rose_center_x - rose_label_width/2 - 1,
- rose_center_y - rose_label_height/2 - 1),
- self.rose_label,
- fill=add_alpha(self.rose_label_font_color),
- font=rose_label_font,
- anchor="lt")
-
- # Paste the image of the arrow on to the main plot. The alpha
- # channel of the image will be used as the mask.
- # This will cause the arrow to overlay the background plot
- image.paste(rose_image, self.rose_position, rose_image)
-
- def _calcXScaling(self):
- """Calculates the x scaling. It will probably be specialized by
- plots where the x-axis represents time.
- """
- (xmin, xmax) = self._calcXMinMax()
-
- self.xscale = weeplot.utilities.scale(xmin, xmax, self.xscale, nsteps=self.x_nticks)
-
- def _calcYScaling(self):
- """Calculates y scaling. Can be used 'as-is' for most purposes."""
- # The filter is necessary because unfortunately the value 'None' is not
- # excluded from min and max (i.e., min(None, x) is not necessarily x).
- # The try block is necessary because min of an empty list throws a
- # ValueError exception.
- ymin = ymax = None
- for line in self.line_list:
- if line.plot_type == 'vector':
- try:
- # For progressive vector plots, we want the magnitude of the complex vector
- yline_max = max(abs(c) for c in [v for v in line.y if v is not None])
- except ValueError:
- yline_max = None
- yline_min = - yline_max if yline_max is not None else None
- else:
- yline_min = min_with_none(line.y)
- yline_max = max_with_none(line.y)
- ymin = min_with_none([ymin, yline_min])
- ymax = max_with_none([ymax, yline_max])
-
- if ymin is None and ymax is None :
- # No valid data. Pick an arbitrary scaling
- self.yscale=(0.0, 1.0, 0.2)
- else:
- self.yscale = weeplot.utilities.scale(ymin, ymax, self.yscale, nsteps=self.y_nticks)
-
- def _calcXLabelFormat(self):
- if self.x_label_format is None:
- self.x_label_format = weeplot.utilities.pickLabelFormat(self.xscale[2])
-
- def _calcYLabelFormat(self):
- if self.y_label_format is None:
- self.y_label_format = weeplot.utilities.pickLabelFormat(self.yscale[2])
-
- def _genXLabel(self, x):
- xlabel = locale.format_string(self.x_label_format, x)
- return xlabel
-
- def _genYLabel(self, y):
- ylabel = locale.format_string(self.y_label_format, y)
- return ylabel
-
- def _calcXMinMax(self):
- xmin = xmax = None
- for line in self.line_list:
- xline_min = min_with_none(line.x)
- xline_max = max_with_none(line.x)
- # If the line represents a bar chart, then the actual minimum has to
- # be adjusted for the bar width of the first point
- if line.plot_type == 'bar':
- xline_min = xline_min - line.bar_width[0]
- xmin = min_with_none([xmin, xline_min])
- xmax = max_with_none([xmax, xline_max])
- return xmin, xmax
-
-
-class TimePlot(GeneralPlot) :
- """Class that specializes GeneralPlot for plots where the x-axis is time."""
-
- def _calcXScaling(self):
- """Specialized version for time plots."""
- if None in self.xscale:
- (xmin, xmax) = self._calcXMinMax()
- self.xscale = weeplot.utilities.scaletime(xmin, xmax)
-
- def _calcXLabelFormat(self):
- """Specialized version for time plots. Assumes that time is in unix epoch time."""
- if self.x_label_format is None:
- (xmin, xmax) = self._calcXMinMax()
- if xmin is not None and xmax is not None:
- delta = xmax - xmin
- if delta > 30*24*3600:
- self.x_label_format = u"%x"
- elif delta > 24*3600:
- self.x_label_format = u"%x %X"
- else:
- self.x_label_format = u"%X"
-
- def _genXLabel(self, x):
- """Specialized version for time plots. Assumes that time is in unix epoch time."""
- if self.x_label_format is None:
- return u''
- time_tuple = time.localtime(x)
- # There are still some strftimes out there that don't support Unicode.
- try:
- xlabel = time.strftime(self.x_label_format, time_tuple)
- except UnicodeEncodeError:
- # Convert it to UTF8, then back again:
- xlabel = time.strftime(self.x_label_format.encode('utf-8'), time_tuple).decode('utf-8')
- return xlabel
-
-
-class PlotLine(object):
- """Represents a single line (or bar) in a plot. """
- def __init__(self, x, y, label='', color=None, fill_color=None, width=None, plot_type='line',
- line_type='solid', marker_type=None, marker_size=10,
- bar_width=None, vector_rotate = None, line_gap_fraction=None):
- self.x = x
- self.y = y
- self.label = label
- self.plot_type = plot_type
- self.line_type = line_type
- self.marker_type = marker_type
- self.marker_size = marker_size
- self.color = color
- self.fill_color = fill_color
- self.width = width
- self.bar_width = bar_width
- self.vector_rotate = vector_rotate
- self.line_gap_fraction = line_gap_fraction
-
-
-def blend_hls(c, bg, alpha):
- """Fade from c to bg using alpha channel where 1 is solid and 0 is
- transparent. This fades across the hue, saturation, and lightness."""
- return blend(c, bg, alpha, alpha, alpha)
-
-
-def blend_ls(c, bg, alpha):
- """Fade from c to bg where 1 is solid and 0 is transparent.
- Change only the lightness and saturation, not hue."""
- return blend(c, bg, 1.0, alpha, alpha)
-
-
-def blend(c, bg, alpha_h, alpha_l, alpha_s):
- """Fade from c to bg in the hue, lightness, saturation colorspace.
- Added hue directionality to choose shortest circular hue path e.g.
- https://stackoverflow.com/questions/1416560/hsl-interpolation
- Also, grey detection to minimize colour wheel travel. Interesting resource:
- http://davidjohnstone.net/pages/lch-lab-colour-gradient-picker
- """
-
- r1,g1,b1 = int2rgb(c)
- h1,l1,s1 = colorsys.rgb_to_hls(r1/255.0, g1/255.0, b1/255.0)
-
- r2,g2,b2 = int2rgb(bg)
- h2,l2,s2 = colorsys.rgb_to_hls(r2/255.0, g2/255.0, b2/255.0)
-
- # Check if either of the values is grey (saturation 0),
- # in which case don't needlessly reset hue to '0', reducing travel around colour wheel
- if s1 == 0: h1 = h2
- if s2 == 0: h2 = h1
-
- h_delta = h2 - h1
-
- if abs(h_delta) > 0.5:
- # If interpolating over more than half-circle (0.5 radians) take shorter, opposite direction...
- h_range = 1.0 - abs(h_delta)
- h_dir = +1.0 if h_delta < 0.0 else -1.0
-
- # Calculte h based on line back from h2 as proportion of h_range and alpha
- h = h2 - ( h_dir * h_range * alpha_h )
-
- # Clamp h within 0.0 to 1.0 range
- h = h + 1.0 if h < 0.0 else h
- h = h - 1.0 if h > 1.0 else h
- else:
- # Interpolating over less than a half-circle, so use normal interpolation as before
- h = alpha_h * h1 + (1 - alpha_h) * h2
-
- l = alpha_l * l1 + (1 - alpha_l) * l2
- s = alpha_s * s1 + (1 - alpha_s) * s2
-
- r,g,b = colorsys.hls_to_rgb(h, l, s)
-
- r = round(r * 255.0)
- g = round(g * 255.0)
- b = round(b * 255.0)
-
- t = rgb2int(int(r),int(g),int(b))
-
- return int(t)
-
-
-def int2rgb(x):
- b = (x >> 16) & 0xff
- g = (x >> 8) & 0xff
- r = x & 0xff
- return r,g,b
-
-
-def int2rgbstr(x):
- return '#%02x%02x%02x' % int2rgb(x)
-
-
-def rgb2int(r,g,b):
- return r + g*256 + b*256*256
-
-
-def add_alpha(i):
- """Add an opaque alpha channel to an integer RGB value"""
- r = i & 0xff
- g = (i >> 8) & 0xff
- b = (i >> 16) & 0xff
- a = 0xff # Opaque alpha
- return r,g,b,a
diff --git a/pillow-rect.patch b/pillow-rect.patch
deleted file mode 100644
index 5d2aa04ec641..000000000000
--- a/pillow-rect.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-diff --git a/bin/weeplot/utilities.py b/bin/weeplot/utilities.py
-index 73e955bd..f1c9d33a 100644
---- a/bin/weeplot/utilities.py
-+++ b/bin/weeplot/utilities.py
-@@ -431,14 +431,20 @@ class ScaledDraw(object):
-
- def rectangle(self, box, **options):
- """Draw a scaled rectangle.
--
-- box: A pair of 2-way tuples, containing coordinates of opposing corners
-- of the box.
--
-+
-+ box: A pair of 2-way tuples for the lower-left, then upper-right corners of
-+ the box [(llx, lly), (urx, ury)]
-+
- options: passed on to draw.rectangle. Usually contains 'fill' (the color)
- """
-- box_scaled = [(coord[0] * self.xscale + self.xoffset + 0.5,
-- coord[1] * self.yscale + self.yoffset + 0.5) for coord in box]
-+ # Unpack the box
-+ (llsx, llsy), (ursx, ursy) = box
-+
-+ ulix = int(llsx * self.xscale + self.xoffset + 0.5)
-+ uliy = int(ursy * self.yscale + self.yoffset + 0.5)
-+ lrix = int(ursx * self.xscale + self.xoffset + 0.5)
-+ lriy = int(llsy * self.yscale + self.yoffset + 0.5)
-+ box_scaled = ((ulix, uliy), (lrix, lriy))
- self.draw.rectangle(box_scaled, **options)
-
- def vector(self, x, vec, vector_rotate, **options):
diff --git a/wee_config b/wee_config
deleted file mode 100644
index d5c7d44145d6..000000000000
--- a/wee_config
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wee_config
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/wee_database b/wee_database
deleted file mode 100644
index 6267ae332d85..000000000000
--- a/wee_database
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wee_database
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/wee_debug b/wee_debug
deleted file mode 100644
index 82e8aef58f7e..000000000000
--- a/wee_debug
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wee_debug
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/wee_device b/wee_device
deleted file mode 100644
index 99788679332f..000000000000
--- a/wee_device
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wee_device
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/wee_extension b/wee_extension
deleted file mode 100644
index b9d58941f3f8..000000000000
--- a/wee_extension
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wee_extension
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/wee_import b/wee_import
deleted file mode 100644
index 487283c1be11..000000000000
--- a/wee_import
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wee_import
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/wee_reports b/wee_reports
deleted file mode 100644
index fba70fe8303d..000000000000
--- a/wee_reports
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wee_reports
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/weewx-version.patch b/weewx-version.patch
deleted file mode 100644
index 22d99178e79a..000000000000
--- a/weewx-version.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-diff --git a/bin/weewx/__init__.py b/bin/weewx/__init__.py
-index e97c4d8b..436fd783 100644
---- a/bin/weewx/__init__.py
-+++ b/bin/weewx/__init__.py
-@@ -7,7 +7,7 @@
- from __future__ import absolute_import
- import time
-
--__version__="4.10.1"
-+__version__="4.10.2"
-
- # Holds the program launch time in unix epoch seconds:
- # Useful for calculating 'uptime.'
-diff --git a/setup.py b/setup.py
-index 93e295b9..256b303e 100755
---- a/setup.py
-+++ b/setup.py
-@@ -32,7 +32,7 @@ from distutils.command.install_lib import install_lib
- from distutils.core import setup
- from distutils.debug import DEBUG
-
--VERSION = "4.10.1"
-+VERSION = "4.10.2"
-
- if sys.version_info < (2, 7):
- log.fatal('WeeWX requires Python V2.7 or greater.')
-diff --git a/weewx.conf b/weewx.conf
-index 517af0b9..cd98bac6 100644
---- a/weewx.conf
-+++ b/weewx.conf
-@@ -20,7 +20,7 @@ log_success = True
- log_failure = True
-
- # Do not modify this. It is used when installing and updating weewx.
--version = 4.10.1
-+version = 4.10.2
-
- ##############################################################################
-
diff --git a/weewx.service b/weewx.service
index f648519c454f..9517eebeaecf 100644
--- a/weewx.service
+++ b/weewx.service
@@ -1,14 +1,15 @@
[Unit]
-Description=weewx weather system
+Description=WeeWX weather system
Requires=time-sync.target
After=time-sync.target
-RequiresMountsFor=/etc /var/lib
+RequiresMountsFor=/etc/weewx /var/lib/weewx /var/lib/weewx/www
[Service]
ExecStart=/usr/bin/weewxd /etc/weewx/weewx.conf
StandardOutput=null
-#User=weewx
-#Group=weewx
+StandardError=journal
+User=weewx
+Group=weewx
[Install]
WantedBy=multi-user.target
diff --git a/weewx.sysusers b/weewx.sysusers
new file mode 100755
index 000000000000..15bb4655e47f
--- /dev/null
+++ b/weewx.sysusers
@@ -0,0 +1,3 @@
+# WeeWX System User Definition
+#Type Name ID GECOS Home directory Shell
+u weewx - "WeeWX weather system" /var/lib/weewx -
diff --git a/weewx.tmpfiles b/weewx.tmpfiles
new file mode 100755
index 000000000000..14a5a35660a0
--- /dev/null
+++ b/weewx.tmpfiles
@@ -0,0 +1,4 @@
+# WeeWX directory permissions
+#Type Path Mode User Group Age Argument…
+d /var/lib/weewx 750 weewx weewx -
+d /var/lib/weewx/www 755 weewx weewx -
diff --git a/weewxd b/weewxd
deleted file mode 100644
index a288cdcdb317..000000000000
--- a/weewxd
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=weewxd
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*
diff --git a/wunderfixer b/wunderfixer
deleted file mode 100644
index 2aeb62bc4c55..000000000000
--- a/wunderfixer
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-app=wunderfixer
-
-WEEWX_BINDIR=/usr/lib/weewx
-WEEWX_PYTHON=python
-$WEEWX_PYTHON $WEEWX_BINDIR/$app $*