From 45dc89e51540cbf06049e07c272a1d2b6d3dcb7d Mon Sep 17 00:00:00 2001 From: dieter Date: Fri, 3 Sep 2021 16:09:00 +0200 Subject: [PATCH 1/5] image generation moved to separate repo (and removed from here) --- .github/workflows/crowpi.yml | 29 ------ README.md | 8 +- image/.gitignore | 13 --- image/crowpi.pkr.hcl | 72 -------------- image/crowpi.sh | 94 ------------------ image/resources/java/java-kiosk.py | 85 ---------------- image/resources/system/config.txt | 22 ---- image/resources/system/wpa-supplicant.conf | 8 -- .../wallpaper/wallpaper-autostart.desktop | 4 - image/resources/wallpaper/wallpaper-hook.sh | 84 ---------------- .../resources/wallpaper/wallpaper-static.jpg | Bin 33983 -> 0 bytes .../wallpaper/wallpaper-systemd.path | 5 - .../wallpaper/wallpaper-systemd.service | 7 -- src/main/java/com/pi4j/crowpi/Launcher.java | 10 +- 14 files changed, 9 insertions(+), 432 deletions(-) delete mode 100644 image/.gitignore delete mode 100644 image/crowpi.pkr.hcl delete mode 100644 image/crowpi.sh delete mode 100644 image/resources/java/java-kiosk.py delete mode 100644 image/resources/system/config.txt delete mode 100644 image/resources/system/wpa-supplicant.conf delete mode 100644 image/resources/wallpaper/wallpaper-autostart.desktop delete mode 100644 image/resources/wallpaper/wallpaper-hook.sh delete mode 100644 image/resources/wallpaper/wallpaper-static.jpg delete mode 100644 image/resources/wallpaper/wallpaper-systemd.path delete mode 100644 image/resources/wallpaper/wallpaper-systemd.service diff --git a/.github/workflows/crowpi.yml b/.github/workflows/crowpi.yml index 8f78539..0a54574 100644 --- a/.github/workflows/crowpi.yml +++ b/.github/workflows/crowpi.yml @@ -52,32 +52,3 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/site/apidocs - ################################################################################ - # Build CrowPi image with Packer - ################################################################################ - - name: Build CrowPi image with Packer - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - run: >- - docker run - --rm --privileged - --workdir /github/workspace/image - -v /dev:/dev - -v /home/runner/work/_temp/_github_home:/github/home - -v /home/runner/work/_temp/_github_workflow:/github/workflow - -v "${{ github.workspace }}:/github/workspace" - mkaczanowski/packer-builder-arm - build crowpi.pkr.hcl - - ################################################################################ - # Create a GitHub release - ################################################################################# - - name: Create release on GitHub - uses: ncipollo/release-action@v1 - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - with: - allowUpdates: ${{ github.ref == 'refs/tags/snapshot' }} - prerelease: ${{ github.ref == 'refs/tags/snapshot' }} - artifacts: >- - image/crowpi.img.zip, - image/crowpi.img.zip.sha256 - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 77a271a..47c21b9 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,7 @@ The CrowPi OS image mentioned further down below supports both workarounds out o ## CUSTOM OS IMAGE -Each [tagged GitHub release](https://github.com/Pi4J/pi4j-example-crowpi/releases/latest) provides a pre-built version of the custom OS -image called "CrowPi OS". It is based on the official "Raspberry Pi OS 2021-03-25" and automatically built using Packer. Using this image -provides the following set of benefits: +The Pi4J-team provides several pre-built [custom OS images](https://github.com/Pi4J/pi4j-os). It's highly recommended to use the so called [CrowPi OS](https://pi4j-download.com/main-crowpi.img.zip) for your CrowPi experiments to get the following set of benefits: - Preconfigured locale (en_US), keyboard (US) and timezone (Europe/Zurich) - Preconfigured wireless country (Switzerland) by default @@ -61,7 +59,7 @@ provides the following set of benefits: - Dynamic wallpaper which shows Ethernet/WLAN address and hostname - Comes with `lirc` preinstalled to run the IR receiver component -You may download the zip-compressed archive `crowpi.img.zip`, extract it and flash it with the imaging tool of your choice to get started. +Download the zip-compressed archive [main-crowpi.img.zip](https://pi4j-download.com/main-crowpi.img.zip), extract it and flash it with the imaging tool of your choice to get started. The default installation provides an user account `pi` with the password `crowpi` and sudo privileges. ## FRAMEWORK @@ -117,7 +115,7 @@ dtparam = i2c_arm=on dtparam = spi=on # Enable audio -dtparam = auto=on +dtparam = audio=on # Enable GPIO-IR dtoverlay = gpio-ir,gpio_pin=20 diff --git a/image/.gitignore b/image/.gitignore deleted file mode 100644 index e0a80b9..0000000 --- a/image/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Packer Output -/crowpi.img -/crowpi.img.zip -/crowpi.img.zip.sha256 - -# Packer Build -/packer_cache/ -/build.hwm -/build.pwd -/build.pwi - -# Local Development -/local/ diff --git a/image/crowpi.pkr.hcl b/image/crowpi.pkr.hcl deleted file mode 100644 index 8499f53..0000000 --- a/image/crowpi.pkr.hcl +++ /dev/null @@ -1,72 +0,0 @@ -source "arm" "crowpi" { - # Raspberry Pi OS with Desktop - file_urls = [ - "https://downloads.raspberrypi.org/raspios_armhf/images/raspios_armhf-2021-03-25/2021-03-04-raspios-buster-armhf.zip" - ] - file_checksum_url = "https://downloads.raspberrypi.org/raspios_armhf/images/raspios_armhf-2021-03-25/2021-03-04-raspios-buster-armhf.zip.sha256" - file_checksum_type = "sha256" - file_target_extension = "zip" - - # Image Options - image_build_method = "resize" - image_path = "crowpi.img" - image_type = "dos" - image_size = "6G" - - # Boot Partition - image_partitions { - name = "boot" - type = "c" - start_sector = 8192 - filesystem = "vfat" - size = "256M" - mountpoint = "/boot" - } - - # System Partition - image_partitions { - name = "root" - type = "83" - start_sector = 532480 - filesystem = "ext4" - size = "0" - mountpoint = "/" - } - - # QEMU Toolchain - qemu_binary_source_path = "/usr/bin/qemu-arm-static" - qemu_binary_destination_path = "/usr/bin/qemu-arm-static" -} - -build { - sources = [ - "sources.arm.crowpi" - ] - - provisioner "file" { - source = "./resources" - destination = "/tmp/resources" - } - - provisioner "shell" { - script = "crowpi.sh" - } - - post-processor "compress" { - output = "crowpi.img.zip" - compression_level = 6 - } - - post-processor "artifice" { - files = [ - "crowpi.img.zip" - ] - } - - post-processor "checksum" { - checksum_types = [ - "sha256" - ] - output = "crowpi.img.zip.sha256" - } -} diff --git a/image/crowpi.sh b/image/crowpi.sh deleted file mode 100644 index 274c19b..0000000 --- a/image/crowpi.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash - -# Script configuration -declare -gr GLUON_JAVAFX_URL="https://gluonhq.com/download/javafx-17-ea-sdk-linux-arm32/" -declare -gr GLUON_JAVAFX_PATH="/opt/javafx-sdk" -declare -gr GLUON_JAVAFX_VERSION_PATH="/opt/javafx-sdk-17" - -# Basic configuration -raspi-config nonint do_hostname crowpi - -# Change localization options -raspi-config nonint do_change_locale en_US.UTF-8 -raspi-config nonint do_configure_keyboard us -raspi-config nonint do_change_timezone Europe/Zurich - -# Enable remote management -raspi-config nonint do_ssh 0 -raspi-config nonint do_vnc 0 - -# Ensure required kernel modules are loaded -truncate -s0 /etc/modprobe.d/raspi-blacklist.conf -grep -qxF 'i2c-dev' || echo 'i2c-dev' >>/etc/modules - -# Enable additional interfaces -raspi-config nonint do_i2c 0 -raspi-config nonint do_spi 0 - -# Enable WiFi by default -for file in /var/lib/systemd/rfkill/*:wlan; do - echo 0 > "${file}" -done - -# Change default account passwords -echo 'root:crowpi' | chpasswd -echo 'pi:crowpi' | chpasswd - -# Install and upgrade software packages -export DEBIAN_FRONTEND=noninteractive -apt-get -qqy update -apt-get -qqy -o 'Dpkg::Options::=--force-confdef' -o 'Dpkg::Options::=--force-confold' upgrade -apt-get -qqy install \ - git \ - imagemagick \ - lirc \ - maven \ - openjdk-11-jdk -rm -rf /var/lib/apt/lists/* - -# Download and extract Gluon JavaFX -wget -O /tmp/gluon-javafx.zip "${GLUON_JAVAFX_URL}" -rm -rf "${GLUON_JAVAFX_VERSION_PATH}" -unzip -d /tmp /tmp/gluon-javafx.zip -rm -f /tmp/gluon-javafx.zip -mv /tmp/javafx-sdk-17 "${GLUON_JAVAFX_VERSION_PATH}" -ln -sf "${GLUON_JAVAFX_VERSION_PATH}" "${GLUON_JAVAFX_PATH}" - -# Create symlink to newest libgluon_drm -GLUON_JAVAFX_DRM="$(ls -v "${GLUON_JAVAFX_VERSION_PATH}"/lib/libgluon_drm-*.so | tail -n1)" -if [[ -n "${GLUON_JAVAFX_DRM}" ]]; then - ln -sf "${GLUON_JAVAFX_DRM}" "${GLUON_JAVAFX_VERSION_PATH}/lib/libgluon_drm.so" -else - echo "Unable to determine latest version of libgluon_drm" - exit 1 -fi - -# Deploy system configuration via /boot/config.txt -install -Dm 0644 /tmp/resources/system/config.txt /boot/config.txt - -# Deploy default WiFi configuration -install -Dm 0644 /tmp/resources/system/wpa-supplicant.conf /etc/wpa_supplicant/wpa_supplicant.conf - -# Disable getting started wizard -rm /etc/xdg/autostart/piwiz.desktop - -# Disable screen blanking by default -mkdir -p /etc/X11/xorg.conf.d/ -cp /usr/share/raspi-config/10-blanking.conf /etc/X11/xorg.conf.d/ - -# Remove default backgrounds -rm /usr/share/rpd-wallpaper/*.jpg - -# Override system-wide default wallpaper -sed -i 's/wallpaper=.*/wallpaper=\/opt\/fhnw\/wallpaper-static.jpg/g' /etc/xdg/pcmanfm/LXDE-pi/desktop-items-*.conf -sed -i 's/wallpaper_mode=.*/wallpaper_mode=stretch/g' /etc/xdg/pcmanfm/LXDE-pi/desktop-items-*.conf - -# Deploy dynamic wallpaper script and resources -sudo -u pi install -Dm 0644 /tmp/resources/wallpaper/wallpaper-autostart.desktop /home/pi/.config/autostart/fhnw-wallpaper.desktop -sudo -u pi install -Dm 0644 /tmp/resources/wallpaper/wallpaper-systemd.service /home/pi/.config/systemd/user/fhnw-wallpaper.service -sudo -u pi install -Dm 0644 /tmp/resources/wallpaper/wallpaper-systemd.path /home/pi/.config/systemd/user/fhnw-wallpaper.path -install -Dm 0755 /tmp/resources/wallpaper/wallpaper-hook.sh /lib/dhcpcd/dhcpcd-hooks/99-fhnw -install -Dm 0644 /tmp/resources/wallpaper/wallpaper-static.jpg /opt/fhnw/wallpaper-static.jpg - -# Deploy java-kiosk helper script for JavaFX apps -sudo install -Dm 0755 /tmp/resources/java/java-kiosk.py /usr/local/bin/java-kiosk diff --git a/image/resources/java/java-kiosk.py b/image/resources/java/java-kiosk.py deleted file mode 100644 index b58fece..0000000 --- a/image/resources/java/java-kiosk.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import shutil -import signal -import subprocess - -# Absolute path to Gluon JavaFX directory -SYSTEM_INIT_BIN = "/usr/sbin/init" -GLUON_JAVAFX_PATH = "/opt/javafx-sdk" - - -# Helper method to split JVM properties specified as -Dkey=value -def jvm_property(data): - parts = tuple(str(data).split('=', 1)) - return parts if len(parts) == 2 else (parts[0], '') - - -# Parse known arguments and preserve others -parser = argparse.ArgumentParser(description='Gluon JavaFX Kiosk Launcher', allow_abbrev=False) -parser.add_argument('--add-modules', default='') -parser.add_argument('-p', '--module-path', default='') -parser.add_argument('-D', default=[], action='append', type=jvm_property, dest='properties') -args, unknown_args = parser.parse_known_args() - -# Patch '--module-path' option -module_path = list(filter(None, args.module_path.split(':'))) -module_path.insert(0, GLUON_JAVAFX_PATH + '/lib') - -# Patch '--add-modules' option -add_modules = list(filter(None, args.add_modules.split(','))) -add_modules.insert(0, 'javafx.controls') - -# Patch generic properties -properties = dict(filter(None, args.properties)) -properties.setdefault('glass.platform', 'Monocle') -properties.setdefault('monocle.platform', 'EGL') -properties.setdefault('monocle.platform.traceConfig', 'false') -properties.setdefault('monocle.egl.lib', GLUON_JAVAFX_PATH + '/lib/libgluon_drm.so') -properties.setdefault('egl.displayid', '/dev/dri/card0') -properties.setdefault('javafx.verbose', 'false') -properties.setdefault('prism.verbose', 'false') - -# Patch 'java.library.path' property -java_library_path = list(filter(None, properties.get('java.library.path', '').split(':'))) -java_library_path.insert(0, GLUON_JAVAFX_PATH + '/lib') -properties['java.library.path'] = ':'.join(java_library_path) - -# Patch environment variables -jvm_env = os.environ.copy() -jvm_env['ENABLE_GLUON_COMMERCIAL_EXTENSIONS'] = 'true' - -# Build final list of JVM arguments -jvm_args = [ - '--module-path', ':'.join(module_path), - '--add-modules', ','.join(add_modules), -] -jvm_args.extend(['-D' + key + '=' + value for key, value in properties.items()]) -jvm_args.extend(unknown_args) - -# Search for absolute path of JVM -jvm_path = shutil.which('java') -if jvm_path is None: - parser.error("Unable to find 'java' binary in current PATH") - -# Ensure we are running as root -if os.geteuid() != 0: - parser.error("Unable to execute 'java-kiosk' without running as root") - -# Run application in kiosk mode -try: - # Ignore Ctrl+C for python process to ensure completion - signal.signal(signal.SIGINT, lambda signum, frame: None) - - # Switch to runlevel 3 to stop X11 - subprocess.run([SYSTEM_INIT_BIN, '3']) - - # Execute JVM with patched options - subprocess.run([jvm_path] + jvm_args, env=jvm_env) -except KeyboardInterrupt: - # Silently ignore KeyboardInterrupt, we expect the user to sometimes abort the script - pass -finally: - # Switch back to runlevel 5 to start X11 - subprocess.run([SYSTEM_INIT_BIN, '5']) diff --git a/image/resources/system/config.txt b/image/resources/system/config.txt deleted file mode 100644 index 929cb38..0000000 --- a/image/resources/system/config.txt +++ /dev/null @@ -1,22 +0,0 @@ -[all] -# Enable X with 128MB GPU memory and custom resolution -start_x=1 -gpu_mem=128 -hdmi_cvt 1024 600 60 6 0 0 0 - -# Enable I2C and SPI -dtparam=i2c_arm=on -dtparam=spi=on - -# Enable audio -dtparam=auto=on - -# Enable GPIO-IR -dtoverlay=gpio-ir,gpio_pin=20 - -# Enable DHT11 -dtoverlay=dht11,gpiopin=4 - -# Enable DRM VC4 V3D with up to 2 frame buffers -dtoverlay=vc4-fkms-v3d -max_framebuffers=2 diff --git a/image/resources/system/wpa-supplicant.conf b/image/resources/system/wpa-supplicant.conf deleted file mode 100644 index f46865b..0000000 --- a/image/resources/system/wpa-supplicant.conf +++ /dev/null @@ -1,8 +0,0 @@ -ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev -update_config=1 -country=CH - -network={ - ssid="fhnw-public" - key_mgmt=NONE -} diff --git a/image/resources/wallpaper/wallpaper-autostart.desktop b/image/resources/wallpaper/wallpaper-autostart.desktop deleted file mode 100644 index 634df06..0000000 --- a/image/resources/wallpaper/wallpaper-autostart.desktop +++ /dev/null @@ -1,4 +0,0 @@ -[Desktop Entry] -Type=Application -Name=FHNW Wallpaper -Exec=/usr/bin/systemctl --user start fhnw-wallpaper.path fhnw-wallpaper.service diff --git a/image/resources/wallpaper/wallpaper-hook.sh b/image/resources/wallpaper/wallpaper-hook.sh deleted file mode 100644 index 4c8608f..0000000 --- a/image/resources/wallpaper/wallpaper-hook.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/sh - -# Configuration variables -BASE_PATH="/opt/fhnw" -LAN_INTERFACE="eth0" -WLAN_INTERFACE="wlan0" -WP_INPUT_FILE="${BASE_PATH}/wallpaper-static.jpg" -WP_OUTPUT_FILE="${BASE_PATH}/wallpaper-dynamic.jpg" - -# Skip if reason is ROUTERADVERT (IPv6 RA happen every couple minutes) -if [ "${reason:-}" = "ROUTERADVERT" ]; then - exit 0 -fi - -# Skip if this does not affect our monitored interfaces -if [ -n "${interface:-}" ] && [ "${interface:-}" != "${LAN_INTERFACE}" ] && [ "${interface:-}" != "${WLAN_INTERFACE}" ]; then - exit 0 -fi - -# Determine IP address based on dhcpcd hook data if possible -case "${reason:-}" in - # If our interface has just been bound, use the passed IP address - BOUND) - if [ "${interface}" = "${LAN_INTERFACE}" ]; then - lan_address="${new_ip_address:-}" - elif [ "${interface}" = "${WLAN_INTERFACE}" ]; then - wlan_address="${new_ip_address:-}" - wlan_ssid="${ifssid:-}" - fi - ;; - # If our lease expired or interface went down, treat as not connected - EXPIRE | NOCARRIER) - if [ "${interface}" = "${LAN_INTERFACE}" ]; then - lan_address="" - elif [ "${interface}" = "${WLAN_INTERFACE}" ]; then - wlan_address="${new_ip_address:-}" - fi - ;; -esac - -# Detect LAN IP address from system if still unknown -if [ -z "${lan_address:-}" ]; then - lan_address="$(ip -4 a s "${LAN_INTERFACE}" | grep -Eo 'inet [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | awk '{print $2}')" - if [ -z "${lan_address}" ]; then - lan_address="" - fi -fi - -# Detect WLAN IP address from system if still unknown -if [ -z "${wlan_address:-}" ]; then - wlan_address="$(ip -4 a s "${WLAN_INTERFACE}" | grep -Eo 'inet [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | awk '{print $2}')" - if [ -z "${wlan_address}" ]; then - wlan_address="" - fi -fi - -# Detect WLAN SSID from system if still unknown -if [ -z "${wlan_ssid}" ]; then - wlan_ssid="$(iwgetid -r)" -fi - -# Build target string for WLAN state -if [ "${wlan_address}" != "" ] && [ -n "${wlan_ssid}" ]; then - wlan_state="${wlan_address} @ ${wlan_ssid}" -else - wlan_state="${wlan_address}" -fi - -# Generate wallpaper with network info -convert "${WP_INPUT_FILE}" \ - -gravity center \ - -pointsize 80 \ - -fill white \ - -draw "text 0,250 'Ethernet: ${lan_address}'" \ - -draw "text 0,350 'WLAN: ${wlan_state}'" \ - -draw "text 0,450 'Hostname: $(uname -n)'" \ - "${WP_OUTPUT_FILE}.new" - -# Atomically replace wallpaper if different from current one -if ! cmp --silent "${WP_OUTPUT_FILE}" "${WP_OUTPUT_FILE}.new"; then - mv -f "${WP_OUTPUT_FILE}.new" "${WP_OUTPUT_FILE}" -else - rm -f "${WP_OUTPUT_FILE}.new" -fi diff --git a/image/resources/wallpaper/wallpaper-static.jpg b/image/resources/wallpaper/wallpaper-static.jpg deleted file mode 100644 index 97264bf65b962c3558f010ad0a7c5b17a0bee925..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33983 zcmeFZcUV)~wl^HN+ie#S3!S3~s5C?GoQ(?7x1f{&L5S14bKWCPIin zAhf`i7LgJmkN_cxBot|hp@kCQ%|7S*o_o)I?)yFWd)`0Z^T(d+$yy_G%rVPvwK2yS za~(|{eFa=JGd48_96NRl@Q}X&ju<~Uo8G(U`iqUVv8lxaJ_7*w!NL#XAN1p00Kh*W zIMBw#=r0bApue1)2mA;)1`q&f08YC>f}a2U%P$sy|83eP@yqfPQSpzu{

