From 6576183b8bf96d9f12dd43c82c33d014f16c297b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 12 Jun 2024 17:16:57 +0100 Subject: [PATCH 01/16] Repackage to hatch/pyproject.toml. --- .github/workflows/build.yml | 8 +- .github/workflows/qa.yml | 11 +- .github/workflows/test.yml | 5 +- Makefile | 10 +- check.sh | 23 ++-- install.sh | 264 ++++++++++++++++++++++++++---------- pyproject.toml | 10 +- tox.ini | 5 +- uninstall.sh | 26 ++-- 9 files changed, 250 insertions(+), 112 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3cfa5c..07620e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,10 +19,10 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} @@ -35,7 +35,7 @@ jobs: make build - name: Upload Packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.RELEASE_FILE }} - path: dist/ \ No newline at end of file + path: dist/ diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 4f17183..ac672a5 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -10,16 +10,15 @@ jobs: test: name: linting & spelling runs-on: ubuntu-latest - env: TERM: xterm-256color steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python '3,11' - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -33,4 +32,8 @@ jobs: - name: Run Code Checks run: | - make check \ No newline at end of file + make check + + - name: Run Bash Code Checks + run: | + make shellcheck diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4522974..6f8cff7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} @@ -37,4 +37,5 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | python -m pip install coveralls - coveralls --service=github \ No newline at end of file + coveralls --service=github + diff --git a/Makefile b/Makefile index 9e0c15c..56cf0df 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,9 @@ endif @echo "deploy: build and upload to PyPi" @echo "tag: tag the repository with the current version\n" +version: + @hatch version + install: ./install.sh --unstable @@ -30,11 +33,14 @@ uninstall: dev-deps: python3 -m pip install -r requirements-dev.txt - sudo apt install dos2unix + sudo apt install dos2unix shellcheck check: @bash check.sh +shellcheck: + shellcheck *.sh + qa: tox -e qa @@ -44,7 +50,7 @@ pytest: nopost: @bash check.sh --nopost -tag: +tag: version git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" build: check diff --git a/check.sh b/check.sh index 4ca0d17..38dfc3a 100644 --- a/check.sh +++ b/check.sh @@ -3,9 +3,10 @@ # This script handles some basic QA checks on the source NOPOST=$1 -LIBRARY_NAME=`hatch project metadata name` -LIBRARY_VERSION=`hatch version | awk -F "." '{print $1"."$2"."$3}'` -POST_VERSION=`hatch version | awk -F "." '{print substr($4,0,length($4))}'` +LIBRARY_NAME=$(hatch project metadata name) +LIBRARY_VERSION=$(hatch version | awk -F "." '{print $1"."$2"."$3}') +POST_VERSION=$(hatch version | awk -F "." '{print substr($4,0,length($4))}') +TERM=${TERM:="xterm-256color"} success() { echo -e "$(tput setaf 2)$1$(tput sgr0)" @@ -28,7 +29,7 @@ while [[ $# -gt 0 ]]; do ;; *) if [[ $1 == -* ]]; then - printf "Unrecognised option: $1\n"; + printf "Unrecognised option: %s\n" "$1"; exit 1 fi POSITIONAL_ARGS+=("$1") @@ -39,8 +40,7 @@ done inform "Checking $LIBRARY_NAME $LIBRARY_VERSION\n" inform "Checking for trailing whitespace..." -grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO -if [[ $? -eq 0 ]]; then +if grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO; then warning "Trailing whitespace found!" exit 1 else @@ -49,8 +49,7 @@ fi printf "\n" inform "Checking for DOS line-endings..." -grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile -if [[ $? -eq 0 ]]; then +if grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile; then warning "DOS line-endings found!" exit 1 else @@ -59,8 +58,7 @@ fi printf "\n" inform "Checking CHANGELOG.md..." -cat CHANGELOG.md | grep ^${LIBRARY_VERSION} > /dev/null 2>&1 -if [[ $? -eq 1 ]]; then +if ! grep "^${LIBRARY_VERSION}" CHANGELOG.md > /dev/null 2>&1; then warning "Changes missing for version ${LIBRARY_VERSION}! Please update CHANGELOG.md." exit 1 else @@ -69,8 +67,7 @@ fi printf "\n" inform "Checking for git tag ${LIBRARY_VERSION}..." -git tag -l | grep -E "${LIBRARY_VERSION}$" -if [[ $? -eq 1 ]]; then +if ! git tag -l | grep -E "${LIBRARY_VERSION}$"; then warning "Missing git tag for version ${LIBRARY_VERSION}" fi printf "\n" @@ -84,4 +81,4 @@ if [[ $NOPOST ]]; then else success "OK" fi -fi \ No newline at end of file +fi diff --git a/install.sh b/install.sh index b45e7c8..3db90bc 100755 --- a/install.sh +++ b/install.sh @@ -1,22 +1,24 @@ #!/bin/bash -LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` -CONFIG=/boot/config.txt -DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` +LIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}') +CONFIG_FILE=config.txt +CONFIG_DIR="/boot/firmware" +DATESTAMP=$(date "+%Y-%m-%d-%H-%M-%S") CONFIG_BACKUP=false APT_HAS_UPDATED=false -RESOURCES_TOP_DIR=$HOME/Pimoroni -WD=`pwd` +RESOURCES_TOP_DIR="$HOME/Pimoroni" +VENV_BASH_SNIPPET="$RESOURCES_TOP_DIR/auto_venv.sh" +VENV_DIR="$HOME/.virtualenvs/pimoroni" USAGE="./install.sh (--unstable)" POSITIONAL_ARGS=() FORCE=false UNSTABLE=false -PYTHON="/usr/bin/python3" +PYTHON="python" +CMD_ERRORS=false user_check() { - if [ $(id -u) -eq 0 ]; then - printf "Script should not be run as root. Try './install.sh'\n" - exit 1 + if [ "$(id -u)" -eq 0 ]; then + fatal "Script should not be run as root. Try './install.sh'\n" fi } @@ -33,15 +35,6 @@ confirm() { fi } -prompt() { - read -r -p "$1 [y/N] " response < /dev/tty - if [[ $response =~ ^(yes|y|Y)$ ]]; then - true - else - false - fi -} - success() { echo -e "$(tput setaf 2)$1$(tput sgr0)" } @@ -51,51 +44,126 @@ inform() { } warning() { - echo -e "$(tput setaf 1)$1$(tput sgr0)" + echo -e "$(tput setaf 1)⚠ WARNING:$(tput sgr0) $1" +} + +fatal() { + echo -e "$(tput setaf 1)⚠ FATAL:$(tput sgr0) $1" + exit 1 +} + +find_config() { + if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then + CONFIG_DIR="/boot" + if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then + fatal "Could not find $CONFIG_FILE!" + fi + fi + inform "Using $CONFIG_FILE in $CONFIG_DIR" +} + +venv_bash_snippet() { + inform "Checking for $VENV_BASH_SNIPPET\n" + if [ ! -f "$VENV_BASH_SNIPPET" ]; then + inform "Creating $VENV_BASH_SNIPPET\n" + mkdir -p "$RESOURCES_TOP_DIR" + cat << EOF > "$VENV_BASH_SNIPPET" +# Add "source $VENV_BASH_SNIPPET" to your ~/.bashrc to activate +# the Pimoroni virtual environment automagically! +VENV_DIR="$VENV_DIR" +if [ ! -f \$VENV_DIR/bin/activate ]; then + printf "Creating user Python environment in \$VENV_DIR, please wait...\n" + mkdir -p \$VENV_DIR + python3 -m venv --system-site-packages \$VENV_DIR +fi +printf " ↓ ↓ ↓ ↓ Hello, we've activated a Python venv for you. To exit, type \"deactivate\".\n" +source \$VENV_DIR/bin/activate +EOF + fi +} + +venv_check() { + PYTHON_BIN=$(which "$PYTHON") + if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then + printf "This script should be run in a virtual Python environment.\n" + if confirm "Would you like us to create and/or use a default one?"; then + printf "\n" + if [ ! -f "$VENV_DIR/bin/activate" ]; then + inform "Creating a new virtual Python environment in $VENV_DIR, please wait...\n" + mkdir -p "$VENV_DIR" + /usr/bin/python3 -m venv "$VENV_DIR" --system-site-packages + venv_bash_snippet + # shellcheck disable=SC1091 + source "$VENV_DIR/bin/activate" + else + inform "Activating existing virtual Python environment in $VENV_DIR\n" + printf "source \"%s/bin/activate\"\n" "$VENV_DIR" + # shellcheck disable=SC1091 + source "$VENV_DIR/bin/activate" + fi + else + printf "\n" + fatal "Please create and/or activate a virtual Python environment and try again!\n" + fi + fi + printf "\n" +} + +check_for_error() { + if [ $? -ne 0 ]; then + CMD_ERRORS=true + warning "^^^ 😬 previous command did not exit cleanly!" + fi } function do_config_backup { if [ ! $CONFIG_BACKUP == true ]; then CONFIG_BACKUP=true FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" - inform "Backing up $CONFIG to /boot/$FILENAME\n" - sudo cp $CONFIG /boot/$FILENAME - mkdir -p $RESOURCES_TOP_DIR/config-backups/ - cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME + inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n" + sudo cp "$CONFIG_DIR/$CONFIG_FILE" "$CONFIG_DIR/$FILENAME" + mkdir -p "$RESOURCES_TOP_DIR/config-backups/" + cp $CONFIG_DIR/$CONFIG_FILE "$RESOURCES_TOP_DIR/config-backups/$FILENAME" if [ -f "$UNINSTALLER" ]; then - echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER + echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> "$UNINSTALLER" fi fi } function apt_pkg_install { - PACKAGES=() + PACKAGES_NEEDED=() PACKAGES_IN=("$@") + # Check the list of packages and only run update/install if we need to for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do PACKAGE="${PACKAGES_IN[$i]}" if [ "$PACKAGE" == "" ]; then continue; fi - printf "Checking for $PACKAGE\n" - dpkg -L $PACKAGE > /dev/null 2>&1 + printf "Checking for %s\n" "$PACKAGE" + dpkg -L "$PACKAGE" > /dev/null 2>&1 if [ "$?" == "1" ]; then - PACKAGES+=("$PACKAGE") + PACKAGES_NEEDED+=("$PACKAGE") fi done - PACKAGES="${PACKAGES[@]}" + PACKAGES="${PACKAGES_NEEDED[*]}" if ! [ "$PACKAGES" == "" ]; then - echo "Installing missing packages: $PACKAGES" + printf "\n" + inform "Installing missing packages: $PACKAGES" if [ ! $APT_HAS_UPDATED ]; then sudo apt update APT_HAS_UPDATED=true fi + # shellcheck disable=SC2086 sudo apt install -y $PACKAGES + check_for_error if [ -f "$UNINSTALLER" ]; then - echo "apt uninstall -y $PACKAGES" >> $UNINSTALLER + echo "apt uninstall -y $PACKAGES" >> "$UNINSTALLER" fi fi } function pip_pkg_install { + # A null Keyring prevents pip stalling in the background PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@" + check_for_error } while [[ $# -gt 0 ]]; do @@ -116,8 +184,8 @@ while [[ $# -gt 0 ]]; do ;; *) if [[ $1 == -* ]]; then - printf "Unrecognised option: $1\n"; - printf "Usage: $USAGE\n"; + printf "Unrecognised option: %s\n" "$1"; + printf "Usage: %s\n" "$USAGE"; exit 1 fi POSITIONAL_ARGS+=("$1") @@ -125,119 +193,155 @@ while [[ $# -gt 0 ]]; do esac done +printf "Installing %s...\n\n" "$LIBRARY_NAME" + user_check +venv_check -if [ ! -f "$PYTHON" ]; then - printf "Python path $PYTHON not found!\n" - exit 1 +if [ ! -f "$(which "$PYTHON")" ]; then + fatal "Python path %s not found!\n" "$PYTHON" fi -PYTHON_VER=`$PYTHON --version` - -printf "$LIBRARY_NAME Python Library: Installer\n\n" +PYTHON_VER=$($PYTHON --version) inform "Checking Dependencies. Please wait..." +# Install toml and try to read pyproject.toml into bash variables + pip_pkg_install toml -CONFIG_VARS=`$PYTHON - < $UNINSTALLER +# Create a stub uninstaller file, we'll try to add the inverse of every +# install command run to here, though it's not complete. +cat << EOF > "$UNINSTALLER" printf "It's recommended you run these steps manually.\n" printf "If you want to run the full script, open it in\n" printf "an editor and remove 'exit 1' from below.\n" exit 1 +source $VIRTUAL_ENV/bin/activate EOF -if $UNSTABLE; then - warning "Installing unstable library from source.\n\n" -else - printf "Installing stable library from pypi.\n\n" -fi +printf "\n" inform "Installing for $PYTHON_VER...\n" + +# Install apt packages from pyproject.toml / tool.pimoroni.apt_packages apt_pkg_install "${APT_PACKAGES[@]}" + +printf "\n" + if $UNSTABLE; then + warning "Installing unstable library from source.\n" pip_pkg_install . else - pip_pkg_install $LIBRARY_NAME + inform "Installing stable library from pypi.\n" + pip_pkg_install "$LIBRARY_NAME" fi + +# shellcheck disable=SC2181 # One of two commands run, depending on --unstable flag if [ $? -eq 0 ]; then success "Done!\n" - echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER + echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> "$UNINSTALLER" fi -cd $WD +find_config + +printf "\n" +# Run the setup commands from pyproject.toml / tool.pimoroni.commands + +inform "Running setup commands...\n" for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do CMD="${SETUP_CMDS[$i]}" - # Attempt to catch anything that touches /boot/config.txt and trigger a backup - if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then + # Attempt to catch anything that touches config.txt and trigger a backup + if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG_DIR/$CONFIG_FILE"* ]] || [[ "$CMD" == *"\$CONFIG_DIR/\$CONFIG_FILE"* ]]; then do_config_backup fi - eval $CMD + if [[ ! "$CMD" == printf* ]]; then + printf "Running: \"%s\"\n" "$CMD" + fi + eval "$CMD" + check_for_error done +printf "\n" + +# Add the config.txt entries from pyproject.toml / tool.pimoroni.configtxt + for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do CONFIG_LINE="${CONFIG_TXT[$i]}" if ! [ "$CONFIG_LINE" == "" ]; then do_config_backup - inform "Adding $CONFIG_LINE to $CONFIG\n" - sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG - if ! grep -q "^$CONFIG_LINE" $CONFIG; then - printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG + inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE" + sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG_DIR/$CONFIG_FILE + if ! grep -q "^$CONFIG_LINE" $CONFIG_DIR/$CONFIG_FILE; then + printf "%s \n" "$CONFIG_LINE" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE fi fi done +printf "\n" + +# Just a straight copy of the examples/ dir into ~/Pimoroni/board/examples + if [ -d "examples" ]; then if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then inform "Copying examples to $RESOURCES_DIR" - cp -r examples/ $RESOURCES_DIR - echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER + cp -r examples/ "$RESOURCES_DIR" + echo "rm -r $RESOURCES_DIR" >> "$UNINSTALLER" success "Done!" fi fi printf "\n" +# Use pdoc to generate basic documentation from the installed module + if confirm "Would you like to generate documentation?"; then + inform "Installing pdoc. Please wait..." pip_pkg_install pdoc - printf "Generating documentation.\n" - $PYTHON -m pdoc $LIBRARY_NAME -o $RESOURCES_DIR/docs > /dev/null - if [ $? -eq 0 ]; then + inform "Generating documentation.\n" + if $PYTHON -m pdoc "$LIBRARY_NAME" -o "$RESOURCES_DIR/docs" > /dev/null; then inform "Documentation saved to $RESOURCES_DIR/docs" success "Done!" else @@ -245,6 +349,22 @@ if confirm "Would you like to generate documentation?"; then fi fi -success "\nAll done!" -inform "If this is your first time installing you should reboot for hardware changes to take effect.\n" -inform "Find uninstall steps in $UNINSTALLER\n" +printf "\n" + +if [ "$CMD_ERRORS" = true ]; then + warning "One or more setup commands appear to have failed." + printf "This might prevent things from working properly.\n" + printf "Make sure your OS is up to date and try re-running this installer.\n" + printf "If things still don't work, report this or find help at %s.\n\n" "$GITHUB_URL" +else + success "\nAll done!" +fi + +printf "If this is your first time installing you should reboot for hardware changes to take effect.\n" +printf "Find uninstall steps in %s\n\n" "$UNINSTALLER" + +if [ "$CMD_ERRORS" = true ]; then + exit 1 +else + exit 0 +fi diff --git a/pyproject.toml b/pyproject.toml index 6318fff..5b164d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,10 +35,10 @@ classifiers = [ "Topic :: System :: Hardware", ] dependencies = [ - "pimoroni-bme280", - "ltr559", - "pimoroni-ioexpander", - "st7789", + "pimoroni-bme280 >= 1.0.0", + "ltr559 >= 1.0.0", + "pimoroni-ioexpander >= 1.0.1", + "st7789 >= 1.0.0", "smbus2" ] @@ -116,7 +116,7 @@ ignore = [ 'requirements-dev.txt' ] -[pimoroni] +[tool.pimoroni] apt_packages = [] configtxt = [] commands = [ diff --git a/tox.ini b/tox.ini index a088862..4726cef 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ commands = python -m build --no-isolation python -m twine check dist/* isort --check . - ruff --format=github . + ruff check . codespell . deps = check-manifest @@ -30,4 +30,5 @@ deps = twine build hatch - hatch-fancy-pypi-readme \ No newline at end of file + hatch-fancy-pypi-readme + diff --git a/uninstall.sh b/uninstall.sh index d5e1b5f..3314b7f 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,13 +1,22 @@ #!/bin/bash FORCE=false -LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` +LIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}') RESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME -PYTHON="/usr/bin/python3" +PYTHON="python" + + +venv_check() { + PYTHON_BIN=$(which $PYTHON) + if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then + printf "This script should be run in a virtual Python environment.\n" + exit 1 + fi +} user_check() { - if [ $(id -u) -eq 0 ]; then - printf "Script should not be run as root. Try './install.sh'\n" + if [ "$(id -u)" -eq 0 ]; then + printf "Script should not be run as root. Try './uninstall.sh'\n" exit 1 fi } @@ -46,16 +55,17 @@ warning() { echo -e "$(tput setaf 1)$1$(tput sgr0)" } -printf "$LIBRARY_NAME Python Library: Uninstaller\n\n" +printf "%s Python Library: Uninstaller\n\n" "$LIBRARY_NAME" user_check +venv_check printf "Uninstalling for Python 3...\n" -$PYTHON -m pip uninstall $LIBRARY_NAME +$PYTHON -m pip uninstall "$LIBRARY_NAME" -if [ -d $RESOURCES_DIR ]; then +if [ -d "$RESOURCES_DIR" ]; then if confirm "Would you like to delete $RESOURCES_DIR?"; then - rm -r $RESOURCES_DIR + rm -r "$RESOURCES_DIR" fi fi From 507a965275f98d7aaa2fa0fa0961f1695dd614cc Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 12 Jun 2024 18:03:34 +0100 Subject: [PATCH 02/16] Port to gpiod/gpiodevice. --- examples/weather.py | 34 ++++++++++++++++++++++-------- weatherhat/__init__.py | 48 +++++++++++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/examples/weather.py b/examples/weather.py index e11e37b..c9a50f2 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -2,9 +2,13 @@ import math import pathlib import time +import select -import RPi.GPIO as GPIO -import ST7789 + +import gpiod +import gpiodevice +from gpiod.line import Bias, Edge +import st7789 import yaml from fonts.ttf import ManropeBold as UserFont from PIL import Image, ImageDraw, ImageFont @@ -94,7 +98,7 @@ def heading(self, data, units): else: data = "{:0.0f}".format(data) - tw, th = self._draw.textsize(data, self.font_large) + _, _, tw, th = self._draw.textbbox((0, 0), data, self.font_large) self._draw.text( (0, 32), @@ -326,7 +330,7 @@ def render(self): y = oy - math.cos(p) * radius name = "".join([word[0] for word in name.split(" ")]) - tw, th = self._draw.textsize(name, font=self.font_small) + _, _, tw, th = self._draw.textbbox((0, 0), name, font=self.font_small) x -= tw / 2 y -= th / 2 self._draw.text((x, y), name, font=self.font_small, fill=COLOR_GREY) @@ -507,12 +511,21 @@ def __init__(self, views): self._current_view = 0 self._current_subview = 0 - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP) + #GPIO.setmode(GPIO.BCM) + #GPIO.setwarnings(False) + #GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP) + + #for pin in BUTTONS: + # GPIO.add_event_detect(pin, GPIO.FALLING, self.handle_button, bouncetime=200) + config = {} for pin in BUTTONS: - GPIO.add_event_detect(pin, GPIO.FALLING, self.handle_button, bouncetime=200) + config[pin] = gpiod.LineSettings(edge_detection=Edge.FALLING, bias=Bias.PULL_UP) + + chip = gpiodevice.find_chip_by_platform() + self._buttons = chip.request_lines(consumer="LTR559", config=config) + self._poll = select.poll() + self._poll.register(self._buttons.fd, select.POLLIN) def handle_button(self, pin): index = BUTTONS.index(pin) @@ -562,6 +575,9 @@ def view(self): return self.get_current_view() def update(self): + if self._poll.poll(10): + for event in self._buttons.read_edge_events(): + self.handle_button(event.line_offset) self.view.update() def render(self): @@ -695,7 +711,7 @@ def update(self, interval=5.0): def main(): - display = ST7789.ST7789( + display = st7789.ST7789( rotation=90, port=0, cs=1, diff --git a/weatherhat/__init__.py b/weatherhat/__init__.py index c773cdb..c9f53f9 100644 --- a/weatherhat/__init__.py +++ b/weatherhat/__init__.py @@ -1,9 +1,12 @@ import math import threading import time +import select import ioexpander as io -import RPi.GPIO as GPIO +import gpiod +import gpiodevice +from gpiod.line import Bias, Edge from bme280 import BME280 from ltr559 import LTR559 from smbus2 import SMBus @@ -12,7 +15,6 @@ __version__ = '0.0.2' - # Wind Vane PIN_WV = 8 # P0.3 ANE6 @@ -46,16 +48,25 @@ class WeatherHAT: def __init__(self): self.updated_wind_rain = False + self._interrupt_pin = 4 self._lock = threading.Lock() self._i2c_dev = SMBus(1) self._bme280 = BME280(i2c_dev=self._i2c_dev) self._ltr559 = LTR559(i2c_dev=self._i2c_dev) - self._ioe = io.IOE(i2c_addr=0x12, interrupt_pin=4) + self._ioe = io.IOE(i2c_addr=0x12) + + self._chip = gpiodevice.find_chip_by_platform() - # Fudge to enable pull-up on interrupt pin - self._ioe._gpio.setup(self._ioe._interrupt_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) + self._int = self._chip.request_lines( + consumer="weatherhat", + config={ + self._interrupt_pin: gpiod.LineSettings( + edge_detection=Edge.FALLING, bias=Bias.PULL_UP + ) + } + ) # Input voltage of IO Expander, this is 3.3 on Breakout Garden self._ioe.set_adc_vref(3.3) @@ -77,8 +88,6 @@ def __init__(self): self._ioe.set_mode(PIN_R5, io.IN_PU) self._ioe.output(PIN_R3, 0) self._ioe.set_pin_interrupt(PIN_R4, True) - self._ioe.on_interrupt(self.handle_ioe_interrupt) - self._ioe.clear_interrupt() # Data API... kinda self.temperature_offset = -7.5 @@ -101,6 +110,16 @@ def __init__(self): self.reset_counts() + self._poll_thread = threading.Thread(target=self._t_poll_ioexpander) + self._poll_thread.start() + + self._ioe.enable_interrupt_out() + self._ioe.clear_interrupt() + + def __del__(self): + self._polling = False + self._poll_thread.join() + def reset_counts(self): self._lock.acquire(blocking=True) self._ioe.clear_switch_counter(PIN_ANE2) @@ -134,8 +153,21 @@ def hpa_to_inches(self, hpa): def degrees_to_cardinal(self, degrees): value, cardinal = min(wind_degrees_to_cardinal.items(), key=lambda item: abs(item[0] - degrees)) return cardinal + + def _t_poll_ioexpander(self): + self._polling = True + poll = select.poll() + poll.register(self._int.fd, select.POLLIN) + while self._polling: + if not poll.poll(10): + continue + for event in self._int.read_edge_events(): + if event.line_offset == self._interrupt_pin: + self.handle_ioe_interrupt() + time.sleep(1.0 / 100) def update(self, interval=60.0): + # Time elapsed since last update delta = float(time.time() - self._t_start) @@ -181,7 +213,7 @@ def update(self, interval=60.0): self.rain = rain_hz * RAIN_MM_PER_TICK - def handle_ioe_interrupt(self, pin): + def handle_ioe_interrupt(self): self._lock.acquire(blocking=True) self._ioe.clear_interrupt() From dabc7831539de970c1413bce278f2d6e62f9e08b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 13 Jun 2024 13:09:05 +0100 Subject: [PATCH 03/16] QA: Apply isort suggestions. --- examples/weather.py | 5 ++--- weatherhat/__init__.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/weather.py b/examples/weather.py index c9a50f2..303323b 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -1,16 +1,15 @@ #!/usr/bin/env python3 import math import pathlib -import time import select - +import time import gpiod import gpiodevice -from gpiod.line import Bias, Edge import st7789 import yaml from fonts.ttf import ManropeBold as UserFont +from gpiod.line import Bias, Edge from PIL import Image, ImageDraw, ImageFont import weatherhat diff --git a/weatherhat/__init__.py b/weatherhat/__init__.py index c9f53f9..744042f 100644 --- a/weatherhat/__init__.py +++ b/weatherhat/__init__.py @@ -1,13 +1,13 @@ import math +import select import threading import time -import select -import ioexpander as io import gpiod import gpiodevice -from gpiod.line import Bias, Edge +import ioexpander as io from bme280 import BME280 +from gpiod.line import Bias, Edge from ltr559 import LTR559 from smbus2 import SMBus From 131d027106d275d99c4a54a90718083878058b7f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 13 Jun 2024 13:12:28 +0100 Subject: [PATCH 04/16] QA: Apply ruff suggestions. --- examples/weather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/weather.py b/examples/weather.py index 303323b..65f4775 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -198,7 +198,7 @@ def draw_info(self, x, y, color, label, data, desc, right=False, vmin=0, vmax=20 vmax = max(vmax, max([h.value for h in data])) # auto ranging? self.graph(data, x + o_x + 30, y + 20, 180, 64, vmin=vmin, vmax=vmax, bar_width=20, colors=[color]) else: - if type(data) is list: + if isinstance(data, list): if len(data) > 0: data = data[-1].value else: From b1c64dbd5c2746cdc429d0d1e80b4403e252ae4e Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 13 Jun 2024 13:18:00 +0100 Subject: [PATCH 05/16] QA: Prune trailing whitespace. --- weatherhat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weatherhat/__init__.py b/weatherhat/__init__.py index 744042f..b3a669f 100644 --- a/weatherhat/__init__.py +++ b/weatherhat/__init__.py @@ -153,7 +153,7 @@ def hpa_to_inches(self, hpa): def degrees_to_cardinal(self, degrees): value, cardinal = min(wind_degrees_to_cardinal.items(), key=lambda item: abs(item[0] - degrees)) return cardinal - + def _t_poll_ioexpander(self): self._polling = True poll = select.poll() From 35410a8558890109a6c3e14f2db34dcecec83a92 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 13 Jun 2024 13:26:11 +0100 Subject: [PATCH 06/16] CI: Fixup tests for gpiod. --- testing/RPi/GPIO/__init__.py | 30 -------------------------- testing/{ST7789 => st7789}/__init__.py | 0 tests/conftest.py | 23 ++++++++++++++------ tests/test_setup.py | 10 ++++----- 4 files changed, 20 insertions(+), 43 deletions(-) delete mode 100644 testing/RPi/GPIO/__init__.py rename testing/{ST7789 => st7789}/__init__.py (100%) diff --git a/testing/RPi/GPIO/__init__.py b/testing/RPi/GPIO/__init__.py deleted file mode 100644 index c9e5593..0000000 --- a/testing/RPi/GPIO/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -BCM = 0 - -IN = 0 -OUT = 1 - -PUD_UP = 1 -PUD_DOWN = 0 - -RISING = 1 -FALLING = 0 - -handlers = {} - - -def setmode(pin): - pass - - -def setwarnings(mode): - pass - - -def setup(pin, direction, pull_up_down=None): - pass - - -def add_event_detect(pin, edge, handler, bouncetime=0): - if pin not in handlers: - handlers[pin] = {} - handlers[pin][edge] = handler diff --git a/testing/ST7789/__init__.py b/testing/st7789/__init__.py similarity index 100% rename from testing/ST7789/__init__.py rename to testing/st7789/__init__.py diff --git a/tests/conftest.py b/tests/conftest.py index 3d2d93c..9c6be8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,13 +29,22 @@ def smbus2(): del sys.modules['smbus2'] -@pytest.fixture(scope='function', autouse=False) -def gpio(): - sys.modules['RPi'] = mock.MagicMock() - sys.modules['RPi.GPIO'] = mock.MagicMock() - yield sys.modules['RPi.GPIO'] - del sys.modules['RPi.GPIO'] - del sys.modules['RPi'] +@pytest.fixture(scope="function", autouse=False) +def gpiod(): + """Mock gpiod module.""" + sys.modules["gpiod"] = mock.MagicMock() + sys.modules["gpiod.line"] = mock.MagicMock() + yield sys.modules["gpiod"] + del sys.modules["gpiod"] + + +@pytest.fixture(scope="function", autouse=False) +def gpiodevice(): + """Mock gpiodevice module.""" + sys.modules["gpiodevice"] = mock.MagicMock() + sys.modules["gpiodevice"].get_pin.return_value = (mock.Mock(), 0) + yield sys.modules["gpiodevice"] + del sys.modules["gpiodevice"] @pytest.fixture(scope='function', autouse=False) diff --git a/tests/test_setup.py b/tests/test_setup.py index 68c7928..3dd1875 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,17 +1,15 @@ -def test_setup(gpio, ioe, bme280, ltr559, smbus2): +def test_setup(gpiod, gpiodevice, ioe, bme280, ltr559, smbus2): import weatherhat - library = weatherhat.WeatherHAT() + _ = weatherhat.WeatherHAT() bus = smbus2.SMBus(1) bme280.BME280.assert_called_once_with(i2c_dev=bus) ltr559.LTR559.assert_called_once_with(i2c_dev=bus) - ioe.IOE.assert_called_once_with(i2c_addr=0x12, interrupt_pin=4) - - del library + ioe.IOE.assert_called_once_with(i2c_addr=0x12) -def test_api(gpio, ioe, bme280, ltr559, smbus2): +def test_api(gpiod, gpiodevice, ioe, bme280, ltr559, smbus2): import weatherhat library = weatherhat.WeatherHAT() From ec1d78f1c21014ea474caa7bf82a67feeb245b58 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 17 Jun 2024 13:59:24 +0100 Subject: [PATCH 07/16] pyproject.toml: Bump ST7789 version. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5b164d1..fb57d5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "pimoroni-bme280 >= 1.0.0", "ltr559 >= 1.0.0", "pimoroni-ioexpander >= 1.0.1", - "st7789 >= 1.0.0", + "st7789 >= 1.0.1", "smbus2" ] From ed7366b6bbe73f7ccaec349243c201a42c0afd6d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 17 Jun 2024 14:28:59 +0100 Subject: [PATCH 08/16] buttons.py: Port to gpiod. --- examples/buttons.py | 50 +++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/examples/buttons.py b/examples/buttons.py index 732ed8a..a2bca50 100644 --- a/examples/buttons.py +++ b/examples/buttons.py @@ -1,41 +1,47 @@ -import signal +import select +from datetime import timedelta -import RPi.GPIO as GPIO +import gpiod +import gpiodevice +from gpiod.line import Bias, Edge print("""buttons.py - Detect which button has been pressed This example should demonstrate how to: -1. set up RPi.GPIO to read buttons, +1. set up gpiod to read buttons, 2. determine which button has been pressed Press Ctrl+C to exit! """) +IP_PU_FE = gpiod.LineSettings(edge_detection=Edge.FALLING, bias=Bias.PULL_UP, debounce_period=timedelta(milliseconds=20)) + # The buttons on Weather HAT are connected to pins 5, 6, 16 and 24 -BUTTONS = [5, 6, 16, 24] +# They short to ground, so we must Bias them with the PULL_UP resistor +# and watch for a falling-edge. +BUTTONS = {5: IP_PU_FE, 6: IP_PU_FE, 16: IP_PU_FE, 24: IP_PU_FE} # These correspond to buttons A, B, X and Y respectively -LABELS = ['A', 'B', 'X', 'Y'] - -# Set up RPi.GPIO with the "BCM" numbering scheme -GPIO.setmode(GPIO.BCM) - -# Buttons connect to ground when pressed, so we should set them up -# with a "PULL UP", which weakly pulls the input signal to 3.3V. -GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP) +LABELS = {5: 'A', 6: 'B', 16: 'X', 24: 'Y'} +# Request the button pins from the gpiochip +chip = gpiodevice.find_chip_by_platform() +lines = chip.request_lines( + consumer="buttons.py", + config=BUTTONS + ) # "handle_button" will be called every time a button is pressed # It receives one argument: the associated input pin. def handle_button(pin): - label = LABELS[BUTTONS.index(pin)] + label = LABELS[pin] print("Button press detected on pin: {} label: {}".format(pin, label)) +# read_edge_events does not allow us to specify a timeout +# so we'll use poll to check if any events are waiting for us... +poll = select.poll() +poll.register(lines.fd, select.POLLIN) -# Loop through out buttons and attach the "handle_button" function to each -# We're watching the "FALLING" edge (transition from 3.3V to Ground) and -# picking a generous bouncetime of 100ms to smooth out button presses. -for pin in BUTTONS: - GPIO.add_event_detect(pin, GPIO.FALLING, handle_button, bouncetime=100) - -# Finally, since button handlers don't require a "while True" loop, -# we pause the script to prevent it exiting immediately. -signal.pause() \ No newline at end of file +# Poll for button events +while True: + if poll.poll(10): + for event in lines.read_edge_events(): + handle_button(event.line_offset) \ No newline at end of file From 52c47333ded2f15f7016ae07a546633ca76436cb Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 17 Jun 2024 14:37:08 +0100 Subject: [PATCH 09/16] weather.py: Add button debounce. --- examples/weather.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/weather.py b/examples/weather.py index 65f4775..ea03f18 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -3,6 +3,7 @@ import pathlib import select import time +from datetime import timedelta import gpiod import gpiodevice @@ -519,7 +520,11 @@ def __init__(self, views): config = {} for pin in BUTTONS: - config[pin] = gpiod.LineSettings(edge_detection=Edge.FALLING, bias=Bias.PULL_UP) + config[pin] = gpiod.LineSettings( + edge_detection=Edge.FALLING, + bias=Bias.PULL_UP, + debounce_period=timedelta(milliseconds=20) + ) chip = gpiodevice.find_chip_by_platform() self._buttons = chip.request_lines(consumer="LTR559", config=config) From af6f7f8c133badefbe999f20f64b4a94921f1adb Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 18 Jun 2024 13:15:49 +0100 Subject: [PATCH 10/16] pyproject.toml: Add sudo to i2c and SPI enable. --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fb57d5b..181d35b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -121,6 +121,6 @@ apt_packages = [] configtxt = [] commands = [ "printf \"Setting up i2c and SPI..\n\"", - "raspi-config nonint do_spi 0", - "raspi-config nonint do_i2c 0" -] \ No newline at end of file + "sudo raspi-config nonint do_spi 0", + "sudo raspi-config nonint do_i2c 0" +] From 227bb1d0015759e5f32d5a694a7f97147a337f02 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 18 Jun 2024 13:16:01 +0100 Subject: [PATCH 11/16] Add requirements-examples.txt. --- requirements-examples.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 requirements-examples.txt diff --git a/requirements-examples.txt b/requirements-examples.txt new file mode 100644 index 0000000..a1bd249 --- /dev/null +++ b/requirements-examples.txt @@ -0,0 +1,6 @@ +fonts +font-manrope +pyyaml +adafruit-io +numpy +pillow From b4f02f77441050a7d56f02ab496500e14bd4c958 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 18 Jun 2024 13:34:04 +0100 Subject: [PATCH 12/16] install.sh: check for and install requirements-examples.txt. --- install.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/install.sh b/install.sh index 3db90bc..61f1a4a 100755 --- a/install.sh +++ b/install.sh @@ -166,6 +166,12 @@ function pip_pkg_install { check_for_error } +function pip_requirements_install { + # A null Keyring prevents pip stalling in the background + PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install -r "$@" + check_for_error +} + while [[ $# -gt 0 ]]; do K="$1" case $K in @@ -335,6 +341,15 @@ fi printf "\n" +if [ -f "requirements-examples.txt" ]; then + if confirm "Would you like to install example dependencies?"; then + inform "Installing dependencies from requirements-examples.txt..." + pip_requirements_install requirements-examples.txt + fi +fi + +printf "\n" + # Use pdoc to generate basic documentation from the installed module if confirm "Would you like to generate documentation?"; then From a6b116a2ec2a7612e377459e26de895c69a6a10d Mon Sep 17 00:00:00 2001 From: Hel Gibbons Date: Wed, 19 Jun 2024 12:47:11 +0100 Subject: [PATCH 13/16] Update README.md --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c1ebbc5..eac2489 100644 --- a/README.md +++ b/README.md @@ -5,41 +5,74 @@ [![PyPi Package](https://img.shields.io/pypi/v/weatherhat.svg)](https://pypi.python.org/pypi/weatherhat) [![Python Versions](https://img.shields.io/pypi/pyversions/weatherhat.svg)](https://pypi.python.org/pypi/weatherhat) -# Pre-requisites +Weather HAT is a tidy all-in-one solution for hooking up climate and environmental sensors to a Raspberry Pi. It has a bright 1.54" LCD screen and four buttons for inputs. The onboard sensors can measure temperature, humidity, pressure and light. The RJ11 connectors will let you easily attach wind and rain sensors. It will work with any Raspberry Pi with a 40 pin header. -This library requires Python ≥3.6 so we'd recommend using it with Raspberry Pi OS Buster or later. +## Where to buy -You must enable: +* [Weather HAT](https://shop.pimoroni.com/products/weather-hat-only) +* [Weather HAT + Weather Sensors Kit](https://shop.pimoroni.com/products/weather-hat) -* i2c: `sudo raspi-config nonint do_i2c 0` -* spi: `sudo raspi-config nonint do_spi 0` +# Installing -You can optionally run `sudo raspi-config` or the graphical Raspberry Pi Configuration UI to enable interfaces. +We'd recommend using this library with Raspberry Pi OS Bookworm or later. It requires Python ≥3.6. -# Installing +## Full install (recommended): + +We've created an easy installation script that will install all pre-requisites and get your Weather HAT +up and running with minimal efforts. To run it, fire up Terminal which you'll find in Menu -> Accessories -> Terminal +on your Raspberry Pi desktop, as illustrated below: + +![Finding the terminal](http://get.pimoroni.com/resources/github-repo-terminal.png) + +In the new terminal window type the commands exactly as it appears below (check for typos) and follow the on-screen instructions: + +```bash +git clone https://github.com/pimoroni/weatherhat-python +cd weatherhat-python +./install.sh +``` + +**Note** Libraries will be installed in the "pimoroni" virtual environment, you will need to activate it to run examples: -Stable library from PyPi: +``` +source ~/.virtualenvs/pimoroni/bin/activate +``` + +## Development: + +If you want to contribute, or like living on the edge of your seat by having the latest code, you can install the development version like so: + +```bash +git clone https://github.com/pimoroni/weatherhat-python +cd weatherhat-python +./install.sh --unstable +``` + +## Install stable library from PyPi and configure manually + +* Set up a virtual environment: `python3 -m venv --system-site-packages $HOME/.virtualenvs/pimoroni` +* Switch to the virtual environment: `source ~/.virtualenvs/pimoroni/bin/activate` +* Install the library: `pip install weatherhat` -* Just run `pip3 install weatherhat` +In some cases you may need to us `sudo` or install pip with: `sudo apt install python3-pip`. -In some cases you may need to use `sudo` or install pip with: `sudo apt install python3-pip` +This will not make any configuration changes, so you may also need to enable: -Latest/development library from GitHub: +* i2c: `sudo raspi-config nonint do_i2c 0` +* spi: `sudo raspi-config nonint do_spi 0` -* `git clone https://github.com/pimoroni/weatherhat-python` -* `cd weatherhat-python` -* `./install.sh --unstable` +You can optionally run `sudo raspi-config` or the graphical Raspberry Pi Configuration UI to enable interfaces. -Some of the examples use additional libraries. You can install them with: +Some of the examples have additional dependencies. You can install them with: ```bash -pip3 install fonts font-manrope pyyaml adafruit-io numpy +pip install fonts font-manrope pyyaml adafruit-io numpy pillow ``` -You may also need to install `libatlas-base-dev` +You may also need to install `libatlas-base-dev`: ``` -sudo apt-get install libatlas-base-dev +sudo apt install libatlas-base-dev ``` # Using The Library From a8640850e183a876f85eee8f5662c43b2c1a1987 Mon Sep 17 00:00:00 2001 From: Hel Gibbons Date: Thu, 20 Jun 2024 14:37:03 +0100 Subject: [PATCH 14/16] Fix lcd.py example --- examples/lcd.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/lcd.py b/examples/lcd.py index fffe60c..df71332 100644 --- a/examples/lcd.py +++ b/examples/lcd.py @@ -1,24 +1,26 @@ #!/usr/bin/env python3 -import ST7789 from fonts.ttf import ManropeBold as UserFont from PIL import Image, ImageDraw, ImageFont +from st7789 import ST7789 -print(""" +print( + """ lcd.py - Hello, World! example on the 1.54" LCD. Press Ctrl+C to exit! -""") +""" +) SPI_SPEED_MHZ = 80 # Create LCD class instance. -disp = ST7789.ST7789( +disp = ST7789( rotation=90, port=0, cs=1, dc=9, backlight=12, - spi_speed_hz=SPI_SPEED_MHZ * 1000 * 1000 + spi_speed_hz=SPI_SPEED_MHZ * 1000 * 1000, ) # Initialize display. @@ -29,7 +31,7 @@ HEIGHT = disp.height # New canvas to draw on. -img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) # Text settings. @@ -39,7 +41,7 @@ back_colour = (0, 170, 170) message = "Hello, World!" -size_x, size_y = draw.textsize(message, font) +_, _, size_x, size_y = draw.textbbox((0, 0), message, font) # Calculate text position x = (WIDTH - size_x) / 2 @@ -57,4 +59,4 @@ # Turn off backlight on control-c except KeyboardInterrupt: - disp.set_backlight(0) \ No newline at end of file + disp.set_backlight(0) From c77d938ee8462020446bb75362f1b5b81be3e1d4 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 21 Jun 2024 10:55:18 +0100 Subject: [PATCH 15/16] README.md: Correct Python minimum version. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eac2489..92b2ea1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Weather HAT is a tidy all-in-one solution for hooking up climate and environment # Installing -We'd recommend using this library with Raspberry Pi OS Bookworm or later. It requires Python ≥3.6. +We'd recommend using this library with Raspberry Pi OS Bookworm or later. It requires Python ≥3.7. ## Full install (recommended): From 927800367ce0389d35608b67d11c9803bedd22ee Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 21 Jun 2024 11:09:48 +0100 Subject: [PATCH 16/16] Prep for v1.0.0. --- CHANGELOG.md | 6 ++++++ weatherhat/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17675fd..9982246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +1.0.0 +----- + +* Repackage to hatch/pyproject +* Port to gpiod (Pi 5 support) + 0.0.2 ----- diff --git a/weatherhat/__init__.py b/weatherhat/__init__.py index b3a669f..89d8d6a 100644 --- a/weatherhat/__init__.py +++ b/weatherhat/__init__.py @@ -13,7 +13,7 @@ from .history import wind_degrees_to_cardinal -__version__ = '0.0.2' +__version__ = '1.0.0' # Wind Vane PIN_WV = 8 # P0.3 ANE6