cd&b#k 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 diff --git a/image/resources/wallpaper/wallpaper-systemd.path b/image/resources/wallpaper/wallpaper-systemd.path deleted file mode 100644 index e980426..0000000 --- a/image/resources/wallpaper/wallpaper-systemd.path +++ /dev/null @@ -1,5 +0,0 @@ -[Unit] -Description=Refresh wallpaper when file changes - -[Path] -PathModified=/opt/fhnw/wallpaper-dynamic.jpg diff --git a/image/resources/wallpaper/wallpaper-systemd.service b/image/resources/wallpaper/wallpaper-systemd.service deleted file mode 100644 index 60c7b46..0000000 --- a/image/resources/wallpaper/wallpaper-systemd.service +++ /dev/null @@ -1,7 +0,0 @@ -[Unit] -Description=Update FHNW wallpaper - -[Service] -Type=oneshot -ExecStartPre=/usr/bin/timeout 5 /bin/bash -c 'while ! pgrep --count -xf "pcmanfm --desktop --profile LXDE-pi" &>/dev/null; do sleep 0.5; done' -ExecStart=/usr/bin/pcmanfm --set-wallpaper=/opt/fhnw/wallpaper-dynamic.jpg --wallpaper-mode=stretch diff --git a/src/main/java/com/pi4j/crowpi/Launcher.java b/src/main/java/com/pi4j/crowpi/Launcher.java index 40c8bf4..8031fbb 100644 --- a/src/main/java/com/pi4j/crowpi/Launcher.java +++ b/src/main/java/com/pi4j/crowpi/Launcher.java @@ -54,7 +54,7 @@ public final class Launcher implements Runnable { /** * Pi4J context for CrowPi platform */ - private final Context pi4j; + private Context pi4j; /** * List of available applications to be executed @@ -86,9 +86,6 @@ public Launcher(List applications) { // Initialize PicoCLI instance this.cmdLine = new CommandLine(this); - // Initialize Pi4J context - this.pi4j = CrowPiPlatform.buildNewContext(); - // Register application runners as subcommands this.applications = applications; this.registerApplicationRunners(); @@ -115,7 +112,12 @@ public void run() { // Interactively ask the user for a desired target and run it // This loop will either run only once or forever, depending on the state of `demoMode` do { + // Initialize Pi4J context + pi4j = CrowPiPlatform.buildNewContext(); + // run the application getTargetInteractively(targets).run(); + // clean up + pi4j.shutdown(); } while (demoMode); } From 4fecc30f23d1179cc5410582327bd6fb3188ed90 Mon Sep 17 00:00:00 2001 From: Frank Delporte Date: Thu, 9 Sep 2021 15:49:52 +0200 Subject: [PATCH 2/5] Update src/main/java/com/pi4j/crowpi/Launcher.java Co-authored-by: Pascal Mathis --- src/main/java/com/pi4j/crowpi/Launcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/pi4j/crowpi/Launcher.java b/src/main/java/com/pi4j/crowpi/Launcher.java index 8031fbb..392ecf0 100644 --- a/src/main/java/com/pi4j/crowpi/Launcher.java +++ b/src/main/java/com/pi4j/crowpi/Launcher.java @@ -114,7 +114,7 @@ public void run() { do { // Initialize Pi4J context pi4j = CrowPiPlatform.buildNewContext(); - // run the application + // Run the application getTargetInteractively(targets).run(); // clean up pi4j.shutdown(); From 60c7f7bbb71312cfca68d01623e5ace21e147ac9 Mon Sep 17 00:00:00 2001 From: Frank Delporte Date: Thu, 9 Sep 2021 15:49:58 +0200 Subject: [PATCH 3/5] Update src/main/java/com/pi4j/crowpi/Launcher.java Co-authored-by: Pascal Mathis --- src/main/java/com/pi4j/crowpi/Launcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/pi4j/crowpi/Launcher.java b/src/main/java/com/pi4j/crowpi/Launcher.java index 392ecf0..d3cbd52 100644 --- a/src/main/java/com/pi4j/crowpi/Launcher.java +++ b/src/main/java/com/pi4j/crowpi/Launcher.java @@ -116,7 +116,7 @@ public void run() { pi4j = CrowPiPlatform.buildNewContext(); // Run the application getTargetInteractively(targets).run(); - // clean up + // Clean up pi4j.shutdown(); } while (demoMode); } From dc08a1d62cce57c9a909c43d3bb693b38971aa9c Mon Sep 17 00:00:00 2001 From: Frank Delporte Date: Thu, 9 Sep 2021 15:50:05 +0200 Subject: [PATCH 4/5] Update src/main/java/com/pi4j/crowpi/Launcher.java Co-authored-by: Pascal Mathis --- src/main/java/com/pi4j/crowpi/Launcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/pi4j/crowpi/Launcher.java b/src/main/java/com/pi4j/crowpi/Launcher.java index d3cbd52..46f5db0 100644 --- a/src/main/java/com/pi4j/crowpi/Launcher.java +++ b/src/main/java/com/pi4j/crowpi/Launcher.java @@ -54,7 +54,7 @@ public final class Launcher implements Runnable { /** * Pi4J context for CrowPi platform */ - private Context pi4j; + private Context pi4j; /** * List of available applications to be executed From 551a9b8db069c28326961b0afc0088f520f2cc95 Mon Sep 17 00:00:00 2001 From: Frank Delporte Date: Thu, 9 Sep 2021 15:50:16 +0200 Subject: [PATCH 5/5] Update README.md Co-authored-by: Pascal Mathis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47c21b9..f01ebf4 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The Pi4J-team provides several pre-built [custom OS images](https://github.com/P - Dynamic wallpaper which shows Ethernet/WLAN address and hostname - Comes with `lirc` preinstalled to run the IR receiver component -Download the zip-compressed archive [main-crowpi.img.zip](https://pi4j-download.com/main-crowpi.img.zip), extract it and flash it with the imaging tool of your choice to get started. +Download the [latest zip-compressed archive](https://pi4j-download.com/latest.php?flavor=crowpi), extract it and flash it with the imaging tool of your choice to get started. The default installation provides an user account `pi` with the password `crowpi` and sudo privileges. ## FRAMEWORK