From 99a5536db35692c84a487c74416f79af2f07fdf2 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 15 Aug 2023 00:25:41 +0200 Subject: [PATCH 001/121] Add info to install script and documentation about 64bit systems #2041 (#2057) * Add info to install script and documentation about 64bit systems #2041 * Fix flake8 error --- docs/sphinx/install.rst | 8 ++++---- installation/includes/02_helpers.sh | 1 + src/jukebox/jukebox/utils.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/sphinx/install.rst b/docs/sphinx/install.rst index 22d00fd78..948c79c42 100644 --- a/docs/sphinx/install.rst +++ b/docs/sphinx/install.rst @@ -12,9 +12,9 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr 1. Connect a Micro SD card to your computer (preferable an SD card with high read throughput) 2. `Download `_ the `Raspberry Pi Imager `_ and open it -3. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system (only the 32 bit version is supported) (you can use the settings menu of the Raspberry Pi Imager to configure SSH and WiFi in a more userfriendly way, or do it manually as described in the next step) +3. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system. future3 does not support 64bit kernels (``aarch64``). You can use the settings menu of the Raspberry Pi Imager to configure SSH and WiFi in a more userfriendly way, or do it manually as described in the next step. In case you already have a 64bit system installed, `you can fix the issue like this `_. 4. Select your Micro SD card (your card will be formatted) -5. Click *Write* +5. Click *Write* 6. Wait for the imaging process to be finished (it'll take a few minutes) @@ -87,8 +87,8 @@ This will switch directly to the specified feature branch during installation. .. attention:: For all branches *except* the current Release, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See :ref:`developer/development_environment:Steps to install`. - - + + If you suspect an error you can monitor the installation-process with diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index adfae9a52..d2e1162f1 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -51,6 +51,7 @@ check_os_type() { echo -e " ... OK!\n" | tee /dev/fd/3 else echo "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" | tee /dev/fd/3 + echo "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" | tee /dev/fd/3 exit 1 fi diff --git a/src/jukebox/jukebox/utils.py b/src/jukebox/jukebox/utils.py index b6d45d598..9be97b6db 100644 --- a/src/jukebox/jukebox/utils.py +++ b/src/jukebox/jukebox/utils.py @@ -168,7 +168,7 @@ def rpc_call_to_str(cfg_rpc_call: Dict, with_args=True) -> str: args_str = args else: try: - args_str = ", ".join([f"'{x}'" if type(x) == str else str(x) for x in args]) + args_str = ", ".join([f"'{x}'" if isinstance(x, str) else str(x) for x in args]) except TypeError: args_str = f"{args}" if kwargs is not None: From b11606a72c169e9625a77591acb5846d36e3f38d Mon Sep 17 00:00:00 2001 From: s-martin Date: Sat, 2 Sep 2023 12:51:56 +0200 Subject: [PATCH 002/121] Create dependabot.yml (#2022) --- .github/dependabot.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..099a56fcc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "monthly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "monthly" From f803185bbf2ad89dc9d0a394d625b76c58c0afbd Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Tue, 10 Oct 2023 21:15:54 +0200 Subject: [PATCH 003/121] future3: Abort installation if user is not "pi" (#2077) * check for user 'pi' and home '/home/pi' retrieve correct information even if called with sudo * removed obsolete v2.x workflow files --- .github/workflows/dockerimage.yml | 21 --------------- .github/workflows/pythonpackage.yml | 40 ----------------------------- installation/install-jukebox.sh | 33 +++++++++++++++++++++++- 3 files changed, 32 insertions(+), 62 deletions(-) delete mode 100644 .github/workflows/dockerimage.yml delete mode 100644 .github/workflows/pythonpackage.yml diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml deleted file mode 100644 index 20b0a4ba3..000000000 --- a/.github/workflows/dockerimage.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Test Install Scripts on Docker - -on: - push: - branches-ignore: - - 'future3/**' - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Build Docker image and run tests - run: | - docker build . --file ./ci/Dockerfile.buster.test_install.amd64 --tag rpi-jukebox-rfid-buster:latest - docker run --rm -i rpi-jukebox-rfid-buster:latest /code/scripts/installscripts/tests/run_installation_tests.sh - docker run --rm -i rpi-jukebox-rfid-buster:latest /code/scripts/installscripts/tests/run_installation_tests2.sh - docker run --rm -i rpi-jukebox-rfid-buster:latest /code/scripts/installscripts/tests/run_installation_tests3.sh diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml deleted file mode 100644 index 262748e99..000000000 --- a/.github/workflows/pythonpackage.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Python Checks and Tests - -on: - push: - branches-ignore: - - 'future3/**' - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - python-version: [3.6, 3.7, 3.8] - - steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install wheel - pip install spidev - pip install -r requirements.txt - - name: Lint with flake8 - run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --filename=*.py,*.py.* - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --filename=*.py,*.py.* - - name: Test with pytest - working-directory: ./components/gpio_control - run: | - pip install -r requirements.txt - pytest diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 47a507309..57c81fb7d 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -17,10 +17,37 @@ GIT_BRANCH=${GIT_BRANCH:-"future3/main"} # Constants GIT_REPO_NAME="RPi-Jukebox-RFID" GIT_URL="https://github.com/${GIT_USER}/${GIT_REPO_NAME}" -HOME_PATH="/home/pi" +echo GIT_BRANCH $GIT_BRANCH +echo GIT_URL $GIT_URL + +CURRENT_USER="${SUDO_USER:-$USER}" +HOME_PATH=$(getent passwd "$CURRENT_USER" | cut -d: -f6) +echo "Current User: $CURRENT_USER" +echo "User home dir: $HOME_PATH" + INSTALLATION_PATH="${HOME_PATH}/${GIT_REPO_NAME}" INSTALL_ID=$(date +%s) +checkPrerequisite() { + #currently the user 'pi' is mandatory + #https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1785 + if [ "${CURRENT_USER}" != "pi" ]; then + echo + echo "ERROR: User must be 'pi'!" + echo " Other usernames are currently not supported." + echo " Please check the wiki for further information" + exit 2 + fi + + if [ "${HOME_PATH}" != "/home/pi" ]; then + echo + echo "ERROR: HomeDir must be '/home/pi'!" + echo " Other usernames are currently not supported." + echo " Please check the wiki for further information" + exit 2 + fi +} + download_jukebox_source() { wget -qO- "${GIT_URL}/tarball/${GIT_BRANCH}" | tar xz # Use case insensitive search/sed because user names in Git Hub are case insensitive @@ -41,6 +68,10 @@ download_jukebox_source() { unset GIT_REPO_DOWNLOAD } + +### CHECK PREREQUISITE +checkPrerequisite + ### RUN INSTALLATION INSTALLATION_LOGFILE="${HOME_PATH}/INSTALL-${INSTALL_ID}.log" exec 3>&1 1>>"${INSTALLATION_LOGFILE}" 2>&1 || { echo "Cannot create log file. Panic."; exit 1; } From 6f1efb81c979f33e89ab69c97fe88853d3197016 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Tue, 10 Oct 2023 21:21:59 +0200 Subject: [PATCH 004/121] fix unavailable repo for pi-rc522 (#2075) * fix unavailable repo for pi-rc522. Use fix version to ensure compatibility * build trigger * REVERT build trigger * fixed sphinx gpiozero version for pyton < 3.8 * fixed sphinx gpiozero version for pyton >= 3.8 --- docs/sphinx/requirements.txt | 3 ++- .../components/rfid/hardware/rc522_spi/requirements.txt | 9 +-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt index f6b511362..e169c2c69 100644 --- a/docs/sphinx/requirements.txt +++ b/docs/sphinx/requirements.txt @@ -4,4 +4,5 @@ readthedocs-sphinx-search # Install packages for documentation build from package repository # that are installed on the PI via APT RPi.GPIO -gpiozero +gpiozero<2.0.0;python_version<'3.8' +gpiozero;python_version>='3.8' diff --git a/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt b/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt index fce2291e4..78a3b459a 100644 --- a/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt +++ b/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt @@ -1,13 +1,6 @@ # RC522 related requirements # You need to install these with `sudo python3 -m pip install --upgrade --force-reinstall -q -r requirements.txt` -# pi-rc522 use latest version from Github -# This is the original versions. Seems unmaintained for the moment -# git+https://github.com/ondryaso/pi-rc522.git#egg=pi-rc522 - -# The fork of kevinvalk has some good improvements -# https://github.com/kevinvalk/pi-rc522 -# Get the kevinvalk fork yet again from a different fork which ensures compatibility with the Phoniebox -git+https://github.com/ChisSoc/pi-rc522.git#egg=pi-rc522 +pi-rc522==2.3.0 From 5e35ce86f05c377cf935d432ac00b8138503acaa Mon Sep 17 00:00:00 2001 From: notapirate Date: Fri, 3 Nov 2023 14:33:56 +0100 Subject: [PATCH 005/121] docker: optimizations (#2074) - linux host: use pulse unix socket - mpd: run as user (pi / root) - mpd: remove port exposure to host, connections to mpd only come from other docker containers - less config adjustments for docker environment Co-authored-by: Christoph Lauer --- .dockerignore | 3 +- docker/armv7/jukebox.Dockerfile | 41 +++++++-------- docker/config/docker.pulse.mpd.conf | 4 +- docker/config/jukebox.overrides.yaml | 5 -- docker/docker-compose.linux.yml | 39 ++++++++++---- docker/docker-compose.mac.yml | 5 -- docker/docker-compose.windows.yml | 12 ----- docker/docker-compose.yml | 32 +++++++----- docker/jukebox.Dockerfile | 22 ++++---- docker/mpd.Dockerfile | 21 ++++---- docs/sphinx/developer/docker.rst | 66 ++++++++---------------- src/jukebox/jukebox/playlistgenerator.py | 2 +- 12 files changed, 115 insertions(+), 137 deletions(-) delete mode 100755 docker/docker-compose.windows.yml diff --git a/.dockerignore b/.dockerignore index fed16790c..55fe62a70 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,6 @@ .git +.githooks +.github .dockerignore .DS_Store @@ -6,7 +8,6 @@ docker docs installation -src shared # webapp diff --git a/docker/armv7/jukebox.Dockerfile b/docker/armv7/jukebox.Dockerfile index b2ca11944..b4f9168e2 100644 --- a/docker/armv7/jukebox.Dockerfile +++ b/docker/armv7/jukebox.Dockerfile @@ -5,22 +5,19 @@ FROM arm32v7/debian:buster-slim # These are only dependencies that are required to get as close to the # Raspberry Pi environment as possible. RUN apt-get update && apt-get install -y \ - alsa-utils \ libasound2-dev \ - libasound2-plugins \ pulseaudio \ pulseaudio-utils \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* -RUN usermod -aG audio,pulse,pulse-access root +ARG UID +ARG USER +ARG HOME +ENV INSTALLATION_PATH ${HOME}/RPi-Jukebox-RFID -ENV HOME /root -ENV MPD_HOST mpd -ENV INSTALLATION_DIR /home/pi/RPi-Jukebox-RFID -ENV DOCKER_DIR ${INSTALLATION_DIR}/docker - -WORKDIR $INSTALLATION_DIR +RUN test ${UID} -gt 0 && useradd -m -u ${UID} ${USER} || continue +RUN usermod -aG pulse ${USER} # Jukebox # Install all Jukebox dependencies @@ -34,13 +31,12 @@ RUN apt-get update && apt-get install -qq -y \ #resolvconf #python3-spidev -COPY . ${INSTALLATION_DIR} - # Install Jukebox # Install libzmq with Websocket support from pre-compiled source -ENV ZMQ_TMP_DIR libzmq -ENV ZMQ_PREFIX /usr/local -RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ +ENV ZMQ_TMP_DIR "/root/libzmq" +ENV ZMQ_PREFIX "/usr/local" +ENV ZMQ_DRAFT_API 1 +RUN mkdir -p ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ wget --quiet --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=1KP6BqLF-i2dCUsHhOUpOwwuOmKsB5GKY' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=1KP6BqLF-i2dCUsHhOUpOwwuOmKsB5GKY" -O libzmq.tar.gz && rm -rf /tmp/cookies.txt; \ tar -xzf libzmq.tar.gz; \ rm -f libzmq.tar.gz; \ @@ -49,26 +45,29 @@ RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ # Install libzmq with Websocket and compile # ENV LIBSODIUM_VERSION 1.0.18 # ENV ZMQ_VERSION 4.3.4 -# RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ +# RUN mkdir -p ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ # wget --quiet https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VERSION}-RELEASE/libsodium-${LIBSODIUM_VERSION}.tar.gz; \ # tar -zxvf libsodium-${LIBSODIUM_VERSION}.tar.gz; \ # cd libsodium-${LIBSODIUM_VERSION}/; \ # ./configure; \ # make && make install -# RUN cd ${HOME}/${ZMQ_TMP_DIR}; \ +# RUN cd ${ZMQ_TMP_DIR}; \ # wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ # tar -xzf libzmq.tar.gz; \ # zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts; \ # make && make install; -RUN pip3 install --pre pyzmq \ - --install-option=--enable-drafts \ - --install-option=--zmq=${ZMQ_PREFIX} +USER ${USER} +WORKDIR ${HOME} +COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ -RUN pip3 install --no-cache-dir -r ${INSTALLATION_DIR}/requirements.txt +RUN pip3 install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt +RUN pip3 install --no-cache-dir --pre --no-binary pyzmq pyzmq EXPOSE 5555 5556 +WORKDIR ${INSTALLATION_PATH}/src/jukebox + # Run Jukebox # CMD bash -CMD python3 ${INSTALLATION_DIR}/src/jukebox/run_jukebox.py +CMD python3 ${INSTALLATION_PATH}/src/jukebox/run_jukebox.py diff --git a/docker/config/docker.pulse.mpd.conf b/docker/config/docker.pulse.mpd.conf index 96f27347e..ad7713d0d 100644 --- a/docker/config/docker.pulse.mpd.conf +++ b/docker/config/docker.pulse.mpd.conf @@ -11,7 +11,7 @@ # be disabled and audio files will only be accepted over ipc socket (using # file:// protocol) or streaming files over an accepted protocol. # -music_directory "/home/pi/RPi-Jukebox-RFID/shared/audiofolders" +music_directory "~/RPi-Jukebox-RFID/shared/audiofolders" # # This setting sets the MPD internal playlist directory. The purpose of this # directory is storage for playlists created by MPD. The server will use @@ -67,7 +67,7 @@ sticker_file "~/.config/mpd/sticker.sql" # initialization. This setting is disabled by default and MPD is run as the # current user. # -user "root" +# user "root" # # This setting specifies the group that MPD will run as. If not specified # primary group of user specified with "user" setting will be used (if set). diff --git a/docker/config/jukebox.overrides.yaml b/docker/config/jukebox.overrides.yaml index 0bd6fad07..ca79486d4 100644 --- a/docker/config/jukebox.overrides.yaml +++ b/docker/config/jukebox.overrides.yaml @@ -1,7 +1,2 @@ playermpd: host: mpd - mpd_conf: /etc/mpd.conf -pulse: - outputs: - primary: - pulse_sink_name: Channel_1__Channel_2.2 \ No newline at end of file diff --git a/docker/docker-compose.linux.yml b/docker/docker-compose.linux.yml index 2bc9ab812..9609dc18d 100755 --- a/docker/docker-compose.linux.yml +++ b/docker/docker-compose.linux.yml @@ -1,10 +1,29 @@ -version: "3.9" - -services: - mpd: - devices: - - /dev/snd - - jukebox: - devices: - - /dev/snd +version: "3.9" + +services: + mpd: + build: + args: + - UID=${UID:-1000} + - USER=pi + - HOME=/home/pi + environment: + - PULSE_SERVER=unix:/tmp/pulse-sock + volumes: + - ../shared/audiofolders:/home/pi/RPi-Jukebox-RFID/shared/audiofolders + - ../shared/playlists:/home/pi/.config/mpd/playlists + - ./config/docker.pulse.mpd.conf:/home/pi/.config/mpd/mpd.conf + - $XDG_RUNTIME_DIR/pulse/native:/tmp/pulse-sock + + jukebox: + build: + args: + - UID=${UID:-1000} + - USER=pi + - HOME=/home/pi + environment: + - PULSE_SERVER=unix:/tmp/pulse-sock + volumes: + - ../shared:/home/pi/RPi-Jukebox-RFID/shared + - ./config/docker.pulse.mpd.conf:/home/pi/.config/mpd/mpd.conf + - $XDG_RUNTIME_DIR/pulse/native:/tmp/pulse-sock diff --git a/docker/docker-compose.mac.yml b/docker/docker-compose.mac.yml index 27163cb93..d20d7ed6e 100644 --- a/docker/docker-compose.mac.yml +++ b/docker/docker-compose.mac.yml @@ -2,16 +2,11 @@ version: "3.9" services: mpd: - environment: - - PULSE_SERVER=tcp:host.docker.internal:4713 volumes: - - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf:rw - ~/.config/pulse:/root/.config/pulse - /usr/local/Cellar/pulseaudio/14.2/etc/pulse:/etc/pulse jukebox: - environment: - - PULSE_SERVER=tcp:host.docker.internal:4713 volumes: - ~/.config/pulse:/root/.config/pulse - /usr/local/Cellar/pulseaudio/14.2/etc/pulse:/etc/pulse diff --git a/docker/docker-compose.windows.yml b/docker/docker-compose.windows.yml deleted file mode 100755 index 51134451d..000000000 --- a/docker/docker-compose.windows.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: "3.9" - -services: - mpd: - environment: - - PULSE_SERVER=tcp:host.docker.internal:4713 - volumes: - - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf:rw - - jukebox: - environment: - - PULSE_SERVER=tcp:host.docker.internal:4713 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 73c462c31..68bb5c351 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -3,27 +3,34 @@ version: "3.9" services: mpd: build: + args: + - UID=0 + - USER=root + - HOME=/root context: ../ dockerfile: ./docker/mpd.Dockerfile container_name: mpd - ports: - - 6600:6600 - - 8800:8800 + environment: + - PULSE_SERVER=tcp:host.docker.internal:4713 restart: unless-stopped volumes: - - ../shared/audiofolders:/home/pi/RPi-Jukebox-RFID/shared/audiofolders:rw - - ../shared/playlists:/root/.config/mpd/playlists:rw - - ./config/docker.mpd.conf:/root/.config/mpd/mpd.conf:rw + - ../shared/audiofolders:/root/RPi-Jukebox-RFID/shared/audiofolders + - ../shared/playlists:/root/.config/mpd/playlists + - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf jukebox: build: + args: + - UID=0 + - USER=root + - HOME=/root context: ../ dockerfile: ./docker/jukebox.Dockerfile container_name: jukebox depends_on: - mpd - links: - - mpd + environment: + - PULSE_SERVER=tcp:host.docker.internal:4713 ports: - 5555:5555 - 5556:5556 @@ -31,9 +38,8 @@ services: restart: unless-stopped tty: true volumes: - - ../src/jukebox:/home/pi/RPi-Jukebox-RFID/src/jukebox - - ../shared:/home/pi/RPi-Jukebox-RFID/shared - - ./config/docker.mpd.conf:/etc/mpd.conf + - ../shared:/root/RPi-Jukebox-RFID/shared + - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf webapp: build: @@ -44,10 +50,8 @@ services: - jukebox environment: - CHOKIDAR_USEPOLLING=true - links: - - jukebox ports: - - 3001:3000 + - 3000:3000 restart: unless-stopped volumes: - ../src/webapp:/home/node/webapp diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index f1ac07141..e8ce1cb64 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -5,36 +5,38 @@ FROM debian:bullseye-slim # These are only dependencies that are required to get as close to the # Raspberry Pi environment as possible. RUN apt-get update && apt-get install -y \ - alsa-utils \ libasound2-dev \ - libasound2-plugins \ pulseaudio \ pulseaudio-utils \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* -RUN usermod -aG audio,pulse,pulse-access root +ARG UID +ARG USER +ARG HOME +ENV INSTALLATION_PATH ${HOME}/RPi-Jukebox-RFID -ENV HOME /root -ENV INSTALLATION_PATH /home/pi/RPi-Jukebox-RFID - -WORKDIR $INSTALLATION_PATH +RUN test ${UID} -gt 0 && useradd -m -u ${UID} ${USER} || continue +RUN usermod -aG pulse ${USER} # Jukebox # Install all Jukebox dependencies RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ gcc at wget \ - espeak mpc mpg123 git ffmpeg spi-tools netcat alsa-tools \ + espeak mpc mpg123 git ffmpeg spi-tools netcat \ python3 python3-dev python3-pip python3-mutagen python3-gpiozero -COPY . ${INSTALLATION_PATH} +USER ${USER} +WORKDIR ${HOME} +COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ RUN pip3 install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt RUN pip3 install pyzmq EXPOSE 5555 5556 +WORKDIR ${INSTALLATION_PATH}/src/jukebox + # Run Jukebox -# CMD bash CMD python3 ${INSTALLATION_PATH}/src/jukebox/run_jukebox.py diff --git a/docker/mpd.Dockerfile b/docker/mpd.Dockerfile index f77b65dc3..e13d7f472 100644 --- a/docker/mpd.Dockerfile +++ b/docker/mpd.Dockerfile @@ -2,9 +2,6 @@ FROM debian:bullseye-slim RUN set -eux ; \ apt-get update && apt-get install -y \ - alsa-utils \ - libasound2-dev \ - libasound2-plugins \ pulseaudio \ pulseaudio-utils \ mpd mpc \ @@ -12,16 +9,16 @@ RUN set -eux ; \ ; \ rm -rf /var/lib/apt/lists/* -ENV HOME /root -RUN mkdir ${HOME}/.config ${HOME}/.config/mpd ; \ - touch ${HOME}/.config/mpd/state -RUN mkdir -p /home/pi/RPi-Jukebox-RFID/shared/audiofolders - -RUN usermod -aG audio,pulse,pulse-access root +ARG UID +ARG USER +ARG HOME -VOLUME ${HOME}/.config/mpd +RUN useradd -m -u ${UID} ${USER} || continue +RUN usermod -aG pulse ${USER} -EXPOSE 6600 +USER ${USER} +RUN mkdir -p ${HOME}/.config/mpd ; \ + touch ${HOME}/.config/mpd/state -CMD [ ! -s ~/.config/mpd/pid ] && mpd --stdout --no-daemon ${HOME}/.config/mpd/mpd.conf +CMD mpd --stdout --no-daemon ${HOME}/.config/mpd/mpd.conf diff --git a/docs/sphinx/developer/docker.rst b/docs/sphinx/developer/docker.rst index 3a2150a21..f3de61902 100644 --- a/docs/sphinx/developer/docker.rst +++ b/docs/sphinx/developer/docker.rst @@ -47,7 +47,6 @@ Prerequisites `Override file `_ in your ``jukebox.yaml``. - * **[Currently required]** Update all relative paths (``../..``) in to ``/home/pi/RPi-Jukebox-RFID``. 4. Change directory into the ``./RPi-Jukebox-RFID/shared/audiofolders`` and copy a set of MP3 files into this folder (for more fun when testing). @@ -56,47 +55,29 @@ Run development environment In contrary to how everything is set up on the Raspberry Pi, it's good practice to isolate different components in different Docker images. They can be run individually or in combination. -To do that, we use ``docker-compose``. +To do that, we use ``docker compose``. Linux ^^^^^^^ -Make sure you don't use ``sudo`` to run your ``docker-compose``. Check out Docker's `post-installation guide ` +Make sure you don't use ``sudo`` to run your ``docker compose``. Check out Docker's `post-installation guide ` for more information. .. code-block:: bash // Build Images - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build // Run Docker Environment - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up // Shuts down Docker containers and Docker network - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down -Note: if you have ``mpd`` running on your system, you need to stop it using: - -.. code-block:: bash - - $ sudo systemctl stop mpd.socket - $ sudo mpd --kill - - -Otherwise you might get the error message: - -.. code-block:: bash - - $ docker-compose -f docker-compose.yml -f docker-compose.linux.yml up - Starting mpd ... - Starting mpd ... error - (...) - Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already in use - -Read these threads for details: `thread 1 `_ -and `thread 2 `_ - +The docker linux setup tries to mirror the current users pulseaudio socket from ``$XDG_RUNTIME_DIR/pulse/native`` to the containers ``mpd`` and ``jukebox``. +If your pulseaudio socket is in another place, adjust path in docker/docker-compose.linux.yml. You can find out the socket path with the command ``pactl info``. +To access the socket, the user inside the docker containers also must have the same UID as the user running docker. You can find out your UID by running ``id -u``. If your UID deviates from the default UID 1000, please run ``export UID=$(id -u)`` prior to any docker commands. Mac ^^^^^ @@ -107,13 +88,13 @@ for Mac hosts. .. code-block:: bash // Build Images - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build // Run Docker Environment - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml up + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml up // Shuts down Docker containers and Docker network - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml down + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml down Windows ^^^^^^^^^^^ @@ -139,30 +120,31 @@ Windows .. code-block:: bash // Build Images - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml build + $ docker compose -f docker/docker-compose.yml build // Run Docker Environment - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml up + $ docker compose -f docker/docker-compose.yml up // Shuts down Docker containers and Docker network - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml down + $ docker compose -f docker/docker-compose.yml down Test & Develop --------------------- The Dockerfile is defined to start all Phoniebox related services. -Open `http://localhost:3001 `_ in your browser to see the web application. +Windows: Open `host.docker.internal:3000 `_ in your browser to see the web application. +Linux / Mac: Open `http://localhost:3000 `_ in your browser to see the web application. While the ``webapp`` container does not require a reload while working on it (hot-reload is enabled), you will have to restart your ``jukebox`` container whenever you make a change (in the Python code). -Instead of stopping and starting the ``docker-compose`` command, you can individually restart your +Instead of stopping and starting the ``docker compose`` command, you can individually restart your ``jukebox`` container. Update the below path with your specific host environment. .. code-block:: bash - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.[ENVIRONMENT].yml restart jukebox + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.[ENVIRONMENT].yml restart jukebox Known issues ---------------- @@ -209,6 +191,8 @@ Typical errors and following exceptions to be ignored in the Docker ``jukebox`` .. code-block:: bash + jukebox | 277:utils.py - jb.utils - MainThread - ERROR - CalledProcessError: Command 'git log --pretty='%h [%cs] %s %d' -n 1 --no-color' returned non-zero exit status 128. + jukebox | 287:utils.py - jb.utils - MainThread - ERROR - CalledProcessError: Command 'git describe --tag --dirty='-dirty'' returned non-zero exit status 128. jukebox | 634:plugs.py - jb.plugin - MainThread - ERROR - Ignoring failed package load finalizer: 'rfid.finalize()' jukebox | 635:plugs.py - jb.plugin - MainThread - ERROR - Reason: FileNotFoundError: [Errno 2] No such file or directory: '/home/pi/RPi-Jukebox-RFID/shared/settings/rfid.yaml' ... @@ -230,14 +214,8 @@ The following command can be run on a Mac. .. code-block:: bash - $ docker build -f docker/jukebox.Dockerfile -t jukebox . - $ docker run -it --rm \ - -v $(PWD)/src/jukebox:/home/pi/RPi-Jukebox-RFID/src/jukebox \ - -v $(PWD)/shared/audiofolders:/home/pi/RPi-Jukebox-RFID/shared/audiofolders \ - -v ~/.config/pulse:/root/.config/pulse \ - -v /usr/local/Cellar/pulseaudio/14.2/etc/pulse/:/etc/pulse \ - -e PULSE_SERVER=tcp:host.docker.internal:4713 \ - --name jukebox jukebox + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build jukebox + $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml run --rm --name jukebox jukebox Resources ^^^^^^^^^^^ diff --git a/src/jukebox/jukebox/playlistgenerator.py b/src/jukebox/jukebox/playlistgenerator.py index b1e12b0de..13dcc0f7d 100755 --- a/src/jukebox/jukebox/playlistgenerator.py +++ b/src/jukebox/jukebox/playlistgenerator.py @@ -191,7 +191,7 @@ def __init__(self, music_library_base_path='/'): but is omitted when generating the playlist entries. I.e. all files in the playlist are relative to this base dir """ self.playlist = [] - self._music_library_base_path = os.path.abspath(music_library_base_path) + self._music_library_base_path = os.path.abspath(os.path.expanduser(music_library_base_path)) # These two variables only store reference content to generate __str__ self._folder = '' self._recursive = False From 373fd38538a63fb7cc00820c74dc782ce3cc832e Mon Sep 17 00:00:00 2001 From: Christian Tietze Date: Wed, 8 Nov 2023 17:00:48 +0100 Subject: [PATCH 006/121] Fix pyzmq installation from source with drafts support (#2096) * Fix pyzmq installation from source with drafts support * Update pyzmq docs link --- installation/routines/setup_jukebox_core.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 88b65bcc0..6f773f129 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -74,7 +74,7 @@ _jukebox_core_build_and_install_pyzmq() { # we need to compile the latest version in Github # As soon WebSockets support is stable in ZMQ, this can be removed # Sources: - # https://pyzmq.readthedocs.io/en/latest/draft.html + # https://pyzmq.readthedocs.io/en/latest/howto/draft.html # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md echo " Build and install pyzmq with WebSockets Support" @@ -98,8 +98,8 @@ _jukebox_core_build_and_install_pyzmq() { _jukebox_core_download_prebuild_libzmq_with_drafts fi - sudo ZMQ_PREFIX=${ZMQ_PREFIX} ZMQ_DRAFT_API=1 \ - pip3 install --no-cache-dir --pre pyzmq + sudo ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ + pip3 install --no-cache-dir --no-binary "pyzmq" --pre pyzmq else echo " Skipping. pyzmq already installed" fi From 0660586f7ec96aea75006cc8df8b86e1d3d11542 Mon Sep 17 00:00:00 2001 From: Christian Tietze Date: Wed, 8 Nov 2023 17:01:23 +0100 Subject: [PATCH 007/121] Remove unused _jukebox_core_download_prebuilt_pyzmq (#2097) See report #2094 --- installation/routines/setup_jukebox_core.sh | 22 ++------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 6f773f129..45eb1610c 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -59,7 +59,7 @@ _jukebox_core_build_libzmq_with_drafts() { make && make install } -_jukebox_core_download_prebuild_libzmq_with_drafts() { +_jukebox_core_download_prebuilt_libzmq_with_drafts() { local ZMQ_TAR_FILENAME="libzmq.tar.gz" _download_file_from_google_drive "${LIBZMQ_GD_DOWNLOAD_ID}" "${ZMQ_TAR_FILENAME}" @@ -95,7 +95,7 @@ _jukebox_core_build_and_install_pyzmq() { if [ "$BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE" = true ] ; then _jukebox_core_build_libzmq_with_drafts else - _jukebox_core_download_prebuild_libzmq_with_drafts + _jukebox_core_download_prebuilt_libzmq_with_drafts fi sudo ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ @@ -105,24 +105,6 @@ _jukebox_core_build_and_install_pyzmq() { fi } -_jukebox_core_download_prebuilt_pyzmq() { - echo " Download prebuilt pyzmq with WebSockets Support" - local PYZMQ_TAR_FILENAME="pyzmq-build-armv6.tar.gz" - - cd "${HOME_PATH}" || exit_on_error - - # ARMv7 as default - PYZMQ_GD_DOWNLOAD_ID=${GD_ID_COMPILED_PYZMQ_ARMV7} - if [[ $(uname -m) == "armv6l" ]]; then - # ARMv6 as fallback - PYZMQ_GD_DOWNLOAD_ID=${GD_ID_COMPILED_PYZMQ_ARMV6} - fi - - _download_file_from_google_drive "${PYZMQ_GD_DOWNLOAD_ID}" "${PYZMQ_TAR_FILENAME}" - tar -xvf "${PYZMQ_TAR_FILENAME}" -C / - rm -f "${PYZMQ_TAR_FILENAME}" -} - _jukebox_core_install_python_requirements() { echo " Install requirements" cd "${INSTALLATION_PATH}" || exit_on_error From 1ca5a81afcb08cdf6f4c3afb7b21b791b2e53ea9 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:26:19 +0100 Subject: [PATCH 008/121] future3 - Move docs back to Github & markdown (#2085) * Start moving docs to markdown * update userguide * add more pages * Rename index.md * Undo last commit * Add autohotspot * Finish userguide without references * Rename bt audio btns * Adding developers and rfid * docker: optimizations (#2074) - linux host: use pulse unix socket - mpd: run as user (pi / root) - mpd: remove port exposure to host, connections to mpd only come from other docker containers - less config adjustments for docker environment Co-authored-by: Christoph Lauer * Start moving docs to markdown * update userguide * add more pages * Rename index.md * Undo last commit * Add autohotspot * Finish userguide without references * Rename bt audio btns * Adding developers and rfid * Rename docs to documentation * Update document structure * Move rfid to developers * Remove sphinx * Remove even more sphinx * Test pydoc * Revert "Test pydoc" This reverts commit e1c6aeb25c690e180b6c2c17c3e055e86af9ad1d. Revert "Remove even more sphinx" This reverts commit 6f5559717740760919d67c245cba359d8fd49ae6. Revert "Remove sphinx" This reverts commit 23abee327db39dc77141a0e2c19677cc05097319. * Moving sphinx for api and command reference * Remove Sphinx for good * update paths to developers --------- Co-authored-by: notapirate Co-authored-by: Christoph Lauer --- .flake8 | 2 - .githooks/pre-commit | 13 +- .github/workflows/pythonpackage_future3.yml | 11 - .gitignore | 3 - .readthedocs.yaml | 28 - CONTRIBUTING.md | 15 - README.md | 19 +- docker/docker-compose.yml | 4 + docker/jukebox.Dockerfile | 3 - docker/webapp.Dockerfile | 2 +- docs/sphinx/_templates/footer.html | 17 - docs/sphinx/api/api.rst | 20 - docs/sphinx/api/callingback.rst | 9 - docs/sphinx/api/cfghandler.rst | 5 - docs/sphinx/api/controls.rst | 19 - docs/sphinx/api/gpioz/connector.rst | 45 - docs/sphinx/api/gpioz/converter.rst | 13 - docs/sphinx/api/gpioz/gpioz.rst | 17 - docs/sphinx/api/gpioz/input_devices.rst | 42 - docs/sphinx/api/gpioz/output_devices.rst | 44 - docs/sphinx/api/playlist.rst | 11 - docs/sphinx/api/plugs.rst | 6 - docs/sphinx/api/publishing.rst | 12 - docs/sphinx/api/rpc_server.rst | 7 - docs/sphinx/api/utils.rst | 6 - docs/sphinx/api/volume.rst | 17 - docs/sphinx/conf.py | 67 -- docs/sphinx/developer/coreapps.rst | 61 -- docs/sphinx/developer/developer_issues.rst | 80 -- .../developer/development_environment.rst | 73 -- docs/sphinx/developer/docker.rst | 250 ----- docs/sphinx/featurelist.rst | 313 ------ docs/sphinx/index.rst | 57 - docs/sphinx/install.rst | 98 -- docs/sphinx/issues.rst | 15 - docs/sphinx/migration.rst | 55 - docs/sphinx/requirements.txt | 8 - docs/sphinx/requirements_pyzmq.txt | 5 - docs/sphinx/rfid/basics.rst | 91 -- docs/sphinx/rfid/genericusb.rst | 1 - docs/sphinx/rfid/mfrc522_spi.rst | 1 - docs/sphinx/rfid/mock_reader.rst | 1 - docs/sphinx/rfid/pn532_i2c.rst | 1 - docs/sphinx/rfid/rdm6300.rst | 1 - docs/sphinx/rfid/rfid.rst | 14 - docs/sphinx/rfid/template.rst | 1 - docs/sphinx/userguide/audio.rst | 152 --- docs/sphinx/userguide/autohotspot.rst | 112 -- .../userguide/bluetooth_audio_buttons.rst | 4 - docs/sphinx/userguide/carddatabase.rst | 94 -- docs/sphinx/userguide/concepts.rst | 46 - docs/sphinx/userguide/configuration.rst | 43 - docs/sphinx/userguide/gpioz.rst | 1 - .../userguide/rpc_command_alias_reference.rst | 188 ---- .../userguide/rpc_command_reference.rst | 994 ------------------ docs/sphinx/userguide/rpc_commands.rst | 116 -- docs/sphinx/userguide/sync_rfidcards.rst | 71 -- docs/sphinx/userguide/system.rst | 121 --- docs/sphinx/userguide/troubleshooting.rst | 107 -- .../primer.rst => documentation/README.md | 32 +- .../calendars/2019-Phoniebox-Calendar.jpg | Bin .../calendars/2020-Phoniebox-Calendar.jpg | Bin .../calendars/2021-Phoniebox-Calendar.jpg | Bin documentation/content/developers/coreapps.md | 56 + .../content/developers/developer-issues.md | 73 ++ .../developers/development-environment.md | 74 ++ documentation/content/developers/docker.md | 265 +++++ .../content/developers/known-issues.md | 13 + .../content/developers/rfid/README.md | 12 + .../content/developers/rfid/basics.md | 93 ++ .../content/developers/rfid/genericusb.md | 14 + .../content/developers/rfid/mfrc522_spi.md | 81 ++ .../content/developers/rfid/mock_reader.md | 26 + .../content/developers}/rfid/mock_reader.png | Bin .../content/developers/rfid/pn532_i2c.md | 53 + .../content/developers/rfid/rdm6300.md | 19 + .../developers/rfid/template_reader.md | 41 + .../developers/to-be-deleted-status.md | 244 +++++ documentation/content/userguide/audio.md | 141 +++ .../content/userguide/autohotspot.md | 107 ++ .../userguide/bluetooth-audio-buttons.md | 63 ++ .../content/userguide/card-database.md | 101 ++ documentation/content/userguide/concepts.md | 27 + .../content/userguide/configuration.md | 40 + .../content/userguide/installation.md | 93 ++ .../content}/userguide/mock_gpio.png | Bin documentation/content/userguide/rfid.md | 80 ++ documentation/content/userguide/system.md | 112 ++ .../content/userguide/troubleshooting.md | 97 ++ documentation/content/userguide/update.md | 39 + installation/routines/customize_options.sh | 24 - installation/routines/setup_jukebox_webapp.sh | 9 - requirements.txt | 3 - resources/default-settings/nginx.default | 37 +- resources/html/runsphinx.html | 24 - run_sphinx.sh | 45 - .../bluetooth_audio_buttons/README.rst | 2 +- src/jukebox/components/gpio/gpioz/README.rst | 2 - .../hardware/template_new_reader/README.rst | 3 +- src/jukebox/jukebox/daemon.py | 11 - 100 files changed, 1998 insertions(+), 3790 deletions(-) delete mode 100644 .readthedocs.yaml delete mode 100644 docs/sphinx/_templates/footer.html delete mode 100644 docs/sphinx/api/api.rst delete mode 100644 docs/sphinx/api/callingback.rst delete mode 100644 docs/sphinx/api/cfghandler.rst delete mode 100644 docs/sphinx/api/controls.rst delete mode 100644 docs/sphinx/api/gpioz/connector.rst delete mode 100644 docs/sphinx/api/gpioz/converter.rst delete mode 100644 docs/sphinx/api/gpioz/gpioz.rst delete mode 100644 docs/sphinx/api/gpioz/input_devices.rst delete mode 100644 docs/sphinx/api/gpioz/output_devices.rst delete mode 100644 docs/sphinx/api/playlist.rst delete mode 100644 docs/sphinx/api/plugs.rst delete mode 100644 docs/sphinx/api/publishing.rst delete mode 100644 docs/sphinx/api/rpc_server.rst delete mode 100644 docs/sphinx/api/utils.rst delete mode 100644 docs/sphinx/api/volume.rst delete mode 100644 docs/sphinx/conf.py delete mode 100644 docs/sphinx/developer/coreapps.rst delete mode 100644 docs/sphinx/developer/developer_issues.rst delete mode 100644 docs/sphinx/developer/development_environment.rst delete mode 100644 docs/sphinx/developer/docker.rst delete mode 100644 docs/sphinx/featurelist.rst delete mode 100644 docs/sphinx/index.rst delete mode 100644 docs/sphinx/install.rst delete mode 100644 docs/sphinx/issues.rst delete mode 100644 docs/sphinx/migration.rst delete mode 100644 docs/sphinx/requirements.txt delete mode 100644 docs/sphinx/requirements_pyzmq.txt delete mode 100644 docs/sphinx/rfid/basics.rst delete mode 100644 docs/sphinx/rfid/genericusb.rst delete mode 100644 docs/sphinx/rfid/mfrc522_spi.rst delete mode 100644 docs/sphinx/rfid/mock_reader.rst delete mode 100644 docs/sphinx/rfid/pn532_i2c.rst delete mode 100644 docs/sphinx/rfid/rdm6300.rst delete mode 100644 docs/sphinx/rfid/rfid.rst delete mode 100644 docs/sphinx/rfid/template.rst delete mode 100644 docs/sphinx/userguide/audio.rst delete mode 100644 docs/sphinx/userguide/autohotspot.rst delete mode 100644 docs/sphinx/userguide/bluetooth_audio_buttons.rst delete mode 100644 docs/sphinx/userguide/carddatabase.rst delete mode 100644 docs/sphinx/userguide/concepts.rst delete mode 100644 docs/sphinx/userguide/configuration.rst delete mode 100644 docs/sphinx/userguide/gpioz.rst delete mode 100644 docs/sphinx/userguide/rpc_command_alias_reference.rst delete mode 100644 docs/sphinx/userguide/rpc_command_reference.rst delete mode 100644 docs/sphinx/userguide/rpc_commands.rst delete mode 100644 docs/sphinx/userguide/sync_rfidcards.rst delete mode 100644 docs/sphinx/userguide/system.rst delete mode 100644 docs/sphinx/userguide/troubleshooting.rst rename docs/sphinx/primer.rst => documentation/README.md (62%) rename {docs => documentation/content}/calendars/2019-Phoniebox-Calendar.jpg (100%) rename {docs => documentation/content}/calendars/2020-Phoniebox-Calendar.jpg (100%) rename {docs => documentation/content}/calendars/2021-Phoniebox-Calendar.jpg (100%) create mode 100644 documentation/content/developers/coreapps.md create mode 100644 documentation/content/developers/developer-issues.md create mode 100644 documentation/content/developers/development-environment.md create mode 100644 documentation/content/developers/docker.md create mode 100644 documentation/content/developers/known-issues.md create mode 100644 documentation/content/developers/rfid/README.md create mode 100644 documentation/content/developers/rfid/basics.md create mode 100644 documentation/content/developers/rfid/genericusb.md create mode 100644 documentation/content/developers/rfid/mfrc522_spi.md create mode 100644 documentation/content/developers/rfid/mock_reader.md rename {docs/sphinx => documentation/content/developers}/rfid/mock_reader.png (100%) create mode 100644 documentation/content/developers/rfid/pn532_i2c.md create mode 100644 documentation/content/developers/rfid/rdm6300.md create mode 100644 documentation/content/developers/rfid/template_reader.md create mode 100644 documentation/content/developers/to-be-deleted-status.md create mode 100644 documentation/content/userguide/audio.md create mode 100644 documentation/content/userguide/autohotspot.md create mode 100644 documentation/content/userguide/bluetooth-audio-buttons.md create mode 100644 documentation/content/userguide/card-database.md create mode 100644 documentation/content/userguide/concepts.md create mode 100644 documentation/content/userguide/configuration.md create mode 100644 documentation/content/userguide/installation.md rename {docs/sphinx => documentation/content}/userguide/mock_gpio.png (100%) create mode 100644 documentation/content/userguide/rfid.md create mode 100644 documentation/content/userguide/system.md create mode 100644 documentation/content/userguide/troubleshooting.md create mode 100644 documentation/content/userguide/update.md delete mode 100755 resources/html/runsphinx.html delete mode 100755 run_sphinx.sh diff --git a/.flake8 b/.flake8 index 42dddd56d..d11edbe8a 100644 --- a/.flake8 +++ b/.flake8 @@ -19,8 +19,6 @@ filename = *.py,*.py.* extend-exclude = # Ignore all scratch development directories scratch*, - # The conf file is mostly autogenerated, ignore it - docs/sphinx/conf.py, # Ignore dirs and files that have not been ported yet */jukebox/NvManager.py # ignore GitHub Codespaces diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 026740b23..95901bcda 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -26,18 +26,7 @@ if [[ -n $PY_FILES ]]; then echo "Flake8 return code: $flake8_code" fi -DOC_FILES="$(git diff --diff-filter=d --staged --name-only -- docs/sphinx/*)" -sphinx_code=0 -if [[ -n $DOC_FILES || -n $PY_FILES ]]; then - echo -e "\n**************************************************************" - echo -e "Files for documentation modified. Testing re-build of docs ... \n" - echo -e "**************************************************************\n" - ./run_sphinx.sh -c - sphinx_code=$? - echo "Sphinx return code: $sphinx_code" -fi - -code=$(( flake8_code + sphinx_code )) +code=$(( flake8_code )) if [[ code -gt 0 ]]; then echo -e "\n**************************************************************" diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index 670f373da..757edb6f8 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -7,14 +7,12 @@ on: paths: - '**.py' - '**.py.*' - - 'docs/sphinx/**' pull_request: branches: - 'future3/**' paths: - '**.py' - '**.py.*' - - 'docs/sphinx/**' jobs: build: @@ -40,9 +38,6 @@ jobs: pip3 install spidev pip3 install -r requirements.txt # For operation of the Jukebox, ZMQ must be compiled from sources due to Websocket support - # When just building the docs, the regular ZMQ package is sufficient - pip3 install -r docs/sphinx/requirements_pyzmq.txt - pip3 install -r docs/sphinx/requirements.txt # Also install all optional dependencies pip3 install -r src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt pip3 install -r src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt @@ -53,9 +48,3 @@ jobs: pip3 install flake8 # Stop the build if linting fails ./run_flake8.sh - - name: Build the docs - working-directory: ./docs/sphinx - run: | - # Stop the build if documentation cannot be built - # Treat all warnings as errors - sphinx-build -W --keep-going -T -a -E -b html . _build diff --git a/.gitignore b/.gitignore index 48270aedb..9d38e1b8c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,6 @@ /shared/*.* /shared/* -# Documentation builder -/docs/sphinx/_build/ - # Application /src/cli_client/pbc diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index f53f8c41c..000000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -build: - os: ubuntu-20.04 - tools: - python: "3.9" - apt_packages: - - libasound2-dev - - libpulse0 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/sphinx/conf.py - builder: html - fail_on_warning: true - -# Optionally set the version of Python and requirements required to build your docs -# autodoc imports (!) modules. This means it needs ALL the dependencies, even the optional ones -python: - install: - - requirements: requirements.txt - - requirements: docs/sphinx/requirements_pyzmq.txt - - requirements: docs/sphinx/requirements.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3089b6d8..c3bc03705 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,6 @@ The Jukebox core app is written entirely in Python. Therefore, we follow the [Py * **Documentation** * You are expected to write some Documentation. It's easy. **Very** easy actually with [Python Docstrings](https://www.geeksforgeeks.org/python-docstrings/) - * If you dare, you may add the python documentation reference to the Sphinx documentation build. But we are also ok with doing that for you # Structure of files and folders @@ -38,8 +37,6 @@ Inside the root folder or the repo, these folders are important: * contains the Python packages that are loaded using the plugin interface * `src/webapp` * contains the Web Interface -* `src/docs/sphinx` - * contains the documentation sources and build flow using Sphinx All folders on all hierarchy levels starting with `scratch*` are ignored by git and flake8. These are intended as local, temporary scratch areas. @@ -132,18 +129,6 @@ cd /home/pi/RPi-Jukebox-RFID If you are convinced some issue should not apply to your case or would require extensive re-coding, that could be OK. Let us know in the pull request - we will look at it. -### Documentation - -When adding or improving documentation, build the documentation and look at it locally. -If you are contributing to existing Python modules, be aware that these are already included in the documentation flow. -Also run through this step in this case! Fix all warnings! - -~~~bash -$ cd /home/pi/RPi-Jukebox-RFID/ -$ ./run_sphinx.sh -# open and check the result: 'file:///path/to/RPi-Jukebox-RFID/docs/sphinx/_build/html/index.html' -~~~ - ### Tests Tests are very few at the moment, but it cannot hurt to run them. If you have tests for your new modules, please add diff --git a/README.md b/README.md index 619534ec3..e45ddaa26 100644 --- a/README.md +++ b/README.md @@ -11,24 +11,7 @@ project check out the [documentation of Version 2]( -

- -

-

- Local copy of documentation for {{ project }} Version {{ release }}. - View online documentation - -

- -{% endif %} -{% endblock %} diff --git a/docs/sphinx/api/api.rst b/docs/sphinx/api/api.rst deleted file mode 100644 index 117fe92f6..000000000 --- a/docs/sphinx/api/api.rst +++ /dev/null @@ -1,20 +0,0 @@ ----------------------- -Developers API ----------------------- - -Developers API Introduction - -.. toctree:: - :maxdepth: 2 - :caption: The Python packages: - - plugs - publishing - rpc_server - cfghandler - playlist - utils - volume - controls - callingback - gpioz/gpioz diff --git a/docs/sphinx/api/callingback.rst b/docs/sphinx/api/callingback.rst deleted file mode 100644 index f913b41fd..000000000 --- a/docs/sphinx/api/callingback.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder - -------------------------------- -Generic Callback Handler -------------------------------- - -.. automodule:: jukebox.callingback - :members: diff --git a/docs/sphinx/api/cfghandler.rst b/docs/sphinx/api/cfghandler.rst deleted file mode 100644 index cfdcd078d..000000000 --- a/docs/sphinx/api/cfghandler.rst +++ /dev/null @@ -1,5 +0,0 @@ -Config Handler -***************** - -.. automodule:: jukebox.cfghandler - :members: diff --git a/docs/sphinx/api/controls.rst b/docs/sphinx/api/controls.rst deleted file mode 100644 index 1ad0a2956..000000000 --- a/docs/sphinx/api/controls.rst +++ /dev/null @@ -1,19 +0,0 @@ - -Controls based on EvDev input devices -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - -Common -************************* - -.. automodule:: components.controls.common.evdev_listener - :members: - :private-members: _filter_by_device_name, _filter_by_mandatory_keys - -Bluetooth audio buttons -************************* - -See also the corresponding user guide :ref:`userguide/bluetooth_audio_buttons:Bluetooth audio buttons` - -.. automodule:: components.controls.bluetooth_audio_buttons - :members: diff --git a/docs/sphinx/api/gpioz/connector.rst b/docs/sphinx/api/gpioz/connector.rst deleted file mode 100644 index eb547de55..000000000 --- a/docs/sphinx/api/gpioz/connector.rst +++ /dev/null @@ -1,45 +0,0 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder - -------------------------------- -GPIOZ Connector Functions -------------------------------- - -.. automodule:: components.gpio.gpioz.plugin.connectivity - -.. currentmodule:: components.gpio.gpioz.plugin.connectivity - -Common -^^^^^^^^ - -.. autoattribute:: components.gpio.gpioz.plugin.connectivity.BUZZ_TONE - -RFID -^^^^^ - -.. autofunction:: register_rfid_callback - - -Volume -^^^^^^^^ - -.. autofunction:: register_volume_led_callback - -.. autofunction:: register_volume_rgbled_callback - -.. autofunction:: register_volume_buzzer_callback - -Audio output sink -^^^^^^^^^^^^^^^^^^ - -.. autofunction:: register_audio_sink_change_callback - -Status -^^^^^^^^ -.. autofunction:: register_status_led_callback - -.. autofunction:: register_status_buzzer_callback - -.. autofunction:: register_status_tonalbuzzer_callback - - diff --git a/docs/sphinx/api/gpioz/converter.rst b/docs/sphinx/api/gpioz/converter.rst deleted file mode 100644 index d65e4b595..000000000 --- a/docs/sphinx/api/gpioz/converter.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder - -------------------------------- -GPIOZ Converters -------------------------------- - -.. automodule:: components.gpio.gpioz.core.converter - :members: - :special-members: +__call__ - - - diff --git a/docs/sphinx/api/gpioz/gpioz.rst b/docs/sphinx/api/gpioz/gpioz.rst deleted file mode 100644 index 024fd90ee..000000000 --- a/docs/sphinx/api/gpioz/gpioz.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder - ----------------------- -GPIOZ ----------------------- - -The GPIOZ plugin connects to GPIO devices such as Buttons, LEDs, Buzzers. It utilizes GPIOZero to do so. - -.. toctree:: - :maxdepth: 2 - :caption: The GPIOZ packages: - - input_devices - output_devices - connector - converter diff --git a/docs/sphinx/api/gpioz/input_devices.rst b/docs/sphinx/api/gpioz/input_devices.rst deleted file mode 100644 index ba29d70f5..000000000 --- a/docs/sphinx/api/gpioz/input_devices.rst +++ /dev/null @@ -1,42 +0,0 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder - -------------------------------- -GPIOZ Input Devices -------------------------------- - -.. automodule:: components.gpio.gpioz.core.input_devices - -.. currentmodule:: components.gpio.gpioz.core.input_devices - -Button -^^^^^^^^ - -.. autoclass:: Button - :members: on_press, set_rpc_actions, close, value, pull_up, pin, hold_time, hold_repeat - -Long press Button -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autoclass:: LongPressButton - :members: on_press, set_rpc_actions, close, value, pull_up, pin, hold_time, hold_repeat - -Short + Long press Button -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autoclass:: ShortLongPressButton - :members: on_short_press, on_long_press, set_rpc_actions, close, value, pull_up, pin, hold_time, hold_repeat - -Twin Button -^^^^^^^^^^^^^^^^^^^ - -.. autoclass:: TwinButton - :members: on_short_press_a, on_short_press_b, on_short_press_ab, on_long_press_a, on_long_press_b, on_long_press_ab, value, is_active, hold_repeat, hold_time, close - -Rotary Encoder -^^^^^^^^^^^^^^^^^^ - -.. autoclass:: RotaryEncoder - :members: on_rotate_clockwise, on_rotate_counter_clockwise, set_rpc_actions, close, pin_a, pin_b - - diff --git a/docs/sphinx/api/gpioz/output_devices.rst b/docs/sphinx/api/gpioz/output_devices.rst deleted file mode 100644 index a30986f03..000000000 --- a/docs/sphinx/api/gpioz/output_devices.rst +++ /dev/null @@ -1,44 +0,0 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder - -------------------------------- -GPIOZ Output Devices -------------------------------- - -.. automodule:: components.gpio.gpioz.core.output_devices - -.. py:currentmodule:: components.gpio.gpioz.core.output_devices - - -LED -^^^ - -.. autoclass:: LED(pin, *, active_high=True, initial_value=False, pin_factory=None, name=None) - :members: flash, on, off, toggle, blink, pin, is_lit, value - -PWMLED -^^^^^^^ - -.. autoclass:: PWMLED(pin, *, active_high=True, initial_value=0, frequency=100, pin_factory=None, name=None) - :members: flash, on, off, toggle, blink, pulse, pin, is_lit, value - - -RGBLED -^^^^^^^ - -.. autoclass:: RGBLED(red, green, blue, *, active_high=True, initial_value=(0, 0, 0), pwm=True, pin_factory=None, name=None) - :members: flash, on, off, toggle, blink, pulse, red, green, blue, is_lit, color, value - - -Buzzer -^^^^^^^ - -.. autoclass:: Buzzer(pin, *, active_high=True, initial_value=False, pin_factory=None, name=None) - :members: flash, on, off, toggle, beep, pin, is_active, value - - -TonalBuzzer -^^^^^^^^^^^^^^ - -.. autoclass:: TonalBuzzer(pin, *, initial_value=None, mid_tone=Tone('A5'), octaves=2, pin_factory=None, name=None) - :members: melody, flash, play, stop, octaves, min_tone, mid_tone, max_tone, tone, is_active, value diff --git a/docs/sphinx/api/playlist.rst b/docs/sphinx/api/playlist.rst deleted file mode 100644 index febeb4c0f..000000000 --- a/docs/sphinx/api/playlist.rst +++ /dev/null @@ -1,11 +0,0 @@ -Playlist Generator -********************** - -.. automodule:: jukebox.playlistgenerator - -.. autoclass:: jukebox.playlistgenerator.PlaylistCollector - :members: - :private-members: _exclude_endings - - - diff --git a/docs/sphinx/api/plugs.rst b/docs/sphinx/api/plugs.rst deleted file mode 100644 index 73f2e1043..000000000 --- a/docs/sphinx/api/plugs.rst +++ /dev/null @@ -1,6 +0,0 @@ ----------------------------- -Plugs: The Plugin Package ----------------------------- - -.. automodule:: jukebox.plugs - :members: diff --git a/docs/sphinx/api/publishing.rst b/docs/sphinx/api/publishing.rst deleted file mode 100644 index 607d9e8d3..000000000 --- a/docs/sphinx/api/publishing.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. automodule:: jukebox.publishing.server - - jukebox.publishing - ------------------- - - .. autofunction:: jukebox.publishing.get_publisher - - jukebox.publishing.server - ------------------------- - - .. autoclass:: jukebox.publishing.server.Publisher - :members: diff --git a/docs/sphinx/api/rpc_server.rst b/docs/sphinx/api/rpc_server.rst deleted file mode 100644 index 634adf763..000000000 --- a/docs/sphinx/api/rpc_server.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. automodule:: jukebox.rpc.server - - jukebox.rpc.server - ------------------------- - - .. autoclass:: jukebox.rpc.server.RpcServer - :members: diff --git a/docs/sphinx/api/utils.rst b/docs/sphinx/api/utils.rst deleted file mode 100644 index 17ba7500c..000000000 --- a/docs/sphinx/api/utils.rst +++ /dev/null @@ -1,6 +0,0 @@ ----------------------------- -Jukebox utility functions ----------------------------- - -.. automodule:: jukebox.utils - :members: diff --git a/docs/sphinx/api/volume.rst b/docs/sphinx/api/volume.rst deleted file mode 100644 index 4bf700832..000000000 --- a/docs/sphinx/api/volume.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. RPI Jukebox RFID -.. RPI Jukebox RFID -.. Copyright (c) See file LICENSE in project root folder - -------------------------------- -Volume and Audio Sink Control -------------------------------- - -.. automodule:: components.volume - -.. autoclass:: components.volume.PulseMonitor - :members: - :inherited-members: Thread - -.. autoclass:: components.volume.PulseVolumeControl - :members: - :inherited-members: Thread diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py deleted file mode 100644 index 79b9476bd..000000000 --- a/docs/sphinx/conf.py +++ /dev/null @@ -1,67 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -import sphinx_rtd_theme -sys.path.insert(0, os.path.abspath('../../src/jukebox')) -# This is needed for autodoc to load components.plugins with regular import - i.e. w/o going through plugs.load(...) -import jukebox.plugs # noqa: E402 -jukebox.plugs.ALLOW_DIRECT_IMPORTS = True -import jukebox.utils # noqa: E402 - - -# -- Project information ----------------------------------------------------- - -project = 'RPi Jukebox RFID' -copyright = '2021-2022, The RPi Jukebox RFID Community' -author = 'The RPi Jukebox RFID Community' - -# The full version, including alpha/beta/rc tags -release = jukebox.version() -version = release - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# html_theme = 'alabaster' -html_theme = 'sphinx_rtd_theme' -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# There are none yet: Comment out to suppress warning -# html_static_path = ['_static'] - -# --------------------------------------------------- - -# Prefix document path to section labels, to use: -# `path/to/file:heading` instead of just `heading` -autosectionlabel_prefix_document = True diff --git a/docs/sphinx/developer/coreapps.rst b/docs/sphinx/developer/coreapps.rst deleted file mode 100644 index 887b8d143..000000000 --- a/docs/sphinx/developer/coreapps.rst +++ /dev/null @@ -1,61 +0,0 @@ -Jukebox Apps -=============== - -The Jukebox's core apps are located in ``src/jukebox``. Run the following command to learn more about each app and its parameters: - -.. code-block:: bash - - $ ./run_app_name.py -h - -Jukebox Core -***************** - -run_jukebox.py ---------------- - -.. automodule:: run_jukebox - -Configuration Tools -******************** - -Before running the configuration tools, stop the Jukebox Core service. -See :ref:`userguide/configuration:Best practice procedure`. - -run_configure_audio.py ------------------------------ - -.. automodule:: run_configure_audio - -run_register_rfid_reader.py ------------------------------ - -.. automodule:: run_register_rfid_reader - -Developer Tools -***************** - -run_rpc_tool.py ---------------- - -.. automodule:: run_rpc_tool - -run_publicity_sniffer.py -------------------------- - -.. automodule:: run_publicity_sniffer - -run_sphinx.sh -------------------------- - -This command rebuilds the documentation using a Sphinx flow, located in the main directory. - -The documentation is built partially from auto-generated RST-files. -Thee files contain the :ref:`userguide/rpc_command_reference:RPC Command Reference` -and :ref:`userguide/rpc_command_alias_reference:RPC Command Alias Reference`. - -.. code-block:: bash - - run_jukebox.py -a - -The above command regenerate these RST files. This only needs to be done when -the RPC call references need to be updated within the documentation flow. diff --git a/docs/sphinx/developer/developer_issues.rst b/docs/sphinx/developer/developer_issues.rst deleted file mode 100644 index dd3a511fd..000000000 --- a/docs/sphinx/developer/developer_issues.rst +++ /dev/null @@ -1,80 +0,0 @@ -Developer Issues -****************** - -.. contents:: - -Building the Webapp on the PI -================================== - -JavaScript heap out of memory --------------------------------- - -While (re-) building the Web App, you get the following output: - -.. code-block:: bash - :emphasize-lines: 12 - - pi@MusicPi:~/RPi-Jukebox-RFID/src/webapp $ npm run build - - > webapp@0.1.0 build - > react-scripts build - - Creating an optimized production build... - - [...] - - <--- JS stacktrace ---> - - FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory - - -**Reason** - -Not enough memory for Node - -**Solution** - -Prior to building set the node memory environment variable. - - #. Make sure the value is less than the total available space on the system, or you may run into the next issue. (Not always though!) - Check memory availability with ``free -mt``. - #. We also experience trouble, when the space is set too small a value. 512 always works, 256 sometimes does, sometimes does not. - If your free memory is small, consider increasing the swap size of your system! - -.. code-block:: bash - - export NODE_OPTIONS=--max-old-space-size=512 - npm run build - -Alternatively, use the provided script, which sets the variable for you (provided your swap size is large enough): - -.. code-block:: bash - - $ ./run_rebuild.sh - - -Process exited too early // kill -9 ---------------------------------------- - -.. code-block:: bash - :emphasize-lines: 8,9 - - pi@MusicPi:~/RPi-Jukebox-RFID/src/webapp $ npm run build - - > webapp@0.1.0 build - > react-scripts build - - ... - - The build failed because the process exited too early. - This probably means the system ran out of memory or someone called 'kill -9' on the process. - -**Reason** - -Node tried to allocate more memory than available on the system. - -**Solution** - -Adjust the node memory variable as described in :ref:`developer/developer_issues:JavaScript heap out of memory`. -But make sure to allocate less memory than the available memory. -If that is not sufficient, increase the swap file size of your system and try again. diff --git a/docs/sphinx/developer/development_environment.rst b/docs/sphinx/developer/development_environment.rst deleted file mode 100644 index 243584eb7..000000000 --- a/docs/sphinx/developer/development_environment.rst +++ /dev/null @@ -1,73 +0,0 @@ -Development Environment -************************ - -You have 3 development options: - -.. contents:: - -Directly on Raspberry Pi ------------------------- - -The full setup is running on the RPi and you access files via SSH. Pretty easy to set up as -you simply do a normal install and switch to the ``future3/develop`` branch. - -Steps to install -```````````````` - -We recommend to use at least a Pi 3 or Pi Zero 2 for development. This hardware won't be needed -in production, but it can be slow while developing. - -1. Install the latest Pi OS on a SD card. -2. Boot up your Raspberry Pi. -3. :ref:`Install ` the Jukebox software as if you were building a Phoniebox. You can install from your own fork and feature branch if you wish which can be changed later as well. The original repository will be set as ``upstream``. -4. Once the installation has successfully ran, reboot your Pi. -5. Due to some resource constraints, the Webapp does not build the latest changes and instead consumes the latest official release. To change that, you need to install NodeJS and build the Webapp locally. -6. Install NodeJS using the existing installer - -.. code-block:: bash - - cd ~/RPi-Jukebox-RFID/installation/routines; \ - source setup_jukebox_webapp.sh; \ - _jukebox_webapp_install_node - -7. To free up RAM, reboot your Pi. -8. Build the Webapp using the existing build command. If the build fails, you might have forgotten to reboot. - -.. code-block:: bash - - cd ~/RPi-Jukebox-RFID/src/webapp; \ - ./run_rebuild.sh -u - -9. The Webapp should now be updated. -10. To continuously update Webapp, pull the latest changes from your repository and rerun the command above. - - -Locally on any Linux machine ------------------------------- - -The jukebox also runs on any Linux machine. The Raspberry Pi specific stuff will not work of course. That is no issue depending -our your development area. USB RFID Readers, however, will work. -You may setup a Python virtual environment or a conda virtual environment. -You will have to install and configure `MPD (Music Player Daemon) `_. - -In addition to the `requirements.txt`, you will this dependency. On the Raspberry PI, the latest stable -release of ZMQ does not support WebSockets. We need to compile the latest -version from Github, which is taken care of by the installation script. -For regular machines, the normal package can be installed: - -.. code-block:: bash - - pip3 install pyzmq - - -You will have to start Jukebox core application and the WebUI separately. The MPD usually runs as a service. - -Using Docker container ------------------------------- - -There is a complete setup :ref:`docker workflow `. - -.. toctree:: - :hidden: - - docker diff --git a/docs/sphinx/developer/docker.rst b/docs/sphinx/developer/docker.rst deleted file mode 100644 index f3de61902..000000000 --- a/docs/sphinx/developer/docker.rst +++ /dev/null @@ -1,250 +0,0 @@ -Phoniebox Development Runbook for Docker environments -******************************************************** - -.. contents:: - -This document describes how to set up a local development environment with Docker. -It is useful to develop certain parts of the Phoniebox application that do not directly require the Raspberry Pi -hardware such as GPIO. *Raspberry Pi OS* is based on Debian but comes with a lot of special packages and a unique -graphical interface. It is difficult to mock a Raspberry Pi whithin a Docker container but we try to keep both -environments as close as possible. The Docker environment is not meant to be deployed on the Raspberry Pi directly for -performance reasons. - -Depending on your host environment (Mac, Linux or Windows), you might need to adapt some of those commands to your needs. - -Prerequisites --------------------------- - -1. Install required software - - * Linux - - * `Docker `_ - * `Compose `_ - - * Mac: - - * `Docker & Compose (Mac) `_ - * `pulseaudio (Doc) `_ - - * Windows: - - * `Docker & Compose (Win) `_ - * `pulseaudio (Win) `_ - -2. Pull the Jukebox repository: ``git clone https://github.com/MiczFlor/RPi-Jukebox-RFID.git`` - - -3. Create a jukebox.yaml file - - * Copy the ``./resources/default-settings/jukebox.default.yaml`` to ``./shared/settings`` and - rename the file to ``jukebox.yaml``. - - ``$ cp ./resources/default-settings/jukebox.default.yaml ./shared/settings/jukebox.yaml`` - - - * Override/Merge the values from the following - `Override file - `_ - in your ``jukebox.yaml``. - -4. Change directory into the ``./RPi-Jukebox-RFID/shared/audiofolders`` and copy a set of MP3 files into this folder (for more fun when testing). - -Run development environment ------------------------------- - -In contrary to how everything is set up on the Raspberry Pi, it's good practice to isolate different components in -different Docker images. They can be run individually or in combination. -To do that, we use ``docker compose``. - -Linux -^^^^^^^ - -Make sure you don't use ``sudo`` to run your ``docker compose``. Check out Docker's `post-installation guide ` -for more information. - -.. code-block:: bash - - // Build Images - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build - - // Run Docker Environment - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up - - // Shuts down Docker containers and Docker network - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down - - -The docker linux setup tries to mirror the current users pulseaudio socket from ``$XDG_RUNTIME_DIR/pulse/native`` to the containers ``mpd`` and ``jukebox``. -If your pulseaudio socket is in another place, adjust path in docker/docker-compose.linux.yml. You can find out the socket path with the command ``pactl info``. -To access the socket, the user inside the docker containers also must have the same UID as the user running docker. You can find out your UID by running ``id -u``. If your UID deviates from the default UID 1000, please run ``export UID=$(id -u)`` prior to any docker commands. - -Mac -^^^^^ - -Remember, pulseaudio is a prerequisite. `Follow these instructions `_ -for Mac hosts. - -.. code-block:: bash - - // Build Images - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build - - // Run Docker Environment - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml up - - // Shuts down Docker containers and Docker network - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml down - -Windows -^^^^^^^^^^^ - -#. Download `pulseaudio `_ -#. Uncompress somewhere in your user folder -#. Edit ``$INSTALL_DIR/etc/pulse/default.pa`` -#. Add the following line - - .. code-block:: bash - - load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 - -1. Edit ``$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf``, find the following line and change it to: - - .. code-block:: bash - - exit-idle-time = -1 - -1. Execute ``$INSTALL_DIR/bin/pulseaudio.exe`` -1. Run ``cocker-compose`` - -.. code-block:: bash - - // Build Images - $ docker compose -f docker/docker-compose.yml build - - // Run Docker Environment - $ docker compose -f docker/docker-compose.yml up - - // Shuts down Docker containers and Docker network - $ docker compose -f docker/docker-compose.yml down - -Test & Develop ---------------------- - -The Dockerfile is defined to start all Phoniebox related services. - -Windows: Open `host.docker.internal:3000 `_ in your browser to see the web application. -Linux / Mac: Open `http://localhost:3000 `_ in your browser to see the web application. - - -While the ``webapp`` container does not require a reload while working on it (hot-reload is enabled), -you will have to restart your ``jukebox`` container whenever you make a change (in the Python code). -Instead of stopping and starting the ``docker compose`` command, you can individually restart your -``jukebox`` container. Update the below path with your specific host environment. - -.. code-block:: bash - - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.[ENVIRONMENT].yml restart jukebox - -Known issues ----------------- - -The docker environment only exists to make development easier and possible without a physical device. It won't -replace it though. Therefore, we currently accept certain issues related to the individual Docker containers. -Here is a list of known errors or weird behaviour which you can easily ignore unless they prevent you from progressing. -If would be of course useful to get rid of them, but currently we make a trade-off between a development environment and -solving the specific details. - -``mpd`` container -^^^^^^^^^^^^^^^^^^ - -When starting the ``mpd`` container, you will see the following errors. You can ignore them, MPD will run. - -.. code-block:: bash - - mpd | exception: bind to '0.0.0.0:6600' failed (continuing anyway, because binding to '[::]:6600' succeeded): Failed to bind socket: Address already in use - mpd | exception: Failed to open '/root/.config/mpd/database': No such file or directory - mpd | exception: RTIOThread could not get realtime scheduling, continuing anyway: sched_setscheduler failed: Operation not permitted - mpd | avahi: Failed to create client: Daemon not running - - -You might also notice the following errors after the ``mpd`` Docker ran for a while. Specifically the first error -could fill up your console, sometimes it stops with the second error message. It's not a problem, sound continues to -work. As a side effect, your CPU usage increases. Just kill the process and restart. - -.. code-block:: bash - - mpd | alsa_mixer: snd_mixer_handle_events() failed: Input/output error - mpd | exception: Failed to read mixer for 'My ALSA Device': snd_mixer_handle_events() failed: Input/output error - - -``jukebox`` container -^^^^^^^^^^^^^^^^^^^^^^ - -Many features of the Phoniebox are based on the Raspberry Pi hardware. This hardware can't be mocked in a virtual Docker -environment. As a result, a few plugins like RFID, GPIO or CPU temperature will throw errors because they can't start -successfully. Unless you want to develop such plugins, you will be able to ignore these errors. The plugin system is built in a way -that the Jukebox daemon will come up. If you want to develop plugins that require hardware support, you will have to -work on the hardware directly. - -Typical errors and following exceptions to be ignored in the Docker ``jukebox`` container are: - -.. code-block:: bash - - jukebox | 277:utils.py - jb.utils - MainThread - ERROR - CalledProcessError: Command 'git log --pretty='%h [%cs] %s %d' -n 1 --no-color' returned non-zero exit status 128. - jukebox | 287:utils.py - jb.utils - MainThread - ERROR - CalledProcessError: Command 'git describe --tag --dirty='-dirty'' returned non-zero exit status 128. - jukebox | 634:plugs.py - jb.plugin - MainThread - ERROR - Ignoring failed package load finalizer: 'rfid.finalize()' - jukebox | 635:plugs.py - jb.plugin - MainThread - ERROR - Reason: FileNotFoundError: [Errno 2] No such file or directory: '/home/pi/RPi-Jukebox-RFID/shared/settings/rfid.yaml' - ... - jukebox | 171:__init__.py - jb.host.lnx - MainThread - ERROR - Error reading temperature. Canceling temperature publisher. FileNotFoundError: [Errno 2] No such file or directory: '/sys/class/thermal/thermal_zone0/temp' - ... - jukebox | 319:server.py - jb.pub.server - host.timer.cputemp - ERROR - Publish command from different thread 'host.timer.cputemp' than publisher was created from 'MainThread'! - - - -Appendix -------------- - -Individual Docker Image -^^^^^^^^^^^^^^^^^^^^^^^^ - -Run an individual Docker container, e.g. ``jukebox``. Similarly you could run ``mpd`` or ``webapp``. - -The following command can be run on a Mac. - -.. code-block:: bash - - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build jukebox - $ docker compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml run --rm --name jukebox jukebox - -Resources -^^^^^^^^^^^ - -**Mac** - -* https://stackoverflow.com/questions/54702179/how-to-access-mac-os-x-microphone-inside-docker-container -* https://stackoverflow.com/questions/40136606/how-to-expose-audio-from-docker-container-to-a-mac -* https://github.com/jessfraz/dockerfiles/blob/master/pulseaudio/Dockerfile - -**Windows** - -* https://stackoverflow.com/questions/52890474/how-to-get-docker-audio-and-input-with-windows-or-mac-host# -* https://arnav.jain.se/2020/enable-audio--video-in-docker-container/ -* https://x410.dev/cookbook/wsl/enabling-sound-in-wsl-ubuntu-let-it-sing/ -* https://research.wmz.ninja/articles/2017/11/setting-up-wsl-with-graphics-and-audio.html - -**Audio** - -* https://github.com/mviereck/x11docker/wiki/Container-sound:-ALSA-or-Pulseaudio -* https://mpd.fandom.com/wiki/PulseAudio -* https://stmllr.net/blog/streaming-audio-with-mpd-and-icecast2-on-raspberry-pi/ - -**MPD** - -* https://stmllr.net/blog/streaming-audio-with-mpd-and-icecast2-on-raspberry-pi/ -* https://github.com/Tob1asDocker/rpi-mpd -* https://github.com/vimagick/dockerfiles/tree/master/mpd - -**ZMQ** - -* https://codeblog.dotsandbrackets.com/using-zeromq-with-docker/ diff --git a/docs/sphinx/featurelist.rst b/docs/sphinx/featurelist.rst deleted file mode 100644 index bc3769e43..000000000 --- a/docs/sphinx/featurelist.rst +++ /dev/null @@ -1,313 +0,0 @@ -.. |[X]| unicode:: 0x2611 -.. |[ ]| unicode:: 0x2610 - -Feature Status -**************** - -**This is where we are in a nutshell:** Playing music from local folders via RFID trigger. -We also built a new WebUI to control the Jukebox from a browser. - -The are a few things that are specifically not integrated yet: playing streams, podcasts or Spotify. - -In the following is the currently implemented feature list in more detail. It also shows some of the shortcomings. -However the list is *not complete in terms of planned features*, -but probably **reflects more of where work is currently being put into**. - -**For new contributors:** If you want to port a feature from version 2.X or implement a new feature, contact us. Open an issue -or join us on in the chat room. You may pick topic marked as open below, but also any other topic missing in below list. -As mentioned, that list is not complete in terms of open features. Check the -`Contribution guide `_. -Topics marked *in progress* are already in the process of implementation by community members. - -.. contents:: - -Jukebox Core App -------------------- - -Base -^^^^^^^^ - -* |[X]| Clean up of surplus files -* |[X]| Host interface (shutdown, reboot) -* |[X]| Temperature getter - - * |[X]| Timer + Publisher - -* |[X]| RPi is_throttled getter - - * |[X]| Decode hex value to readable string (check version 2.x mqtt as reference?) - * |[X]| Timer + Publisher - -* |[X]| Git hash log information - - * |[X]| Log and publish this! - -* |[X]| Version number getter (Version number should be stored in a python file) - - * |[X]| Log and publish this - -* |[X]| Exit via RPC -* |[X]| Service restart via RPC - - * |[X]| Check if really running as a service - -* |[X]| Storage space getter / publisher (shutil.disk_usage) -* |[X]| Getter for error logs to show in WebUI - - * Get file location from FileHandlers (files may be stale!) - * Logger might be disabled or not connected - -* |[ ]| Enable/Disable debug logging from RPC -* |[X]| Publisher of errors (specialized logger handler) - - * This is a configurable logger handler in logger.yaml - -* |[X]| Basic Logging Config should enable Publisher stream handler -* |[ ]| Disable Console Stream Handler (or set to warning) when running as a service -* |[X]| Log & publish start time -* |[ ]| Method to change configuration through WebUI - - * The difficulty lies bringing the running Jukebox to accept the changes. There probably won't be a catch all solution - but rather a custom implementation for a select few features - -* |[X]| Strategy to post config changes via PubSub: Must be taken care of by the setter function modifying the property - - -Via RPC -^^^^^^^^ - -* |[X]| List of loaded / failed plugins -* |[X]| card action reference -* |[X]| Help command (available commands) - - * which basically is a plugin reference - -* |[X]| Simplified alias definitions for often used RPC commands (for RFID, GPIO, etc) - - * |[ ]| Port all previous commands - * |[X]| Reference file write-out: now also included in Sphinx documentation - * |[ ]| Export available alias definitions to RPC - * |[ ]| Base quick select on yaml file (*in progress*) - - * or write a yaml file as artifact which contains all the meta information about the functions as well? - * or include a ``get_signature`` function that returns the meta information for a given alias - -Config handler -^^^^^^^^^^^^^^^^^^^ - -* |[X]| While saving config to disk: local file change detection -* |[X]| cfghandler creates setndefault() at arbitrary depth - -ZMQ Publisher -^^^^^^^^^^^^^^^^^ - -* |[X]| Last Value Cache -* |[X]| Subscriber detection and initial status update -* |[X]| Port configuration option (WS and/or TCP) -* |[ ]| Callback registration option for plugin on topic send - - * How to interact with threads? - -Playback -^^^^^^^^^^^^^^^^^ - -* |[X]| Playlist generator - - * |[X]| Local folders - - * |[X]| Non-recursive folder play - * |[X]| Recursive folder play - - * |[X]| Podcast - * |[X]| Livestreams - * |[X]| NEW: Playback of m3u playlists (e.g. folder.m3u) ? - -* |[ ]| Folder configuration (*in progress*) - - * |[ ]| `Reference `_ - * |[ ]| Resume: Save and restore position (how interact with shuffle?) - * |[ ]| Single: Enable mpc single - * |[ ]| Shuffle: Enable mpc random (not shuffle) - - * Rename to random, as this is mpc random - - * |[ ]| Loop: Loop playlist - -MPD Player -^^^^^^^^^^^^^^^^^ - -* |[ ]| Thread safety for status information / configuration (*in progress*) -* |[ ]| Differential status post (*in progress*) -* |[ ]| Second swipe option setter via RPC (*in progress*) -* |[ ]| Before every music lib update, player should check user rights (not only after start-up) - -RFID -^^^^^^^^^^^^^^^^^ - -* |[X]| Test with Reader disabled -* |[X]| Start-up behaviour with un-configured Reader -* |[X]| Command card -> is now parameter ignore_same_id_delay -* |[X]| Revised RFID reader user-query setup script - - * |[ ]| Ask for place option - -* |[ ]| Enable config flag ? -* |[X]| Place not swipe / Timer thread - - * |[X]| Configurable card removal action - -* |[X]| Readers support - - * |[X]| USB (e.g. Neuftech) - * |[X]| RDM6300 - * |[X]| MFRC522 - * |[X]| RC532 - * |[X]| Multi-reader support - * |[X]| GUI Fake Reader for Development - * |[ ]| PC/SC Cards (what actually is this?) - -* |[X]| Publish RFID Card ID via PubSub - - * Needs to be thread safe - -* |[X]| Card reference IF via RPC - -* |[X]| Second Swipe Options -> must be part of player control (partially broken at the moment) - - * Freely configurable with an RPC call - * Ignore (nothing) - * Toggle Pause/Play - * Skip to next track - * Re-start playlist - -Cards -^^^^^^^^^^^^^^^^^ - -* |[ ]| Write a simplified card summary to - - * |[ ]| file - * |[X]| RPC - -* |[ ]| Card assignment function for WebUI - - * |[X]| Via RPC command alias definitions - * |[ ]| Full custom RPC call - -* |[X]| Remove card - -Timer -^^^^^^^^^^^^^^^ - -* |[X]| Shutdown timer -* |[X]| Play stop timer -* |[X]| Shutdown timer volume reduction - - * Decreases volume every x min until zero, then shuts down - * Needs to be cancelable - -* |[X]| Publish mechanism of timer status -* |[X]| Change multitimer function call interface such that endless timer etc wont pass the `iteration` kwarg -* |[ ]| Make timer settings persistent -* |[ ]| Idle timer - - * This needs clearer specification: Idle is when no music is playing and no user interaction is taking place - * i.e. needs information from RPC AND from player status. Let's do this when we see a little clearer about Spotify - - - -Volume -^^^^^^^^^^^^^^^^^ - -* |[X]| Jingle playback volume as fixed value in config -* |[X]| Default volume setting after boot-up -* |[X]| Max Volume -* |[X]| PulseAudio integration with event handler -* |[X]| Bluetooth support -* |[X]| Automatic audio sink toggle - - * |[ ]| Callbacks for audio sink change - -GPIO -^^^^^^^^^^^^^^^^^ - -* |[X]| All done! Read the docs at :ref:`userguide/gpioz:GPIO Recipes`! -* |[ ]| USB Buttons: It's a different category as it works similar to the RFID cards (in progress) - -WLAN -^^^^^^^^^^^^^^^^^ - -* |[X]| Ad-hoc WLAN Hot spot -* |[X]| IP address read-out - -Spotify -^^^^^^^^^^^^^^^^^ - -* |[ ]| Everything - -Others -^^^^^^^^^^^^^^^^^ - -* |[ ]| MQTT -* |[ ]| Record and Playback using a Mic -* |[ ]| Dot Matrix Displays - -Start-up stuff -^^^^^^^^^^^^^^^^^ - -* |[X]| check music folder permissions -* |[X]| mpc update / (mpc rescan) -* |[X]| sudo iwconfig wlan0 power off (need to be done after every restart) -* |[X]| Optional power down HDMI circuits: /usr/bin/tvservice -o - - -Debug Tools --------------- - -* |[X]| Publishing Sniffer - - * |[ ]| Update mode vs linear mode ? - -* |[X]| RPC command line client - - * |[X]| with tab-completion and history - - -WebUI --------------- - -* |[X]| Playback Control -* |[X]| Cover Art -* |[X]| Register cards / Delete cards -* |[X]| Shutdown button -* |[ ]| Settings configuration page -* |[ ]| System information page - - * |[ ]| Configure (one or multiple) WLANs - * |[X]| Enable/Disable Auto-Hotspot - -* |[X]| ``run_npm_build`` script - - * |[X]| Must consider ``export NODE_OPTIONS=--max-old-space-size=512`` - - -Installation Procedure ------------------------ - -* |[X]| Single call installation script -* |[X]| Query for settings vs. automatic version -* |[X]| IPQoS in SSH config -* |[X]| Separate static IP and IPv6 disable -* |[ ]| For all system config file changes, check prior to modification, if modification already exists - - -Documentation --------------- - -* |[X]| Sphinx / Restructured Text tool flow -* |[ ]| What is the Phoniebox -* |[X]| Artifacts: Generate artifacts (on command line switch only) for - - * |[X]| loaded plugins and rpc command aliases (to sphinx and shared/artifcats) - * |[X]| rpc command aliases (to sphinx and shared/artifcats) - -* |[ ]| How to: Write a plugin diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst deleted file mode 100644 index a83777673..000000000 --- a/docs/sphinx/index.rst +++ /dev/null @@ -1,57 +0,0 @@ -.. RPi Jukebox for Kids documentation master file, created by - sphinx-quickstart on Sun Sep 26 15:26:31 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to RPi Jukebox RFID's documentation! -================================================ - -.. include:: primer.rst - -.. toctree:: - :maxdepth: 2 - :caption: Getting Started: - - featurelist - install - migration - issues - -.. toctree:: - :maxdepth: 2 - :caption: User Guide: - - userguide/concepts - userguide/system - userguide/configuration - userguide/audio - userguide/troubleshooting - rfid/rfid - userguide/gpioz - userguide/bluetooth_audio_buttons - userguide/rpc_commands - userguide/rpc_command_reference - userguide/rpc_command_alias_reference - userguide/carddatabase - userguide/autohotspot - userguide/sync_rfidcards - - -.. toctree:: - :maxdepth: 2 - :caption: Developer Reference: - - developer/developer_issues.rst - developer/coreapps - developer/development_environment - api/api.rst - - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/sphinx/install.rst b/docs/sphinx/install.rst deleted file mode 100644 index 948c79c42..000000000 --- a/docs/sphinx/install.rst +++ /dev/null @@ -1,98 +0,0 @@ -Installing Phoniebox future3 -============================ - -Install Raspberry Pi OS Lite -------------------------------------------- - -.. important:: Currently, the installation does only work on Raspberry Pi's with ARMv7 and ARMv8 architecture, so 2, 3 and 4! - Pi 1 and Zero's are currently unstable and will require a bit more work! - -Before you can install the Phoniebox software, you need to prepare your Raspberry Pi. - -1. Connect a Micro SD card to your computer (preferable an SD card with high read throughput) -2. `Download `_ - the `Raspberry Pi Imager `_ and open it -3. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system. future3 does not support 64bit kernels (``aarch64``). You can use the settings menu of the Raspberry Pi Imager to configure SSH and WiFi in a more userfriendly way, or do it manually as described in the next step. In case you already have a 64bit system installed, `you can fix the issue like this `_. -4. Select your Micro SD card (your card will be formatted) -5. Click *Write* -6. Wait for the imaging process to be finished (it'll take a few minutes) - - -Pre-boot preparation -------------------------------------------- - -You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to proceed with the next steps. - -1. Open a terminal of your choice. -2. Insert your card again if it has been ejected automatically. -3. Navigate to your SD card e.g., ``cd /Volumes/boot`` for Mac or ``D:`` for Windows. -4. Enable SSH by adding a simple file. - - .. code-block:: bash - - $ touch ssh - -5. Set up your Wifi connection. - - *Mac* - - .. code-block:: bash - - $ nano wpa_supplicant.conf - - *Windows* - - .. code-block:: bash - - D:\> notepad wpa_supplicant.conf - -6. Insert the following content, update your country, Wifi credentials and save the file. - - .. code-block:: bash - - country=DE - ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev - update_config=1 - - network={ - ssid="network-name" - psk="network-password" - } - -7. Eject your SD card and insert it into your Raspberry Pi. -8. Start your Raspberry Pi by attaching a power supply. -9. Login into your Raspberry Pi, username is ``pi`` and password is ``raspberry``. - If ``raspberrypi.local`` does not work, find out your Raspberry Pi's IP address from your router. - -Install Phoniebox software -------------------------------------------- - -Run the following command in your SSH terminal and follow the instructions - -.. code-block:: bash - - cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh) - -This will get the latest stable release from the branch future3/main. -To install directly from a specific branch and/or a different repository -specify the variables like this: - -.. code-block:: bash - - cd; GIT_USER='MiczFlor' GIT_BRANCH='future3/develop' bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) - - -This will switch directly to the specified feature branch during installation. - -.. attention:: For all branches *except* the current Release, you will need to build the Web App locally on the Pi. - This is not part of the installation process due to memory limitation issues. - See :ref:`developer/development_environment:Steps to install`. - - - -If you suspect an error you can monitor the installation-process with - -.. code-block:: bash - - cd; tail -f INSTALL-.log - diff --git a/docs/sphinx/issues.rst b/docs/sphinx/issues.rst deleted file mode 100644 index d38d3ce5c..000000000 --- a/docs/sphinx/issues.rst +++ /dev/null @@ -1,15 +0,0 @@ -Known Issues -****************** - -Browsers ----------- - -The Web UI will **not** work with Firefox, due to an issue with websockets and pyzmq. Please use a different -browser for now. - -Configuration --------------- -In ``jukebox.yaml`` (and all other config files): do not use relative paths with ``~/some/dir``. -Always use entire explicit path, e.g. ``/home/pi/some/dir``. - -**Sole** exception is in playermpd.mpd_conf. diff --git a/docs/sphinx/migration.rst b/docs/sphinx/migration.rst deleted file mode 100644 index 8d3599953..000000000 --- a/docs/sphinx/migration.rst +++ /dev/null @@ -1,55 +0,0 @@ -Updating -************** - -Updating from Version 3.0 to 3.1 ---------------------------------------- - -There are a number of significant changes to the overall system setup, which require a fresh install. Notable changes in this respect include: - - #. Shift system setup to PulseAudio - #. Run MPD as user-local service - #. Run Jukebox Daemon as user local service - -.. important:: You need re-install on a fresh SD card! - A git pull procedure as described below will not work! - - -Updating your Jukebox Version 3 -------------------------------------- - -Things on Version 3 are moving fast and you may want to keep up with recent changes. Since we are in Alpha Release stage, -a fair number of fixes are expected to be committed in the near future. - -You will need to do three things to update your version from develop (or the next release candidate version) - -#. Pull the newest code base from Github -#. Check for new entries in the configuration -#. Re-build the WebUI - -.. code-block:: bash - - # Switch to develop (if desired) - $ git checkout future3/develop - - # Get latest code - $ git pull - - # Check if new (mandatory) options appeared in jukebox.yaml - # with your favourite diff tool and merge them - $ diff shared/settings/jukebox.yaml resources/default-settings/jukebox.default.yaml - - $ cd src/webapp - $ ./run_rebuild.sh - - -Migration Path from Version 2 -------------------------------------- - -There is no update path coming from Version 2.x of the Jukebox. -You need to do a fresh install of Version 3 on a fresh Raspian Bullseye image. - -.. important:: Do start with a fresh SD card image! - -Do not just pull the future3 branch into you existing Version 2.x directory. -Do not run the installer on an system that had Version 2.x running before on it. -Stuff has changed too much to make this feasible. diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt deleted file mode 100644 index e169c2c69..000000000 --- a/docs/sphinx/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# These are just the additional RTD-specific requirements -readthedocs-sphinx-search - -# Install packages for documentation build from package repository -# that are installed on the PI via APT -RPi.GPIO -gpiozero<2.0.0;python_version<'3.8' -gpiozero;python_version>='3.8' diff --git a/docs/sphinx/requirements_pyzmq.txt b/docs/sphinx/requirements_pyzmq.txt deleted file mode 100644 index e2a37a6e1..000000000 --- a/docs/sphinx/requirements_pyzmq.txt +++ /dev/null @@ -1,5 +0,0 @@ -# For operation of the Jukebox, -# ZMQ must be compiled from sources due to Websocket support -# When just building the docs, the regular ZMQ package is sufficient - -pyzmq diff --git a/docs/sphinx/rfid/basics.rst b/docs/sphinx/rfid/basics.rst deleted file mode 100644 index 7bc49ddeb..000000000 --- a/docs/sphinx/rfid/basics.rst +++ /dev/null @@ -1,91 +0,0 @@ -Basics ---------- - -Cards placed on the reader trigger an action. An action may be any callable plugin function through the RPC with any arguments. -Typically, this would be "play some folder", but can also be "activate shutdown timer", or "increase volume". -This is configured in the :ref:`userguide/carddatabase:Card Database`. - -You may configure a single or even multiple parallel readers (of different or identical types). - -Successive card swipes are suppressed to avoid bouncing effects. This behavior can be deactivated for individual cards. - -Reader Types ------------- - -place-capable: - Some readers give a single event signal when the card is placed on the reader. This is sufficient - to build a fully-featured Jukebox. Other readers give a continuous signal. They allow both card placements - and card removals. This can be used to play the Jukebox when a card is placed and to pause it when it's removed. - - Generally, **not** all :ref:`USB-based RFID readers ` are place-capable. - - The known place-capable readers are :ref:`RDM6300 `, - :ref:`MFRC522 ` or - :ref:`PN532 `. - -Frequency: - Readers operate on one of two different frequencies: 125kHz or 13.56 MHz. - Make sure to buy compatible cards, RFID stickers or key fobs working with the same frequency as the reader. - -Reader Configuration ------------------------ - -During the installation process, you can already configure a RFID reader. To manually configure RFID reader(s), -:ref:`please run the tool ` ``src/jukebox/run_register_rfid_reader.py``. - -It will generate a reader configuration file at ``shared/settings/rfid.yaml``. -You can re-run the tool to change the settings any time. - -Some options are not covered by the tool. You may change the file manually. - -.. code-block:: yaml - - rfid: - readers: - read_00: - module: fake_reader_gui - config: .... - same_id_delay: float|integer - log_ignored_cards: true|false - place_not_swipe: - enabled: true|false - card_removal_action: - alias: pause - -For each reader, there is an entry ``read_XX``. - -module: - Indicates the Python package used for this reader. Filled by the RFID configuration tool. - -config: - Filled by the - :ref:`RFID configuration tool ` ``src/jukebox/run_register_rfid_reader.py`` - based on default values and user input. - After running the tool, you may manually change some settings here, as not everything can - be configured through the tool. Note that re-running the tool will completely rewrite the - configuration file. - -same_id_delay: float | integer - Minimum delay in seconds between 2 card detections before triggering a new action. This - is to prevent double triggering or bouncing. - -place_not_swipe: true | false - For place-capable RFID readers enable dual action mode: - a start action (e.g. playing) on card placement and card removal action (e.g. pause). - -card_removal_action: Dictionary - Executes the given function on card removal. Only relevant if place_not_swipe is true. The action is identical for all cards read on - that reader. The removal-action can be set to ignored on a card-by-card basis. - More on card action configurations in :ref:`userguide/rpc_commands:RPC Commands`. - - Developer's note: The reason for a unique removal action for all cards is that card triggering and card removal are happening - in two separate threads. Removal needs to be in a time-out thread. Thus, we would need to transport information from - one thread to another. This can be done of course but is not implemented (yet). Ignoring card removal is much easier and works for now. - -log_ignored_cards: true | false - Log all cards that are ignored due to same_id_delay. This is a option for developers. Don't use it unless you need it for debugging as it has - the potential to spam your log files. - -Second Swipe - Looking for 'Second Swipe' option? That is part of the Player configuration and not part of the RFID configuration, as - the 'Second Swipe' action needs to take into account the player state, which can also be altered through the WebUI. diff --git a/docs/sphinx/rfid/genericusb.rst b/docs/sphinx/rfid/genericusb.rst deleted file mode 100644 index 03ef2c4ef..000000000 --- a/docs/sphinx/rfid/genericusb.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../src/jukebox/components/rfid/hardware/generic_usb/README.rst \ No newline at end of file diff --git a/docs/sphinx/rfid/mfrc522_spi.rst b/docs/sphinx/rfid/mfrc522_spi.rst deleted file mode 100644 index ac57bda26..000000000 --- a/docs/sphinx/rfid/mfrc522_spi.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../src/jukebox/components/rfid/hardware/rc522_spi/README.rst diff --git a/docs/sphinx/rfid/mock_reader.rst b/docs/sphinx/rfid/mock_reader.rst deleted file mode 100644 index 8acb618ec..000000000 --- a/docs/sphinx/rfid/mock_reader.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../src/jukebox/components/rfid/hardware/fake_reader_gui/README.rst diff --git a/docs/sphinx/rfid/pn532_i2c.rst b/docs/sphinx/rfid/pn532_i2c.rst deleted file mode 100644 index 849fc6e05..000000000 --- a/docs/sphinx/rfid/pn532_i2c.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.rst \ No newline at end of file diff --git a/docs/sphinx/rfid/rdm6300.rst b/docs/sphinx/rfid/rdm6300.rst deleted file mode 100644 index e84643e77..000000000 --- a/docs/sphinx/rfid/rdm6300.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../src/jukebox/components/rfid/hardware/rdm6300_serial/README.rst \ No newline at end of file diff --git a/docs/sphinx/rfid/rfid.rst b/docs/sphinx/rfid/rfid.rst deleted file mode 100644 index ad7c00d24..000000000 --- a/docs/sphinx/rfid/rfid.rst +++ /dev/null @@ -1,14 +0,0 @@ ----------------------- -RFID Readers ----------------------- - -.. toctree:: - :maxdepth: 2 - - basics - genericusb - rdm6300 - mfrc522_spi - pn532_i2c - mock_reader - template diff --git a/docs/sphinx/rfid/template.rst b/docs/sphinx/rfid/template.rst deleted file mode 100644 index 70d01622b..000000000 --- a/docs/sphinx/rfid/template.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../src/jukebox/components/rfid/hardware/template_new_reader/README.rst \ No newline at end of file diff --git a/docs/sphinx/userguide/audio.rst b/docs/sphinx/userguide/audio.rst deleted file mode 100644 index 51b7b9a40..000000000 --- a/docs/sphinx/userguide/audio.rst +++ /dev/null @@ -1,152 +0,0 @@ -Audio Configuration -==================== - -The Jukebox supports 2 audio outputs, primary and secondary. The **primary output** is the default output and must -be available after system boot. This will typically be your sound card or the Pi's built-in headphone output. - -The **secondary output** is an optional alternative output where the audio stream can be routed to. -Stream transfer happens on user input or automatically on the connection of an audio device. -This is mainly targeted at Bluetooth Headsets/Speakers. - -Audio outputs run via PulseAudio and the basic configuration should be easy. -There is a :ref:`configuration tool`, -to setup the configuration for the Jukebox Core App. - -To set up the audio - - #. Follow the setup steps according to your sound card - #. Check that the sound output works :ref:`as described below` - #. Run the the tool :ref:`developer/coreapps:run_configure_audio.py` - #. :ref:`Fine-tune audio parameters` - -Checking system sound output -------------------------------- - -Run the following steps in a console: - -.. code-block:: bash - - # Check available PulseAudio sinks - $ pactl list sinks short - 0 alsa_output.platform-soc_sound.stereo-fallback module-alsa-card.c s16le 2ch 48000Hz - 1 bluez_sink.C4_FB_20_63_CO_FE.a2dp_sink module-bluez5-device.c s16le 2ch 44100Hz - - # Set the default sink (this will be reset at reboot) - $ pactl set-default-sink sink_name - - # Check default sink is correctly set - $ pactl info - .... - # Check volume level (exit with ESC) - $ alsamixer - - # Play a sound - $ paplay /usr/share/sounds/alsa/Front_Center.wav - - # This must also work when using an ALSA device - $ aplay /usr/share/sounds/alsa/Front_Center.wav - -You can also try different PulseAudio sinks without setting the default sink. In this case the volume is the last used -volume level for this sink: - -.. code-block:: bash - - $ paplay -d sink_name /usr/share/sounds/alsa/Front_Center.wav - - -Bluetooth ------------ - -Bluetooth setup consists of three steps - - #. Pair and connect your Bluetooth device - #. Check the output works - #. Re-run the config tool - -To pair and connect, follow these steps. This will be a one-time setup. - -.. code-block:: bash - - $ bluetoothctl - Agent registered - [CHG] Controller B8:27:EB:44:C4:33 Pairable: yes - #### Put your headset into pairing mode - [bluetooth]# scan on - Discovery started - #### Wait a few seconds for your device to appear - .... - [NEW] Device C4:FB:20:63:CO:FE PowerLocus Buddy - .... - [bluetooth]# scan off - .... - [bluetooth]# pair C4:FB:20:63:CO:FE - .... - Pairing successful - .... - [bluetooth]# trust C4:FB:20:63:CO:FE - .... - [bluetooth]# connect C4:FB:20:63:CO:FE - .... - [PowerLocus Buddy]# exit - - -Wait for a few seconds and then with ``$ pactl list sinks short``, check wether the Bluetooth device shows up as an output. -Its name usually looks like this: ``bluez_sink.C4_FB_20_63_CO_FE.a2dp_sink``. - -Run through steps in `Checking system sound output` to check wether the output is working or not. -If it does not work immediately, turn your headset off and on to force a reconnect. - -Rerun the config tool to register the Bluetooth device with the Jukebox core app as its secondary audio output. - -Additional options -------------------- - -For other audio configuration options, please look at the ``jukebox.yaml`` for now. - -Directly edit ``jukebox.yaml`` following the steps: :ref:`userguide/configuration:Best practice procedure`. - - -Developer Information ------------------------ - -The optional processing stages *Equalizer* and *Mono down mix* are realized by PulseAudio plugins. -The processing chain is - -.. code-block:: text - - player --> mono mix --> equalizer --> hardware sink - -Both plugins (if enabled) appear in the PulseAudio sinks - -.. code-block:: bash - - $ pactl list sinks short - ... - -Which means we can put any of these as sink into the jukebox configuration file (if there is any need). - -Mono down mix is enabled by the module ``module-remap-sink`` -for which `documentation and an example can be found here -`_. - -The equalizer is the PulseAudio module ``module-ladspa-sink`` with the `corresponding documentation -`_. - -This in turn loads a `LADSPA plugin `_. -The LADSPA plugin in the ``Eq10X2`` plugin of the `CAPS Library `_ -The CAPS library is available as linux package ``caps``. - -This is the same plugin which is used in the -`equalizer for pure ALSA `_ -configurations which is part of the linux package ``libasound2-plugin-equal``. - -You are, of course, free to modify the PulseAudio configuration to your needs. References - - #. `PulseAudio Documentation `_ - #. `PulseAudio Examples `_ - -In this case, run the configuration tool with below parameter to avoid touching the PulseAudio configuration file. - -.. code-block:: bash - - $ ./run_configure_audio.py --ro_pulse diff --git a/docs/sphinx/userguide/autohotspot.rst b/docs/sphinx/userguide/autohotspot.rst deleted file mode 100644 index 9156412b1..000000000 --- a/docs/sphinx/userguide/autohotspot.rst +++ /dev/null @@ -1,112 +0,0 @@ -Auto-Hotspot -************ - -The Auto-Hotspot function allows the Jukebox to switch between its connection between a known WiFi and an automatically -generated hotspot so that you can still access via SSH or Webapp. - -.. important:: Please configure the WiFi connection to your home access point before enabling these feature! - -To create a hotspot and allow clients to connect `hostapd` [1]_ and `dnsmasq` [2]_ - -How to connect --------------- - -When the Jukebox is not able to connect to a known WiFi it will create a hotspot named ``Phoniebox_Hotspot``. You will be -able to connect to this hotspot using the given password in the installation or the default password: ``PlayItLoud!`` - -Webapp -^^^^^^ - -After connecting to the ``Phoniebox_Hotspot`` you are able to connect to the webapp accessing the website `10.0.0.5 `_ - -ssh -^^^ - -After connecting to the ``Phoniebox_Hotspot`` you are able to connect via ssh to your Jukebox - -.. code-block:: bash - - ssh pi@10.0.0.5 - - -Changing basic configuration of the hotspot -------------------------------------------- - -The whole hotspot configuration can be found at ``/etc/hostapd/hostapd.conf``. - -The following parameters are relevant: - -* ``ssid`` for the displayed hotspot name -* ``wpa_passphrase`` for the password of the hotspot -* ``country_code`` the country you are currently in - -.. code-block:: bash - - $ cat /etc/hostapd/hostapd.conf - - #2.4GHz setup wifi 80211 b,g,n - interface=wlan0 - driver=nl80211 - ssid=Phoniebox_Hotspot - hw_mode=g - channel=8 - wmm_enabled=0 - macaddr_acl=0 - auth_algs=1 - ignore_broadcast_ssid=0 - wpa=2 - wpa_passphrase==PlayItLoud! - wpa_key_mgmt=WPA-PSK - wpa_pairwise=CCMP TKIP - rsn_pairwise=CCMP - - #80211n - Change GB to your WiFi country code - country_code=DE - ieee80211n=1 - ieee80211d=1 - -Disabling automatism --------------------- - -Auto-Hotspot can be enabled or disabled using the Webapp. - -.. important:: Disabling or enabling will keep the last state. - -Troubleshooting --------------------- - -Phoniebox is not connecting to the known WiFi -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The script will fall back to the hotspot so you still have some type of connection. - -Check your password in ``/etc/wpa_supplicant/wpa_supplicant.conf``. - -AutoHotspot functionality is not working -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can check the output of the script by running the following script: - -.. code-block:: bash - - $ sudo /usr/bin/autohotspot - -You need to add a new wifi network to the Raspberry Pi -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Because it is in Auto-Hotspot mode, you won't be able to scan for new wifi signals. - -You will need to add a new network to ``/etc/wpa_supplicant/wpa_supplicant.conf`` manually. Enter the following details -replacing mySSID and myPassword with your details. If your WiFi has a hidden SSID then include the line ``scan_ssid=1``. - -Resources ---------- - -`Raspberry Pi - Auto WiFi Hotspot Switch - Direct Connection `__ - ------------- - -References: - -.. [1] http://w1.fi/hostapd/ -.. [2] https://thekelleys.org.uk/dnsmasq/doc.html diff --git a/docs/sphinx/userguide/bluetooth_audio_buttons.rst b/docs/sphinx/userguide/bluetooth_audio_buttons.rst deleted file mode 100644 index b935edb36..000000000 --- a/docs/sphinx/userguide/bluetooth_audio_buttons.rst +++ /dev/null @@ -1,4 +0,0 @@ -Bluetooth audio buttons -************************* - -.. include:: ../../../src/jukebox/components/controls/bluetooth_audio_buttons/README.rst \ No newline at end of file diff --git a/docs/sphinx/userguide/carddatabase.rst b/docs/sphinx/userguide/carddatabase.rst deleted file mode 100644 index d0b40a903..000000000 --- a/docs/sphinx/userguide/carddatabase.rst +++ /dev/null @@ -1,94 +0,0 @@ -Card Database -***************** - -In the card database, an RPC command is assigned to every card. - -This RPC command is called every time when the card is swiped (or placed) on the reader. Every -RPC callable function can be called. See :ref:`userguide/rpc_commands:RPC Commands` for an introduction. - -The card database is stored in ``shared\settings\cards.yaml``. -Here are some examples for RPC command assignments to cards '0001' to '0003' using the alias option: - -.. important:: Card IDs **must** be strings! So, be sure to quote numbers! - -.. code-block:: yaml - - '0001': - # A RPC command using the alias definition without any arguments - # Here: pause playback - alias: pause - '0002': - # A RPC command using the alias definition with one arguments - # Here: Trigger music playback through the card interface - alias: play_card - args: [path/to/folder] - '0003': - # A RPC command using keyword arguments. Args and kwargs can also be mixed. - # Args and Kwargs translate directly into the function python call - # Some as in '0002' but using kwargs - alias: play_card - kwargs: - folder: path/to/folder - -.. note:: - * Remember card ids must be strings! So, quote them! - * *args* must be a **list** of arguments to be passed! Even if ony a single argument is passed. So, use *args: [value]*. - We try catch mis-uses but that might not always work. - -Additional options -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In addition to the RPC commands, these options may be specified for every card - -ignore_card_removal_action: true | false (default: false) - Only applies when using a place-capable reader and *place_not_swipe* is *true*. This option is ignored otherwise, - so it does not hurt. - - Do not execute card removal action, when this card is removed from the reader. Useful for command card, - that e.g. enable the shutdown timer - - .. code-block:: yaml - - '0004': - alias: timer_shutdown - ignore_card_removal_action: true - - -ignore_same_id_delay: true | false (default: false) - Override the ``same_id_delay`` parameter from the reader configuration for this card. - If true, the ``same_id_delay`` for this card is treated as 0. - This makes sense e.g., for an "increase volume" card in combination with a place-capable RFID reader. - As long as the card is placed on the reader, the volume is increased. - - .. note:: This parameter causes *ignore_card_removal_action* to be treated as true - - .. code-block:: yaml - - '0005': - alias: incr_volume - ignore_same_id_delay: true - ignore_card_removal_action: true - -Full RPC action specification -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You have seen some examples card actions using the *alias* configuration. A full RPC action can also be specified -using the following syntax: - -.. code-block:: yaml - - '0006': - # Option 1: Omit the keyword 'alias' - # Here: Set the volume to level 12 - package: volume - plugin: ctrl - method: set_volume - args: [12] - '0007': - # Option 2: Set 'alias' to custom - # Here: Set the volume to level 12 - alias: custom - package: volume - plugin: ctrl - method: set_volume - args: [12] diff --git a/docs/sphinx/userguide/concepts.rst b/docs/sphinx/userguide/concepts.rst deleted file mode 100644 index 19d5d37e5..000000000 --- a/docs/sphinx/userguide/concepts.rst +++ /dev/null @@ -1,46 +0,0 @@ -Concepts -================================================ - -The Jukebox is based on three concepts. Don't worry we won't dive into all the juicy developer details (just yet). -But a rough understanding is important as an foundation to understand the configuration files. - -Plugin Interface ----------------- - -The core app is centered around a plugin concept. This serves three purposes: - - #. Dynamically load packages with additional functionality based on configuration files. - #. Initialize and close these packages at app start / close. This happens automatically in the background. Failing - packages (for any reason) are ignored during start-up. So when some functionality is not available, always - check the logs to ensure all packages have loaded successfully! See :ref:`userguide/troubleshooting:Troubleshooting`. - #. Register and present functions which can be called via the :ref:`userguide/concepts:Remote Procedure Call Server (RPC)` - -That's about what you need to know for the plugin concept. Developer detailed information -can be found here (TBD). - -Remote Procedure Call Server (RPC) --------------------------------------- - -The Remote Procedure Call (RPC) server allows to remotely trigger actions (e.g., from the Webapp) within the Jukebox core application. -Only Python functions registered by the plugin interface can be called. This -simplifies external APIs and let's us focus on the relevant user functions. - -Why should you care? Because we use the same protocol when triggering actions from other inputs like a card swipe, a -GPIO button press, etc. How that works is described in :ref:`userguide/rpc_commands:RPC Commands`. - -You will find a full list of RPC callable functions in :ref:`userguide/rpc_command_reference:RPC Command Reference` -and aliases for convinience in :ref:`userguide/rpc_command_alias_reference:RPC Command Alias Reference` - -For developers the details can be found here (TBD). We also have a tool to send RPC commands to the running Jukebox application: -:ref:`developer/coreapps:run_rpc_tool.py` - -Publishing Message Queue --------------------------- - -The Publishing Message Queue is the complimentary part to the RPC where the core application publishes its status and status updates. -As a user, you need not worry about it. - -If you want to interact with the Jukebox from your own application, this is where you get the current -state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming -status messages: :ref:`developer/coreapps:run_publicity_sniffer.py`. - diff --git a/docs/sphinx/userguide/configuration.rst b/docs/sphinx/userguide/configuration.rst deleted file mode 100644 index ac40b16d7..000000000 --- a/docs/sphinx/userguide/configuration.rst +++ /dev/null @@ -1,43 +0,0 @@ -Jukebox Configuration -======================= - -The Jukebox configuration is managed by set of files located in ``../shared/settings``. -Some configuration changes can be made through the WebUI and take immediate effect. - -The majority of configuration options is only available by editing the config files - -*when the service is not running!* -Don't fear (overly), they contain commentaries. - -For several aspects we have :ref:`developer/coreapps:Configuration Tools` and detailed guides: - - * :ref:`userguide/audio:Audio Configuration` - * :ref:`RFID Reader Configuration` - -Even after running the tools certain aspects can only be changed by modifying the configuration files directly. - -Best practice procedure -------------------------- - -.. code-block:: bash - - # Make sure the Jukebox service is stopped - $ systemctl --user stop jukebox-daemon - - # Edit the file(s) - $ nano ./shared/settings/jukebox.yaml - - # Start Jukebox in console and check the log output (optional) - $ ./src/jukebox/run_jukebox.py - # and if OK, press Ctrl-C and restart the service - - # Restart the service - $ systemctl --user start jukebox-daemon - - -To try different configurations, you can start the Jukebox with a custom config file. -This could be useful if you want your Jukebox to only allow a lower volume when started -at night time when there is time to go to bed :-) - -.. code-block:: bash - - $./run_jukebox.py --conf ../path/to/custom/config.yaml diff --git a/docs/sphinx/userguide/gpioz.rst b/docs/sphinx/userguide/gpioz.rst deleted file mode 100644 index 5ba83d97a..000000000 --- a/docs/sphinx/userguide/gpioz.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../src/jukebox/components/gpio/gpioz/README.rst \ No newline at end of file diff --git a/docs/sphinx/userguide/rpc_command_alias_reference.rst b/docs/sphinx/userguide/rpc_command_alias_reference.rst deleted file mode 100644 index e5dfc2cb5..000000000 --- a/docs/sphinx/userguide/rpc_command_alias_reference.rst +++ /dev/null @@ -1,188 +0,0 @@ -RPC Command Alias Reference -*************************** - - -.. |--| unicode:: U+2014 -.. |->| unicode:: U+21d2 - -.. py:function:: play_card(...) -> player.ctrl.play_card(folder: str, recursive: bool = False) - :noindex: - - **Play music folder triggered by card swipe** - - Main entry point for trigger music playing from RFID reader. Decodes second swipe options before playing folder content - - Checks for second (or multiple) trigger of the same folder and calls first swipe / second swipe action - accordingly. - - :param folder: Folder path relative to music library path - :param recursive: Add folder recursively - - .. note:: This function you'll want to use most often - -.. py:function:: play_album(...) -> player.ctrl.play_album(albumartist: str, album: str) - :noindex: - - **Play Album triggered by card swipe** - - Playback a album found in MPD database. - - All album songs are added to the playlist - The playlist is cleared first. - - :param albumartist: Artist of the Album provided by MPD database - :param album: Album name provided by MPD database - - .. note:: This function plays the content of a given album - -.. py:function:: play_single(...) -> player.ctrl.play_single(song_url) - :noindex: - - **Play a single song triggered by card swipe** - - - - .. note:: This function plays the content of a given song URL - -.. py:function:: play_folder(...) -> player.ctrl.play_folder(folder: str, recursive: bool = False) -> None - :noindex: - - **Play a folder URL triggered by card swipe** - - Playback a music folder. - - Folder content is added to the playlist as described by :mod:`jukebox.playlistgenerator`. - The playlist is cleared first. - - :param folder: Folder path relative to music library path - :param recursive: Add folder recursively - - .. note:: This function plays the content of a given folder URL - -.. py:function:: pause(...) -> player.ctrl.pause(state: int = 1) - :noindex: - - Enforce pause to state (1: pause, 0: resume) - - This is what you want as card removal action: pause the playback, so it can be resumed when card is placed - on the reader again. What happens on re-placement depends on configured second swipe option - - .. note:: This is what you want as card removal action for place capable readers - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: next_song(...) -> player.ctrl.next() - :noindex: - - Play next track in current playlist - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: prev_song(...) -> player.ctrl.prev() - :noindex: - - - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: toggle(...) -> player.ctrl.toggle() - :noindex: - - Toggle pause state, i.e. do a pause / resume depending on current state - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: set_volume(...) -> volume.ctrl.set_volume(volume: int) - :noindex: - - Set the volume (0-100) for the currently active output - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: change_volume(...) -> volume.ctrl.change_volume(step: int) - :noindex: - - Increase/decrease the volume by step for the currently active output - - .. note:: For place-capable readers increment volume as long as card is on reader - - Default actions modifiers - **ignore_card_removal_action** |--| True - - **ignore_same_id_delay** |--| True - -.. py:function:: set_soft_max_volume(...) -> volume.ctrl.set_soft_max_volume(max_volume: int) - :noindex: - - Limit the maximum volume to max_volume for the currently active output - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: toggle_output(...) -> volume.ctrl.toggle_output() - :noindex: - - Toggle the audio output sink - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: shutdown(...) -> host.shutdown() - :noindex: - - Shutdown the host machine - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: reboot(...) -> host.reboot() - :noindex: - - Reboot the host machine - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: say_my_ip(...) -> host.say_my_ip(option='full') - :noindex: - - - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: timer_shutdown(...) -> timers.timer_shutdown.start(wait_seconds=None) - :noindex: - - **Start the shutdown timer** - - Start the timer (with default or new parameters) - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: timer_fade_volume(...) -> timers.timer_fade_volume.start(iterations=None, wait_seconds_per_iteration=None) - :noindex: - - **Start the volume fade out timer and shutdown** - - Start the timer (with default or new parameters) - - Default actions modifiers - **ignore_card_removal_action** |--| True - -.. py:function:: timer_stop_player(...) -> timers.timer_stop_player.start(wait_seconds=None) - :noindex: - - **Start the stop music timer** - - Start the timer (with default or new parameters) - - Default actions modifiers - **ignore_card_removal_action** |--| True - diff --git a/docs/sphinx/userguide/rpc_command_reference.rst b/docs/sphinx/userguide/rpc_command_reference.rst deleted file mode 100644 index e600ea336..000000000 --- a/docs/sphinx/userguide/rpc_command_reference.rst +++ /dev/null @@ -1,994 +0,0 @@ -RPC Command Reference -*********************** - - -This file provides a summary of all the callable functions through the RPC. It depends on the loaded modules - -.. contents:: - -Module: publishing -------------------------------------------- - - -**loaded_from**: components.publishing - -Plugin interface for Jukebox Publisher - - -.. py:function:: publishing.republish(topic=None) - :noindex: - - Re-publish the topic tree 'topic' to all subscribers - - :param topic: Topic tree to republish. None = resend all - - -Module: volume -------------------------------------------- - - -**loaded_from**: components.volume - -PulseAudio Volume Control Plugin Package - - -.. py:function:: volume.ctrl.change_volume(step: int) - :noindex: - - Increase/decrease the volume by step for the currently active output - - -.. py:function:: volume.ctrl.get_mute() - :noindex: - - Return mute status for the currently active output - - -.. py:function:: volume.ctrl.get_outputs() - :noindex: - - Get current output and list of outputs - - -.. py:function:: volume.ctrl.get_soft_max_volume() - :noindex: - - Return the maximum volume limit for the currently active output - - -.. py:function:: volume.ctrl.get_volume() - :noindex: - - Get the volume - - -.. py:function:: volume.ctrl.mute(mute=True) - :noindex: - - Set mute status for the currently active output - - -.. py:function:: volume.ctrl.publish_outputs() - :noindex: - - Publish current output and list of outputs - - -.. py:function:: volume.ctrl.publish_volume() - :noindex: - - Publish (volume, mute) - - -.. py:function:: volume.ctrl.set_output(sink_index: int) - :noindex: - - Set the active output (sink_index = 0: primary, 1: secondary) - - -.. py:function:: volume.ctrl.set_soft_max_volume(max_volume: int) - :noindex: - - Limit the maximum volume to max_volume for the currently active output - - -.. py:function:: volume.ctrl.set_volume(volume: int) - :noindex: - - Set the volume (0-100) for the currently active output - - -.. py:function:: volume.ctrl.toggle_output() - :noindex: - - Toggle the audio output sink - - -Module: jingle -------------------------------------------- - - -**loaded_from**: components.jingle - -Jingle Playback Factory for extensible run-time support of various file types - - -.. py:function:: jingle.play(filename) - :noindex: - - Play the jingle using the configured jingle service - - Note: This runs in a separate thread. And this may cause troubles - when changing the volume level before - and after the sound playback: There is nothing to prevent another - thread from changing the volume and sink while playback happens - and afterwards we change the volume back to where it was before! - - There is no way around this dilemma except for not running the jingle as a - separate thread. Currently (as thread) even the RPC is started before the sound - is finished and the volume is reset to normal... - - However: Volume plugin is loaded before jingle and sets the default - volume. No interference here. It can now only happen - if (a) through the RPC or (b) some other plugin the volume is changed. Okay, now - (a) let's hope that there is enough delay in the user requesting a volume change - (b) let's hope no other plugin wants to do that - (c) no bluetooth device connects during this time (and pulseaudio control is set to toggle_on_connect) - and take our changes with the threaded approach. - - -.. py:function:: jingle.play_startup() - :noindex: - - Play the startup sound (using jingle.play) - - -.. py:function:: jingle.play_shutdown() - :noindex: - - Play the shutdown sound (using jingle.play) - - -Module: jingle.alsawave -------------------------------------------- - - -**loaded_from**: components.jingle.alsawave - -ALSA wave jingle Service for jingle.JingleFactory - - -.. py:function:: jingle.alsawave.alsawave.play(filename) - :noindex: - - Play the wave file - - -Module: jingle.jinglemp3 -------------------------------------------- - - -**loaded_from**: components.jingle.jinglemp3 - -Generic MP3 jingle Service for jingle.JingleFactory - - -.. py:function:: jingle.jinglemp3.jinglemp3.play(filename) - :noindex: - - Play the MP3 file - - -Module: player -------------------------------------------- - - -**loaded_from**: components.playermpd - -Package for interfacing with the MPD Music Player Daemon - - -.. py:function:: player.ctrl.get_current_song(param) - :noindex: - - - - -.. py:function:: player.ctrl.get_folder_content(folder: str) - :noindex: - - Get the folder content as content list with meta-information. Depth is always 1. - - Call repeatedly to descend in hierarchy - - :param folder: Folder path relative to music library path - - -.. py:function:: player.ctrl.get_player_type_and_version() - :noindex: - - - - -.. py:function:: player.ctrl.get_song_by_url(song_url) - :noindex: - - - - -.. py:function:: player.ctrl.list_albums() - :noindex: - - - - -.. py:function:: player.ctrl.list_all_dirs() - :noindex: - - - - -.. py:function:: player.ctrl.list_song_by_artist_and_album(albumartist, album) - :noindex: - - - - -.. py:function:: player.ctrl.map_filename_to_playlist_pos(filename) - :noindex: - - - - -.. py:function:: player.ctrl.move() - :noindex: - - - - -.. py:function:: player.ctrl.next() - :noindex: - - Play next track in current playlist - - -.. py:function:: player.ctrl.pause(state: int = 1) - :noindex: - - Enforce pause to state (1: pause, 0: resume) - - This is what you want as card removal action: pause the playback, so it can be resumed when card is placed - on the reader again. What happens on re-placement depends on configured second swipe option - - -.. py:function:: player.ctrl.play() - :noindex: - - - - -.. py:function:: player.ctrl.play_album(albumartist: str, album: str) - :noindex: - - Playback a album found in MPD database. - - All album songs are added to the playlist - The playlist is cleared first. - - :param albumartist: Artist of the Album provided by MPD database - :param album: Album name provided by MPD database - - -.. py:function:: player.ctrl.play_card(folder: str, recursive: bool = False) - :noindex: - - Main entry point for trigger music playing from RFID reader. Decodes second swipe options before playing folder content - - Checks for second (or multiple) trigger of the same folder and calls first swipe / second swipe action - accordingly. - - :param folder: Folder path relative to music library path - :param recursive: Add folder recursively - - -.. py:function:: player.ctrl.play_folder(folder: str, recursive: bool = False) -> None - :noindex: - - Playback a music folder. - - Folder content is added to the playlist as described by :mod:`jukebox.playlistgenerator`. - The playlist is cleared first. - - :param folder: Folder path relative to music library path - :param recursive: Add folder recursively - - -.. py:function:: player.ctrl.play_single(song_url) - :noindex: - - - - -.. py:function:: player.ctrl.playerstatus() - :noindex: - - - - -.. py:function:: player.ctrl.playlistinfo() - :noindex: - - - - -.. py:function:: player.ctrl.prev() - :noindex: - - - - -.. py:function:: player.ctrl.queue_load(folder) - :noindex: - - - - -.. py:function:: player.ctrl.remove() - :noindex: - - - - -.. py:function:: player.ctrl.repeatmode(mode) - :noindex: - - - - -.. py:function:: player.ctrl.replay() - :noindex: - - Re-start playing the last-played folder - - Will reset settings to folder config - - -.. py:function:: player.ctrl.replay_if_stopped() - :noindex: - - Re-start playing the last-played folder unless playlist is still playing - - .. note:: To me this seems much like the behaviour of play, - but we keep it as it is specifically implemented in box 2.X - - -.. py:function:: player.ctrl.resume() - :noindex: - - - - -.. py:function:: player.ctrl.rewind() - :noindex: - - Re-start current playlist from first track - - Note: Will not re-read folder config, but leave settings untouched - - -.. py:function:: player.ctrl.second_swipe_action() - :noindex: - - Toggle pause state, i.e. do a pause / resume depending on current state - - -.. py:function:: player.ctrl.seek(new_time) - :noindex: - - - - -.. py:function:: player.ctrl.shuffle(random) - :noindex: - - - - -.. py:function:: player.ctrl.stop() - :noindex: - - - - -.. py:function:: player.ctrl.toggle() - :noindex: - - Toggle pause state, i.e. do a pause / resume depending on current state - - -.. py:function:: player.ctrl.update() - :noindex: - - - - -Module: cards -------------------------------------------- - - -**loaded_from**: components.rfid.cards - -Handling the RFID card database - - -.. py:function:: cards.list_cards() - :noindex: - - Provide a summarized, decoded list of all card actions - - This is intended as basis for a formatter function - - Format: 'id': {decoded_function_call, ignore_same_id_delay, ignore_card_removal_action, description, from_alias} - - -.. py:function:: cards.delete_card(card_id: str, auto_save: bool = True) - :noindex: - - :param auto_save: - :param card_id: - - -.. py:function:: cards.register_card(card_id: str, cmd_alias: str, args: Union[List, NoneType] = None, kwargs: Union[Dict, NoneType] = None, ignore_card_removal_action: Union[bool, NoneType] = None, ignore_same_id_delay: Union[bool, NoneType] = None, overwrite: bool = False, auto_save: bool = True) - :noindex: - - Register a new card based on quick-selection - - If you are going to call this through the RPC it will get a little verbose - - **Example:** Registering a new card with ID *0009* for increment volume with a custom argument to inc_volume - (*here: 15*) and custom *ignore_same_id_delay value*:: - - plugin.call_ignore_errors('cards', 'register_card', - args=['0009', 'inc_volume'], - kwargs={'args': [15], 'ignore_same_id_delay': True, 'overwrite': True}) - - -.. py:function:: cards.register_card_custom() - :noindex: - - Register a new card with full RPC call specification (Not implemented yet) - - -.. py:function:: cards.load_card_database(filename) - :noindex: - - - - -.. py:function:: cards.save_card_database(filename=None, *, only_if_changed=True) - :noindex: - - Store the current card database. If filename is None, it is saved back to the file it was loaded from - - -Module: rfid -------------------------------------------- - - -**loaded_from**: components.rfid.reader - - - - -Module: timers -------------------------------------------- - - -**loaded_from**: components.timers - - - - -.. py:function:: timers.timer_shutdown.cancel() - :noindex: - - Cancel the timer - - -.. py:function:: timers.timer_shutdown.get_state() - :noindex: - - Get the current state and config as dictionary - - -.. py:function:: timers.timer_shutdown.get_timeout() - :noindex: - - Get the configured time-out - - :return: The total wait time. (Not the remaining wait time!) - - -.. py:function:: timers.timer_shutdown.is_alive() - :noindex: - - Check if timer is active - - -.. py:function:: timers.timer_shutdown.publish() - :noindex: - - Publish the current state and config - - -.. py:function:: timers.timer_shutdown.set_timeout(wait_seconds: float) - :noindex: - - Set a new time-out in seconds. Re-starts the timer if already running! - - -.. py:function:: timers.timer_shutdown.start(wait_seconds=None) - :noindex: - - Start the timer (with default or new parameters) - - -.. py:function:: timers.timer_shutdown.toggle() - :noindex: - - Toggle the activation of the timer - - -.. py:function:: timers.timer_shutdown.trigger() - :noindex: - - Trigger the next target execution before the time is up - - -.. py:function:: timers.timer_stop_player.cancel() - :noindex: - - Cancel the timer - - -.. py:function:: timers.timer_stop_player.get_state() - :noindex: - - Get the current state and config as dictionary - - -.. py:function:: timers.timer_stop_player.get_timeout() - :noindex: - - Get the configured time-out - - :return: The total wait time. (Not the remaining wait time!) - - -.. py:function:: timers.timer_stop_player.is_alive() - :noindex: - - Check if timer is active - - -.. py:function:: timers.timer_stop_player.publish() - :noindex: - - Publish the current state and config - - -.. py:function:: timers.timer_stop_player.set_timeout(wait_seconds: float) - :noindex: - - Set a new time-out in seconds. Re-starts the timer if already running! - - -.. py:function:: timers.timer_stop_player.start(wait_seconds=None) - :noindex: - - Start the timer (with default or new parameters) - - -.. py:function:: timers.timer_stop_player.toggle() - :noindex: - - Toggle the activation of the timer - - -.. py:function:: timers.timer_stop_player.trigger() - :noindex: - - Trigger the next target execution before the time is up - - -.. py:function:: timers.timer_fade_volume.cancel() - :noindex: - - Cancel the timer - - -.. py:function:: timers.timer_fade_volume.get_timeout() - :noindex: - - Get the configured time-out - - :return: The total wait time. (Not the remaining wait time!) - - -.. py:function:: timers.timer_fade_volume.is_alive() - :noindex: - - Check if timer is active - - -.. py:function:: timers.timer_fade_volume.publish() - :noindex: - - Publish the current state and config - - -.. py:function:: timers.timer_fade_volume.set_timeout(wait_seconds: float) - :noindex: - - Set a new time-out in seconds. Re-starts the timer if already running! - - -.. py:function:: timers.timer_fade_volume.start(iterations=None, wait_seconds_per_iteration=None) - :noindex: - - Start the timer (with default or new parameters) - - -.. py:function:: timers.timer_fade_volume.toggle() - :noindex: - - Toggle the activation of the timer - - -.. py:function:: timers.timer_fade_volume.trigger() - :noindex: - - Trigger the next target execution before the time is up - - -Module: host -------------------------------------------- - - -**loaded_from**: components.hostif.linux - - - - -.. py:function:: host.shutdown() - :noindex: - - Shutdown the host machine - - -.. py:function:: host.reboot() - :noindex: - - Reboot the host machine - - -.. py:function:: host.jukebox_is_service() - :noindex: - - Check if current Jukebox process is running as a service - - -.. py:function:: host.is_any_jukebox_service_active() - :noindex: - - Check if a Jukebox service is running - - .. note:: Does not have the be the current app, that is running as a service! - - -.. py:function:: host.restart_service() - :noindex: - - Restart Jukebox App if running as a service - - -.. py:function:: host.get_disk_usage(path='/') - :noindex: - - Return the disk usage in Megabytes as dictionary for RPC export - - -.. py:function:: host.get_cpu_temperature() - :noindex: - - Get the CPU temperature with single decimal point - - No error handling: this is expected to take place up-level! - - -.. py:function:: host.publish_cpu_temperature() - :noindex: - - - - -.. py:function:: host.get_ip_address() - :noindex: - - Get the IP address - - -.. py:function:: host.say_my_ip(option='full') - :noindex: - - - - -.. py:function:: host.wlan_disable_power_down(card=None) - :noindex: - - Turn off power management of wlan. Keep RPi reachable via WLAN - - This must be done after every reboot - card=None takes card from configuration file - - -.. py:function:: host.get_autohotspot_status() - :noindex: - - Get the status of the auto hotspot feature - - -.. py:function:: host.stop_autohotspot() - :noindex: - - Stop auto hotspot functionality - - Basically disabling the cronjob and running the script one last time manually - - -.. py:function:: host.start_autohotspot() - :noindex: - - start auto hotspot functionality - - Basically enabling the cronjob and running the script one time manually - - -.. py:function:: host.timer_temperature.cancel() - :noindex: - - Cancel the timer - - -.. py:function:: host.timer_temperature.get_timeout() - :noindex: - - Get the configured time-out - - :return: The total wait time. (Not the remaining wait time!) - - -.. py:function:: host.timer_temperature.is_alive() - :noindex: - - Check if timer is active - - -.. py:function:: host.timer_temperature.publish() - :noindex: - - Publish the current state and config - - -.. py:function:: host.timer_temperature.set_timeout(wait_seconds: float) - :noindex: - - Set a new time-out in seconds. Re-starts the timer if already running! - - -.. py:function:: host.timer_temperature.start(wait_seconds=None) - :noindex: - - Start the timer (with default or new parameters) - - -.. py:function:: host.timer_temperature.toggle() - :noindex: - - Toggle the activation of the timer - - -.. py:function:: host.timer_temperature.trigger() - :noindex: - - Trigger the next target execution before the time is up - - -Module: bluetooth_audio_buttons -------------------------------------------- - - -**loaded_from**: components.controls.bluetooth_audio_buttons - -Plugin to attempt to automatically listen to it's buttons (play, next, ...) -when a bluetooth sound device (headphone, speakers) connects - - -.. py:function:: bluetooth_audio_buttons.activate(device_name: str, exact: bool = True, open_initial_delay: float = 0.25) - :noindex: - - - - -Module: gpio -------------------------------------------- - - -**loaded_from**: components.gpio.gpioz.plugin - -The GPIOZ plugin interface build all input and output devices from the configuration file and connects -the actions and callbacks. It also provides a very restricted, but common API for the output devices to the RPC. -That API is mainly used for testing. All the relevant output state changes are usually made through callbacks directly -using the output device's API. - - -.. py:function:: gpio.on(name: str) - :noindex: - - Turn an output device on - - :param name: The alias name output device instance - - -.. py:function:: gpio.off(name: str) - :noindex: - - Turn an output device off - - :param name: The alias name output device instance - - -.. py:function:: gpio.set_value(name: str, value: Any) - :noindex: - - Set the output device to :attr:`value` - - :param name: The alias name output device instance - - :param value: Value to set the device to - - -.. py:function:: gpio.flash(name, on_time=1, off_time=1, n=1, *, fade_in_time=0, fade_out_time=0, tone=None, color=(1, 1, 1)) - :noindex: - - Flash (blink or beep) an output device - - This is a generic function for all types of output devices. Parameters not applicable to an - specific output device are silently ignored - - :param name: The alias name output device instance - - :param on_time: Time in seconds in state ``ON`` - - :param off_time: Time in seconds in state ``OFF`` - - :param n: Number of flash cycles - - :param tone: The tone in to play, e.g. 'A4'. *Only for TonalBuzzer*. - - :param color: The RGB color *only for PWMLED*. - - :param fade_in_time: Time in seconds for transitioning to on. *Only for PWMLED and RGBLED* - - :param fade_out_time: Time in seconds for transitioning to off. *Only for PWMLED and RGBLED* - - -Module: music_cover_art -------------------------------------------- - - -**loaded_from**: components.music_cover_art - -Read all cover art from music save it to a cache for the UI to load - - -.. py:function:: music_cover_art.ctrl.get_by_filename_as_base64(audio_src: str) - :noindex: - - - - -Module: misc -------------------------------------------- - - -**loaded_from**: components.misc - -Miscellaneous function package - - -.. py:function:: misc.rpc_cmd_help() - :noindex: - - Return all commands for RPC - - -.. py:function:: misc.get_all_loaded_packages() - :noindex: - - Get all successfully loaded plugins - - -.. py:function:: misc.get_all_failed_packages() - :noindex: - - Get all plugins with error during load or initialization - - -.. py:function:: misc.get_start_time() - :noindex: - - Time when JukeBox has been started - - -.. py:function:: misc.get_log_debug() - :noindex: - - Get the log file (from the debug_file_handler) - - -.. py:function:: misc.get_log_error() - :noindex: - - Get the log file (from the error_file_handler) - - -.. py:function:: misc.get_version() - :noindex: - - - - -.. py:function:: misc.get_git_state() - :noindex: - - Return git state information for the current branch - - -.. py:function:: misc.empty_rpc_call(msg: str = '') - :noindex: - - This function does nothing. - - The RPC command alias 'none' is mapped to this function. - - This is also used when configuration errors lead to non existing RPC command alias definitions. - When the alias definition is void, we still want to return a valid function to simplify error handling - up the module call stack. - - :param msg: If present, this message is send to the logger with severity warning - - - - -Generation notes -------------------------------------------- - - -This is an automatically generated file from the loaded plugins: - -* *publishing*: components.publishing -* *volume*: components.volume -* *jingle*: components.jingle -* *jingle.alsawave*: components.jingle.alsawave -* *jingle.jinglemp3*: components.jingle.jinglemp3 -* *player*: components.playermpd -* *cards*: components.rfid.cards -* *rfid*: components.rfid.reader -* *timers*: components.timers -* *host*: components.hostif.linux -* *bluetooth_audio_buttons*: components.controls.bluetooth_audio_buttons -* *gpio*: components.gpio.gpioz.plugin -* *music_cover_art*: components.music_cover_art -* *misc*: components.misc diff --git a/docs/sphinx/userguide/rpc_commands.rst b/docs/sphinx/userguide/rpc_commands.rst deleted file mode 100644 index 5b2a58515..000000000 --- a/docs/sphinx/userguide/rpc_commands.rst +++ /dev/null @@ -1,116 +0,0 @@ -RPC Commands -***************** - -We use the RPC commands when triggering actions from different inputs like a card swipe, -a GPIO button press, etc. Triggering an action is equal to sending an RPC function call. -In many places the command to send when an input is triggered is configurable in a YAML-file. - -Basics ---------- - -Consequently, you need to know how to specify the RPC command in the YAML file. -Here is the essence of what you need to know: - -An RPC command consists of up to three parts - - #. the function to execute (e.g. play_folder, change_volume) - #. the positional arguments (optional) - #. the keyword arguments (optional) - -The function specification consists of two (e.g., ``host.shutdown``) or three terms (e.g., ``volume.ctrl.change_volume``). -In configuration files, this will look like this: - -.. code-block:: yaml - - package: host - plugin: shutdown - -Or like this for a three part function with the argument set to ``5``: - -.. code-block:: yaml - - package: volume - plugin: ctrl - method: change_volume - args: [5] - -The keyword ``method`` is optional. If needs to be used depends on the function you want to call. -You will find a full list of RPC callable functions in :ref:`userguide/rpc_command_reference:RPC Command Reference`, - -Aliases --------- - -Not so complicated, right? It will get even easier. For common commands we have defined aliases. An alias simply maps -to a pre-defined RPC command, e.g. ``play_card`` maps to ``player.ctrl.play_card``. - -Instead of - -.. code-block:: yaml - - package: player - plugin: ctrl - method: play_card - args: [path/to/folder] - -you can simply specify instead : - -.. code-block:: yaml - - alias: play_card - args: [path/to/folder] - -Using in alias is optional. But if the keyword is present in the configuration it takes precedence over an explicit -specified RPC command. All alias definitions may be found -in the :ref:`userguide/rpc_command_alias_reference:RPC Command Alias Reference`. - -Arguments --------------- - -Arguments can be specified in similar fashion to Python function arguments: as positional arguments and / or -keyword arguments. Let's check out play_card, which is defined as: - -.. py:function:: play_card(...) -> player.ctrl.play_card(folder: str, recursive: bool = False) - :noindex: - - :param folder: Folder path relative to music library path - :param recursive: Add folder recursively - -This means it takes two arguments: - - * folder of type string - * recursive of type bool - -In the following examples, we will always use the alias for smaller configuration text. All three examples -do exactly the same, but use different ways of specifying the command. - -.. code-block:: yaml - - alias: play_card - args: [path/to/folder, True] - -.. code-block:: yaml - - alias: play_card - args: [path/to/folder] - kwargs: - recursive: True - -.. code-block:: yaml - - alias: play_card - kwargs: - folder: path/to/folder - recursive: True - - -.. important:: *args* must be a **list** of arguments to be passed! Even if only a single argument is passed. - So, use *args: [value]*. We try catch mis-uses but that might not always work. - - -You will find some more examples the configuration of the :ref:`userguide/carddatabase:Card Database` - -For developers ----------------- - -There is a ready-to-use decoding functions which decodes an RPC command (with or without alias) -from a YAML entry:func:`jukebox.utils.decode_rpc_command`. diff --git a/docs/sphinx/userguide/sync_rfidcards.rst b/docs/sphinx/userguide/sync_rfidcards.rst deleted file mode 100644 index 5cb554acb..000000000 --- a/docs/sphinx/userguide/sync_rfidcards.rst +++ /dev/null @@ -1,71 +0,0 @@ -Syncronisation RFID Cards -************************* - -This component handles the synchronisation of RFID cards (audiofolder and card database entries). - -It allows to manage card database entries and audiofiles of one to many Phonieboxes -in a central place (e.g. NAS, primary Phoniebox etc.) in the network, -but allows to play the audio offline once the data has synced. -The synchronisation can be initiated with the command ``sync-all`` -and optionally on every RFID scan for a particular CardID and its corresponding audiofolder. -To execute the ``sync-all`` command, bind a RFID card to the command. -For the "RFID scan sync" feature, activate the option in the configuration -or bind a RFID card to the command for dynamic activation or deactivation. - -Synchronisation ---------------- - -The synchronisation will be FROM a server TO the Phoniebox, overriding existing files. -A local configuration will be lost after the synchronization. -If you want to make the initial setup e.g. via WebUi copy the files and use it as a base for the server. - -To access the files on the server, 2 modes are supported: SSH or MOUNT. -Please make sure you have the correct access rights to the source and use key-based authentication for SSH. - -RFID scan sync -^^^^^^^^^^^^^^ -If the feature "RFID scan sync" is activated, there will be a check on every RFID scan against the server -if a matching card entry and audiofolder is available. If so, changes will be synced. -The playback will be delayed for the time the data is transfered (see "sync-all" to use a full synchronization if a lot of new files have been added). -If the server is not reachable, the check will be aborted after the timeout. -Therfore, an unreachable server will cause a delay (see commands to toggle activation state). -Deleted card entries / audiofolders (not the contained items) will not be purged locally if deleted on remote. -This is also true for changed card entries (the old audiofolder / -files will remain). To remove not existing items us a "sync-all". - -Configuration -------------- - -Set the corresponding setting in ``shared\settings\jukebox.yaml`` to activate this feature. - -.. code-block:: yaml - - modules: - named: - ... - sync_rfidcards: synchronisation.rfidcards - - ... - sync_rfidcards: - enable: false - config_file: ../../shared/settings/sync_rfidcards.yaml - -The settings file (``shared\settings\sync_rfidcards.yaml``) contains the following configuration - -.. code-block:: yaml - - sync_rfidcards: - # Holds the activation state of the optional feature "RFID scan sync". Values are "TRUE" or "FALSE" - on_rfid_scan_enabled: true # bool - # Server Access mode. MOUNT or SSH - mode: mount # 'mount' or 'ssh' - credentials: - # IP or hostname of the server (used to check connectivity and for SSH mode). e.g. "192.168.0.2" or "myhomeserver.local" - server: '' - # Port (used to check connectivity and for SSH mode). e.g. "80" or "22" - port: # int - # Timeout to reach the server (in seconds) (used to check connectivity). e.g. 1 - timeout: 1 # int - # Path to the shared files to sync (without trailing slash) (remote path for SSH mode or local path for MOUNT mode). e.g. "/mnt/Phoniebox" - path: '' - # Username if SSH mode is used. - username: '' diff --git a/docs/sphinx/userguide/system.rst b/docs/sphinx/userguide/system.rst deleted file mode 100644 index 3740663ab..000000000 --- a/docs/sphinx/userguide/system.rst +++ /dev/null @@ -1,121 +0,0 @@ -System Setup -===================== - -A few words on how the system is setup and interacts. - -The system consists of - - #. the :ref:`userguide/system:Music Player Daemon (MPD)` which we use for all music playback (local, stream, podcast, ...) - #. the :ref:`userguide/system:PulseAudio` for flexible audio output support - #. the :ref:`userguide/system:Jukebox Core Service` for controlling MPD and PulseAudio and providing all the features - #. the :ref:`userguide/system:Web UI` which is served through an Nginx web server - #. a set of :ref:`developer/coreapps:Configuration Tools` and - a set of :ref:`developer/coreapps:Developer Tools` - -.. note:: The default install puts everything into the folder ``/home/pi/RPi-Jukebox-RFID``. - Another folder might work, but is certainly not tested. Things are installed for the default user ``pi``. Again, - another user might work, but is not tested. - -Music Player Daemon (MPD) --------------------------- - -The Music Player Daemon runs as *user-local* service (not as system-wide service which is usually the default). -This is important for the interaction with PulseAudio. - -You will find the MPD configuration file under - -.. code-block:: text - - $HOME/.config/mpd/mpd.conf - -All MPD *var*-files are also located in ``$HOME/.config/mpd``. - -The service can be controlled with the *systemctl*-command when adding the parameter ``--user``: - -.. code-block:: bash - - $ systemctl --user status mpd - $ systemctl --user start mpd - $ systemctl --user stop mpd - -.. important:: Never start or enable the system-wide MPD service with `sudo systemctl start mpd`! - -To check if MPD is running or has issues, use - -.. code-block:: bash - - $ systemctl --user status mpd - # or, if you need to get the full logs - $ journalctl --user -b -u mpd - -The ``systemd`` service file is located at the default location for user services: - -.. code-block:: text - - /usr/lib/systemd/user/mpd.service - -PulseAudio ---------------------- - -We use PulseAudio for the audio output configuration. Check out the Audio Configuration page for details. - -There is a number of reasons for that: - - * It is easier to support and setup different audio hardware. Over the years, many builders have - tried many different ways to set up audio on their Jukebox so this become the most reliable and compatible - solution - * We can cleanly control and switch between different audio outputs independent of the playback software - * The current Pi OS based on Bullseye does not allow another way to control Bluetooth based speakers, - as Bluealsa is currently not working with Bluez 5 - -The PulseAudio configuration file is located at - -.. code-block:: text - - ~/.config/pulse/default.pa - -Service control and service configuration file location is identical to MPD. - -Jukebox Core Service ---------------------- - -The :ref:`developer/coreapps:Jukebox Core` runs as a *user-local* service with the name ``jukebox-daemon``. -Similar to MPD, it's important that it does run as system-wide service to be able to interact with PulseAudio. - -The service can be controlled with the ``systemctl``-command by adding the parameter ``--user`` - -.. code-block:: bash - - $ systemctl --user start jukebox-daemon - $ systemctl --user stop jukebox-daemon - -Check out the service with - -.. code-block:: bash - - $ systemctl --user status jukebox-daemon - # and if you need to get the full log output - $ journalctl --user -b -u jukebox-daemon - -The ``systemd`` service file is located at the default location for user services: - -.. code-block:: text - - /usr/lib/systemd/user/jukebox-daemon.service - -Starting and stopping the service can be useful for debugging or configuration checks. - -Web UI ------------------------ - -The Web UI is served using nginx. Nginx runs as a system service. The home directory is localed at - -.. code-block:: text - - /home/pi/RPi-Jukebox-RFID/src/webapp/build - -The Nginx configuration is located at - -.. code-block:: text - - /etc/nginx/sites-available/default diff --git a/docs/sphinx/userguide/troubleshooting.rst b/docs/sphinx/userguide/troubleshooting.rst deleted file mode 100644 index 24d1fec9c..000000000 --- a/docs/sphinx/userguide/troubleshooting.rst +++ /dev/null @@ -1,107 +0,0 @@ -Troubleshooting -***************** - -We have made a point of providing extensive log messages. -In full debug mode, this may become very verbose. In fact, better observability -has been one of the design goals for version 3. - -There are various options to get access to debug information. - -Debugging your setup runs in several steps - - #. Check that :ref:`audio output works` - #. Check that :ref:`MPD works` - #. Checking log messages from the Jukebox Core App as described below - -The short answer ----------------- - -**We are still in the Pre-Release phase, so by default two log files should be written out:** - -.. code-block:: bash - - ../shared/logs/app.log : Complete Debug Messages - ../shared/logs/errors.log: Only Errors and Warnings - -These files always contain the messages of the current run only. -The logs of previous runs are post-fixed with ``.1``, e.g. ``app.log.1``. This is useful for debugging issues during -shutdown of the service. - -The logs are also available via the Web Server: - -.. code-block:: - - http://ip.of.your.box/logs - -.. important:: Always check the time modification date or the beginning of the log - file to ensure you are not looking at an old log file! - -The long answer: A few more details ------------------------------------- - -If started without parameters, the Jukebox checks for the existence of ``../shared/settings/logger.yaml`` -and if present, uses that configuration for logging. This file is created by the installation process. -The default configuration file is also provided in ``../resources/default-settings/logger.default.yaml``. -We use Python's logging module to provide the debug messages which is configured through this file. - -**We are still in the Pre-Release phase which means full debug logging is enabled by default.** - -Default logging configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The default logging config does 2 things: - -1. It writes 2 log files: - -.. code-block:: bash - - ../shared/logs/app.log : Complete Debug Messages - ../shared/logs/errors.log : Only Errors and Warnings - -2. Prints logging messages to the console. If run as a service, only error messages are emitted to console to avoid spamming the system log files. - -Debug logging in console -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -For debugging, it is usually very helpful to observe the apps output directly -on the console log. - -.. code-block:: bash - - # Make sure the Jukebox service is stopped: - $ systemctl --user stop jukebox-daemon - - # Start the Jukebox in debug mode: - # with default logger: - $ ./run_jukebox.py - - # or with custom logger configuration: - $ ./run_jukebox.py --logger ../path/to/logger.yaml - -Fallback configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -It is possible to start the Jukebox with a catch-all debug enabler with a logger.yaml. -Attention: This only emits messages to the console and does not write to the log files! -This is more a fallback features: - -.. code-block:: bash - - $./run_jukebox.py -vv - -Extreme cases -^^^^^^^^^^^^^ - -Sometimes, the Jukebox app might crash with an exception and stack trace which is -neither logged, nor caught and handled. - -If run locally from your console, you will see it immediately. No worries! - -If running as a service, you will probably not even notice immediately that something has -gone pear-shaped. Services are restarted automatically when they fail. - -Things are just not behaving as expected? Time to check the system logs: - -.. code-block:: bash - - $journalctl --user -b -u jukebox-daemon diff --git a/docs/sphinx/primer.rst b/documentation/README.md similarity index 62% rename from docs/sphinx/primer.rst rename to documentation/README.md index 90de9f916..d71a57e0b 100644 --- a/docs/sphinx/primer.rst +++ b/documentation/README.md @@ -1,24 +1,32 @@ -What is this? -================================================ +# Welcome to RPi Jukebox RFID’s documentation! -The exciting, new **Version 3** of the RPi Jukebox RFID. A complete rewrite of the Jukebox code base. +The exciting, new Version 3 of the RPi Jukebox RFID. A complete rewrite of the Jukebox code base. -.. important:: This documentation applies to the Version 3 which is developed in the branches *future3/main* and *future3/develop*. - Currently the default Version is 2.X +> [!NOTE] +> This documentation applies to the Version 3 which is developed in the branches `future3/main` and `future3/develop`. Currently the default Version is 2.x To find out more about the RPi Jukebox RFID -project check out the `documentation of Version 2 `_ or `www.phoniebox.de `_. +project check out the [documentation of Version 2](https://github.com/MiczFlor/RPi-Jukebox-RFID) or [www.phoniebox.de](https://www.phoniebox.de/). -Why? ------ +## Quickstart + +* [Installing Phoniebox future3](./content/userguide/installation.md) +* [Update](./content/userguide/update.md) +* [Feature Status](status.md) +* [Known Issues](./content/developers/known-issues.md) +* [User Guide](./content/userguide/) +* [Developer Reference](developers) + +## future3 + +### Why? * Better extensibility, clear architecture allowing for easier integration of new features * Higher performance especially on lower end hardware (it's a stretch at the moment) * Better maintainability * Better observability for debugging -How? ------- +### How? * Jukebox core is a holistic Python3-only application * Avoid shell script invocation during runtime wherever possible @@ -27,8 +35,7 @@ How? * Implemented a plugin concept to dynamically load Python modules configurable through the configuration file * In conjunction with the RPC, this is a neat way of allowing additional features without having to touch the core all the time -Where are we? Help wanted! --------------------------- +### Where are we? Help wanted! The initial proof-of-concept phase has been left behind and there is quite some functionality available already. This is still an ongoing process but the WebUI and RFID-triggered playback of local files work. @@ -36,4 +43,3 @@ This is still an ongoing process but the WebUI and RFID-triggered playback of lo Features/files from version 2.X will only be copied/merged once they can be integrated and tested. If you don't find your v2.X contributions, it doesn't mean they are obsolete. Things will be integrated step by step. And, of course, you are welcome to adapt your previous contributions to this new exiting structure. - diff --git a/docs/calendars/2019-Phoniebox-Calendar.jpg b/documentation/content/calendars/2019-Phoniebox-Calendar.jpg similarity index 100% rename from docs/calendars/2019-Phoniebox-Calendar.jpg rename to documentation/content/calendars/2019-Phoniebox-Calendar.jpg diff --git a/docs/calendars/2020-Phoniebox-Calendar.jpg b/documentation/content/calendars/2020-Phoniebox-Calendar.jpg similarity index 100% rename from docs/calendars/2020-Phoniebox-Calendar.jpg rename to documentation/content/calendars/2020-Phoniebox-Calendar.jpg diff --git a/docs/calendars/2021-Phoniebox-Calendar.jpg b/documentation/content/calendars/2021-Phoniebox-Calendar.jpg similarity index 100% rename from docs/calendars/2021-Phoniebox-Calendar.jpg rename to documentation/content/calendars/2021-Phoniebox-Calendar.jpg diff --git a/documentation/content/developers/coreapps.md b/documentation/content/developers/coreapps.md new file mode 100644 index 000000000..fa6e7c99c --- /dev/null +++ b/documentation/content/developers/coreapps.md @@ -0,0 +1,56 @@ +# Jukebox Apps + +The Jukebox\'s core apps are located in `src/jukebox`. Run the following +command to learn more about each app and its parameters: + +``` bash +$ ./run_app_name.py -h +``` + +## Jukebox Core + +### `run_jukebox.py` + +This is the main app and starts the Jukebox Core. + +Usually this runs as a service, which is started automatically after boot-up. At times, it may be necessary to restart the service. For example after a configuration change. Not all configuration changes can be applied on-the-fly. See [Jukebox Configuration](../userguide/configuration.md#jukebox-configuration). + +For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. See [Troubleshooting](../userguide/troubleshooting.md). + +## Configuration Tools + +Before running the configuration tools, stop the Jukebox Core service. +See [Best practice procedure](../userguide/configuration.md#best-practice-procedure). + +### `run_configure_audio.py` + +Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. + +Will also setup equalizer and mono down mixer in the pulseaudio config file. + +Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../userguide/audio.md). + +### `run_register_rfid_reader.py` + +Setup tool to configure the RFID Readers. + +Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change the settings. For more information see [RFID Readers](../rfid/README.md). + +> [!NOTE] +> This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). Any manual modifications to the settings will have to be re-applied + +## Developer Tools + +### `run_rpc_tool.py` + +Command Line Interface to the Jukebox RPC Server + +A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging. + +The tool features auto-completion and command history. + +The list of available commands is fetched from the running Jukebox service. + +### `run_publicity_sniffer.py` + +A command line tool that monitors all messages being sent out from the Jukebox via the publishing interface. Received messages are printed in the console. Mainly used for debugging. diff --git a/documentation/content/developers/developer-issues.md b/documentation/content/developers/developer-issues.md new file mode 100644 index 000000000..c20cff5ec --- /dev/null +++ b/documentation/content/developers/developer-issues.md @@ -0,0 +1,73 @@ +# Developer Issues + +## Building the Webapp on the PI + +### JavaScript heap out of memory + +While (re-) building the Web App, you get the following output: + +``` {.bash emphasize-lines="12"} +pi@MusicPi:~/RPi-Jukebox-RFID/src/webapp $ npm run build + +> webapp@0.1.0 build +> react-scripts build + +Creating an optimized production build... + +[...] + +<--- JS stacktrace ---> + +FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory +``` + +**Reason** + +Not enough memory for Node + +**Solution** + +Prior to building set the node memory environment variable. + +1. Make sure the value is less than the total available space on the + system, or you may run into the next issue. (Not always though!) + Check memory availability with `free -mt`. +2. We also experience trouble, when the space is set too small a + value. 512 always works, 256 sometimes does, sometimes does not. + If your free memory is small, consider increasing the swap size of + your system! + +``` bash +export NODE_OPTIONS=--max-old-space-size=512 +npm run build +``` + +Alternatively, use the provided script, which sets the variable for you +(provided your swap size is large enough): + +``` bash +$ ./run_rebuild.sh +``` + +### Process exited too early // kill -9 + +``` {.bash emphasize-lines="8,9"} +pi@MusicPi:~/RPi-Jukebox-RFID/src/webapp $ npm run build + +> webapp@0.1.0 build +> react-scripts build + +... + +The build failed because the process exited too early. +This probably means the system ran out of memory or someone called 'kill -9' on the process. +``` + +**Reason** + +Node tried to allocate more memory than available on the system. + +**Solution** + +Adjust the node memory variable as described in [JavaScript heap out of memory](#javascript-heap-out-of-memory). But make sure to allocate less memory than the available memory. If that is not sufficient, increase the swap file size of your +system and try again. diff --git a/documentation/content/developers/development-environment.md b/documentation/content/developers/development-environment.md new file mode 100644 index 000000000..0b271ce38 --- /dev/null +++ b/documentation/content/developers/development-environment.md @@ -0,0 +1,74 @@ +# Development Environment + +You have 3 development options: + +## Directly on Raspberry Pi + +The full setup is running on the RPi and you access files via SSH. +Pretty easy to set up as you simply do a normal install and switch to +the `future3/develop` branch. + +### Steps to install + +We recommend to use at least a Pi 3 or Pi Zero 2 for development. This +hardware won\'t be needed in production, but it can be slow while +developing. + +1. Install the latest Pi OS on a SD card. +2. Boot up your Raspberry Pi. +3. [Install](../installation.md) the Jukebox software as if you were building a + Phoniebox. You can install from your own fork and feature branch if + you wish which can be changed later as well. The original repository + will be set as `upstream`. +4. Once the installation has successfully ran, reboot your Pi. +5. Due to some resource constraints, the Webapp does not build the + latest changes and instead consumes the latest official release. To + change that, you need to install NodeJS and build the Webapp + locally. +6. Install NodeJS using the existing installer + + ``` bash + cd ~/RPi-Jukebox-RFID/installation/routines; \ + source setup_jukebox_webapp.sh; \ + _jukebox_webapp_install_node + ``` + +7. To free up RAM, reboot your Pi. +8. Build the Webapp using the existing build command. If the build + fails, you might have forgotten to reboot. + + ``` bash + cd ~/RPi-Jukebox-RFID/src/webapp; \ + ./run_rebuild.sh -u + ``` + +9. The Webapp should now be updated. +10. To continuously update Webapp, pull the latest changes from your + repository and rerun the command above. + +## Locally on any Linux machine + +The jukebox also runs on any Linux machine. The Raspberry Pi specific +stuff will not work of course. That is no issue depending our your +development area. USB RFID Readers, however, will work. You may setup a +Python virtual environment or a conda virtual environment. You will have +to install and configure [MPD (Music Player +Daemon)](https://www.musicpd.org/). + +In addition to the `requirements.txt`, you will this +dependency. On the Raspberry PI, the latest stable release of ZMQ does +not support WebSockets. We need to compile the latest version from +Github, which is taken care of by the installation script. For regular +machines, the normal package can be installed: + +``` bash +pip3 install pyzmq +``` + +You will have to start Jukebox core application and the WebUI +separately. The MPD usually runs as a service. + +## Using Docker container + +There is a complete setup +`docker workflow `. diff --git a/documentation/content/developers/docker.md b/documentation/content/developers/docker.md new file mode 100644 index 000000000..f3b9c825f --- /dev/null +++ b/documentation/content/developers/docker.md @@ -0,0 +1,265 @@ +# Phoniebox Development Runbook for Docker environments + +This document describes how to set up a local development environment +with Docker. It is useful to develop certain parts of the Phoniebox +application that do not directly require the Raspberry Pi hardware such +as GPIO. *Raspberry Pi OS* is based on Debian but comes with a lot of +special packages and a unique graphical interface. It is difficult to +mock a Raspberry Pi whithin a Docker container but we try to keep both +environments as close as possible. The Docker environment is not meant +to be deployed on the Raspberry Pi directly for performance reasons. + +Depending on your host environment (Mac, Linux or Windows), you might +need to adapt some of those commands to your needs. + +## Prerequisites + +1. Install required software + * Linux + * [Docker](https://docs.docker.com/engine/install/debian/) + * [Compose](https://docs.docker.com/compose/install/) + * Mac + * [Docker & Compose (Mac)](https://docs.docker.com/docker-for-mac/install/) + * [pulseaudio (Docker)](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html) + * Windows + * [Docker & Compose (Windows)](https://docs.docker.com/docker-for-windows/install/) + * [pulseaudio (Windows)](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) + +2. Pull the Jukebox repository: + ``` + $ git clone https://github.com/MiczFlor/RPi-Jukebox-RFID.git + `````` + +3. Create a jukebox.yaml file + * Copy the `./resources/default-settings/jukebox.default.yaml` to `./shared/settings` and rename the file to `jukebox.yaml`. + ``` + $ cp ./resources/default-settings/jukebox.default.yaml ./shared/settings/jukebox.yaml + ``` + * Override/Merge the values from the following [Override file](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/docker/config/jukebox.overrides.yaml) in your `jukebox.yaml`. + * **\[Currently required\]** Update all relative paths (`../..`) in to `/home/pi/RPi-Jukebox-RFID`. + +4. Change directory into the `./RPi-Jukebox-RFID/shared/audiofolders` + and copy a set of MP3 files into this folder (for more fun when + testing). + +## Run development environment + +In contrary to how everything is set up on the Raspberry Pi, it\'s good +practice to isolate different components in different Docker images. +They can be run individually or in combination. To do that, we use +`docker-compose`. + +### Linux + +Make sure you don\'t use `sudo` to run your `docker-compose`. Check out +Docker\'s [post-installation guide](https://docs.docker.com/engine/install/linux-postinstall/] for more information. + +``` bash +// Build Images +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build + +// Run Docker Environment +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up + +// Shuts down Docker containers and Docker network +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down +``` + +Note: if you have `mpd` running on your system, you need to stop it +using: + +``` bash +$ sudo systemctl stop mpd.socket +$ sudo mpd --kill +``` + +Otherwise you might get the error message: + +``` bash +$ docker-compose -f docker-compose.yml -f docker-compose.linux.yml up +Starting mpd ... +Starting mpd ... error +(...) +Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already in use +``` + +Read these threads for details: [thread 1](https://unix.stackexchange.com/questions/456909/socket-already-in-use-but-is-not-listed-mpd) and [thread 2](https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num/5106755#5106755) + +### Mac + +Remember, pulseaudio is a prerequisite. [Follow these +instructions](https://stackoverflow.com/a/50939994/1062438) for Mac +hosts. + +``` bash +// Build Images +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build + +// Run Docker Environment +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml up + +// Shuts down Docker containers and Docker network +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml down +``` + +### Windows + +1. Download + [pulseaudio](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) + +2. Uncompress somewhere in your user folder + +3. Edit `$INSTALL_DIR/etc/pulse/default.pa` + +4. Add the following line + + ``` bash + load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 + ``` + +5. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the + following line and change it to: + + ``` bash + exit-idle-time = -1 + ``` + +6. Execute `$INSTALL_DIR/bin/pulseaudio.exe` + +7. Run `cocker-compose` + + ``` bash + // Build Images + $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml build + + // Run Docker Environment + $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml up + + // Shuts down Docker containers and Docker network + $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml down + ``` + +## Test & Develop + +The Dockerfile is defined to start all Phoniebox related services. + +Open in your browser to see the web application. + +While the `webapp` container does not require a reload while working on +it (hot-reload is enabled), you will have to restart your `jukebox` +container whenever you make a change (in the Python code). Instead of +stopping and starting the `docker-compose` command, you can individually +restart your `jukebox` container. Update the below path with your +specific host environment. + +``` bash +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.[ENVIRONMENT].yml restart jukebox +``` + +## Known issues + +The docker environment only exists to make development easier and +possible without a physical device. It won\'t replace it though. +Therefore, we currently accept certain issues related to the individual +Docker containers. Here is a list of known errors or weird behaviour +which you can easily ignore unless they prevent you from progressing. If +would be of course useful to get rid of them, but currently we make a +trade-off between a development environment and solving the specific +details. + +### `mpd` container + +When starting the `mpd` container, you will see the following errors. +You can ignore them, MPD will run. + +``` bash +mpd | exception: bind to '0.0.0.0:6600' failed (continuing anyway, because binding to '[::]:6600' succeeded): Failed to bind socket: Address already in use +mpd | exception: Failed to open '/root/.config/mpd/database': No such file or directory +mpd | exception: RTIOThread could not get realtime scheduling, continuing anyway: sched_setscheduler failed: Operation not permitted +mpd | avahi: Failed to create client: Daemon not running +``` + +You might also notice the following errors after the `mpd` Docker ran +for a while. Specifically the first error could fill up your console, +sometimes it stops with the second error message. It\'s not a problem, +sound continues to work. As a side effect, your CPU usage increases. +Just kill the process and restart. + +``` bash +mpd | alsa_mixer: snd_mixer_handle_events() failed: Input/output error +mpd | exception: Failed to read mixer for 'My ALSA Device': snd_mixer_handle_events() failed: Input/output error +``` + +### `jukebox` container + +Many features of the Phoniebox are based on the Raspberry Pi hardware. +This hardware can\'t be mocked in a virtual Docker environment. As a +result, a few plugins like RFID, GPIO or CPU temperature will throw +errors because they can\'t start successfully. Unless you want to +develop such plugins, you will be able to ignore these errors. The +plugin system is built in a way that the Jukebox daemon will come up. If +you want to develop plugins that require hardware support, you will have +to work on the hardware directly. + +Typical errors and following exceptions to be ignored in the Docker +`jukebox` container are: + +``` bash +jukebox | 634:plugs.py - jb.plugin - MainThread - ERROR - Ignoring failed package load finalizer: 'rfid.finalize()' +jukebox | 635:plugs.py - jb.plugin - MainThread - ERROR - Reason: FileNotFoundError: [Errno 2] No such file or directory: '/home/pi/RPi-Jukebox-RFID/shared/settings/rfid.yaml' +... +jukebox | 171:__init__.py - jb.host.lnx - MainThread - ERROR - Error reading temperature. Canceling temperature publisher. FileNotFoundError: [Errno 2] No such file or directory: '/sys/class/thermal/thermal_zone0/temp' +... +jukebox | 319:server.py - jb.pub.server - host.timer.cputemp - ERROR - Publish command from different thread 'host.timer.cputemp' than publisher was created from 'MainThread'! +``` + +## Appendix + +### Individual Docker Image + +Run an individual Docker container, e.g. `jukebox`. Similarly you could +run `mpd` or `webapp`. + +The following command can be run on a Mac. + +``` bash +$ docker build -f docker/jukebox.Dockerfile -t jukebox . +$ docker run -it --rm \ + -v $(PWD)/src/jukebox:/home/pi/RPi-Jukebox-RFID/src/jukebox \ + -v $(PWD)/shared/audiofolders:/home/pi/RPi-Jukebox-RFID/shared/audiofolders \ + -v ~/.config/pulse:/root/.config/pulse \ + -v /usr/local/Cellar/pulseaudio/14.2/etc/pulse/:/etc/pulse \ + -e PULSE_SERVER=tcp:host.docker.internal:4713 \ + --name jukebox jukebox +``` + +### Resources + +**Mac** + +- +- +- + +**Windows** + +- +- +- +- + +**Audio** + +- +- +- + +**MPD** + +- +- +- + +**ZMQ** + +- diff --git a/documentation/content/developers/known-issues.md b/documentation/content/developers/known-issues.md new file mode 100644 index 000000000..598ecd791 --- /dev/null +++ b/documentation/content/developers/known-issues.md @@ -0,0 +1,13 @@ +# Known Issues + +## Browsers + +The Web UI will **not** work with Firefox, due to an issue with websockets and pyzmq. Please use a different +browser for now. + +## Configuration + +In `jukebox.yaml` (and all other config files): do not use relative paths with `~/some/dir`. +Always use entire explicit path, e.g. `/home/pi/some/dir`. + +**Sole** exception is in `playermpd.mpd_conf`. diff --git a/documentation/content/developers/rfid/README.md b/documentation/content/developers/rfid/README.md new file mode 100644 index 000000000..0717ef661 --- /dev/null +++ b/documentation/content/developers/rfid/README.md @@ -0,0 +1,12 @@ +# RFID Readers + +* [Basics](basics.md) +* [Reader Types](basics.md#reader-types) +* [Reader Configuration](basics.md#reader-configuration) +* Readers + * [Generic USB Reader](genericusb.md) + * [RDM6300 Reader](rdm63000.md) + * [MFRC522 SPI Reader](mfrc522_spi.md) + * [PN532 I2C Reader](pn532_i2c.md) + * [Mock Reader](mock_reader.md) + * [Template Reader](template_reader.md) \ No newline at end of file diff --git a/documentation/content/developers/rfid/basics.md b/documentation/content/developers/rfid/basics.md new file mode 100644 index 000000000..f1e1bc499 --- /dev/null +++ b/documentation/content/developers/rfid/basics.md @@ -0,0 +1,93 @@ +# RFID + +## Basics + +Cards placed on the reader trigger an action. An action may be any +callable plugin function through the RPC with any arguments. Typically, +this would be "play some folder", but can also be "activate shutdown +timer", or "increase volume". This is configured in the +[Card Database](../userguide/card-database.md). + +You may configure a single or even multiple parallel readers (of +different or identical types). + +Successive card swipes are suppressed to avoid bouncing effects. This +behavior can be deactivated for individual cards. + +## Reader Types + +#### place-capable: + +Some readers give a single event signal when the card is placed on +the reader. This is sufficient to build a fully-featured Jukebox. +Other readers give a continuous signal. They allow both card +placements and card removals. This can be used to play the Jukebox +when a card is placed and to pause it when it's removed. + +Generally, **not** all [USB-based RFID readers](genericusb.md) are place-capable. + +The known place-capable readers are [RDM6300 Reader](rdm6300:RDM6300), [MFRC522 SPI Reader](mfrc522_spi:MFRC522) or [PN532 I2C Reader](pn532_i2c:PN532). + +#### Frequency: + +Readers operate on one of two different frequencies: 125kHz or 13.56 MHz. Make sure to buy compatible cards, RFID stickers or key fobs working with the same frequency as the reader. + +## Reader Configuration + +During the installation process, you can already configure a RFID +reader. To manually configure RFID reader(s), +[please run the tool](../developer/coreapps.md#run_register_rfid_reader.py), (`src/jukebox/run_register_rfid_reader.py`). + +It will generate a reader configuration file at +`shared/settings/rfid.yaml`. You can re-run the tool to change the +settings any time. + +Some options are not covered by the tool. You may change the file +manually. + +``` yaml +rfid: + readers: + read_00: + module: fake_reader_gui + config: .... + same_id_delay: float|integer + log_ignored_cards: true|false + place_not_swipe: + enabled: true|false + card_removal_action: + alias: pause +``` + +For each reader, there is an entry `read_XX`. + +#### module: + +Indicates the Python package used for this reader. Filled by the RFID configuration tool. + +#### config: + +Filled by the [RFID configuration tool](../developer/coreapps.md#run_register_rfid_reader.py) (`src/jukebox/run_register_rfid_reader.py`) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. + +#### same_id_delay: float \| integer + +Minimum delay in seconds between 2 card detections before triggering a new action. This is to prevent double triggering or bouncing. + +#### place_not_swipe: true \| false + +For place-capable RFID readers enable dual action mode: a start action (e.g. playing) on card placement and card removal action (e.g. pause). + +#### card_removal_action: Dictionary + +Executes the given function on card removal. Only relevant if place_not_swipe is true. The action is identical for all cards read on that reader. The removal-action can be set to ignored on a card-by-card basis. More on card action configurations in [RPC Commands](../userguide/rpc-commands.md). + +> [!NOTE] +> Developer's note: The reason for a unique removal action for all cards is that card triggering and card removal are happening in two separate threads. Removal needs to be in a time-out thread. Thus, we would need to transport information from one thread to another. This can be done of course but is not implemented (yet). Ignoring card removal is much easier and works for now. + +#### log_ignored_cards: true \| false + +Log all cards that are ignored due to same_id_delay. This is a option for developers. Don't use it unless you need it for debugging as it has the potential to spam your log files. + +#### Second Swipe + +Looking for 'Second Swipe' option? That is part of the Player configuration and not part of the RFID configuration, as the 'Second Swipe' action needs to take into account the player state, which can also be altered through the WebUI. diff --git a/documentation/content/developers/rfid/genericusb.md b/documentation/content/developers/rfid/genericusb.md new file mode 100644 index 000000000..2f049291d --- /dev/null +++ b/documentation/content/developers/rfid/genericusb.md @@ -0,0 +1,14 @@ +# Generic USB Reader + +**place-capable**: typically no + +This module covers all types of USB-based RFID input readers. If you +plan to connect multiple USB-based RFID readers to the Jukebox, make +sure to connect all of them before running the registration tool [run_register_rfid_reader.py](../developers/coreapps.md). + +> [!NOTE] +> The user needs to be part of the group \'input\' for evdev to work. This should usually be the case. However, a user can be added with: +> +>``` bash +>sudo usermod -a -G input USER +>``` diff --git a/documentation/content/developers/rfid/mfrc522_spi.md b/documentation/content/developers/rfid/mfrc522_spi.md new file mode 100644 index 000000000..726136d61 --- /dev/null +++ b/documentation/content/developers/rfid/mfrc522_spi.md @@ -0,0 +1,81 @@ +# MFRC522 SPI Reader + +RC522 RFID reader via SPI connection. + +**place-capable**: yes + +## Installation + +Run the ref:[run_rfid_configuration]{.title-ref} tool for guided +installation. + +## Options + +In principle Raspberry PIs support multiple SPI interfaces. The reader +class is based on pi-rc522 which uses spidev. This allows to use +different SPI bus configurations. The below parameters regarding pin-out +are just routed through to spidev. Have a look at the spidev +documentation for details if you really want to use a different SPI bus. +The default setup makes most sense for almost everyone. + +#### spi_bus *(default=0)* + +The SPI Bus ID. The default bus is 0. For other bus IDs, the RPi also needs to re-configured. For that reason we set this to zero. + +#### spi_ce *(default=0)* + +SPI chip enable pin. On default SPI bus 0, this can be + +- 0 = GPIO8 (Pin 24) +- 1 = GPIO7 (Pin 26) + +For other SPI buses refer to RPi documentation. + +#### pin_irq + +Mandatory IRQ pin. This can be any GPIO pin. + +#### pin_rst *(default=0)* + +Reset pin for hardware reset. This is an optional pin. If not used, + +- hardware reset will only be performed by power-on-reset. This has been tested on works fine. +- you **must** tie the reset pin of the MFRC522 board **high**! + +#### mode_legacy *(default=false)* + +4-byte-only legacy mode: previously the pirc522 library could only read the lower 4 bytes of a card UID. It can now read 4-byte and full 7-byte UIDs. Legacy mode turns back to the old behaviour. This only makes sense, if you already have an large RFID collection and do not want to re-assign every card. + +#### antenna_gain *(default=4)* + +Antenna gain factor of the RFID reader chip on the MFRC522 board. + +#### log_all_cards *(default=false)* + +If true all card read-outs will be logged, even when card is permanently on reader. Only for debugging. + +## Board Connections + +The following pin-out is for the default SPI Bus 0 on Raspberry Pins. + +*MFRC522 default wiring (spi_bus=0, spi_ce=0)* +|Pin Board Name |Function |RPI GPIO |RPI Pin | +|----------------|----------|----------|---------| +|SDA |CE |GPIO8 |24 | +|SCK |SCLK |GPIO11 |23 | +|MOSI |MOSI |GPIO10 |19 | +|MISO |MISO |GPIO9 |21 | +|IRQ GND |IRQ |GPIO24 |18 | +|RST 3.3V |RST |GPIO25 |22 | + + +Some RC522 boards use reversed labeling for MOSI and MISO pins. The good +thing is, no harm is done to the card reader when incorrectly connected. +In case no cards are read, try swapping the connections for MOSI and +MISO. + +## Hardware + +MFRC522 boards can be picked up from many places for little money. + +Good quality ones can be found e.g. here diff --git a/documentation/content/developers/rfid/mock_reader.md b/documentation/content/developers/rfid/mock_reader.md new file mode 100644 index 000000000..da90d5405 --- /dev/null +++ b/documentation/content/developers/rfid/mock_reader.md @@ -0,0 +1,26 @@ +# Mock Reader + +A fake reader using a TK GUI for development purposes. This target +software development which does not happen on the RPi but on another +machine - probably in a Python virtual environment. + +**place-capable**: yes + +If you [mock the GPIO pins](../userguide/gpio.md), this GUI will show the GPIO devices. + +![image](mock_reader.png) + +> [!NOTE] +> When using Anaconda, the GUI will look horrible! That's because Anaconda's TK is compiled without FreeType support. +> +> There is a very quick and very dirty [fix](https://stackoverflow.com/questions/47769187/make-anacondas-tkinter-aware-of-system-fonts-or-install-new-fonts-for-anaconda). +> Replacing the TK lib in Anaconda's environment with the system `libtk`. +> However, this depends on an exact version match of the `libtk`. +> +>``` bash +>cd /path/to/anaconda3/envs/rpi/lib +>mv ./libtk8.6.so ./libtk8.6.so.bak +>ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so libtk8.6.so +>``` +> +>An alternative is to simply use Python's `venv` module to create a virtual environment. This uses your systems Python version and thus will work with your system `libtk`. It might not be the RPi's Python version - but that should not matter in most cases. diff --git a/docs/sphinx/rfid/mock_reader.png b/documentation/content/developers/rfid/mock_reader.png similarity index 100% rename from docs/sphinx/rfid/mock_reader.png rename to documentation/content/developers/rfid/mock_reader.png diff --git a/documentation/content/developers/rfid/pn532_i2c.md b/documentation/content/developers/rfid/pn532_i2c.md new file mode 100644 index 000000000..819f051f3 --- /dev/null +++ b/documentation/content/developers/rfid/pn532_i2c.md @@ -0,0 +1,53 @@ +# PN532 I2C Reader + +The PN532-based readers connected via I2C + +This reader module is based on the [py532lib +library](https://github.com/HubCityLabs/py532lib) and uses the I2C bus. +It utilizes a polling mechanism. It only needs 2 pins, but has a certain +performance overhead due to polling. + +## Options + +There are no configurable options for this module. + +## Hardware + +This reader module has been tested with the excellent and highly +recommendable [Adafruit PN532 breakout +board](https://www.adafruit.com/product/364). It also comes with [heaps +of documentation and even +schematics](https://learn.adafruit.com/adafruit-pn532-rfid-nfc/downloads). + +I have used various RFID boards and this one has an excellent reading +range and no problem with various types of cards and stickers. + +You can usually pick up a board at + +> - +> - + +## Board Connections + +*Default wiring* + +| PN532 | RPI GPIO | RPI Pin | +|-------|--------------|---------| +| 5V | 5V | > 4 | +| GND | GND | > 6 | +| SDA | GPIO 2 (SDA) | > 3 | +| SCL | GPIO 3 (SCL) | > 5 | + +Other pins from the Adafruit board need not be connected. Especially do +**not** connect the 3.3V pin, if connecting the 5.0V pin. It is +recommended to use the 5.0V because that does not draw power from the +PI's own voltage regulator. + +## Jumpers + +*Jumper settings for I2C protocol* + +Jumper | Position +-------|---------- +SEL0 | ON +SEL1 | OFF diff --git a/documentation/content/developers/rfid/rdm6300.md b/documentation/content/developers/rfid/rdm6300.md new file mode 100644 index 000000000..6cc47e563 --- /dev/null +++ b/documentation/content/developers/rfid/rdm6300.md @@ -0,0 +1,19 @@ +# RDM6300 Reader + +The RDM6300 / RDM630 connected via serial UART port + +**place-capable**: yes + +## Options + +Number Format + +## Board Connections + +The voltage level of the RX/TX is 3.3V despite the wide-spread belief +that it is 5V. (At least on the boards I have). I did check with an +oscilloscope. And one can easily identify the AMS1117 LDO voltage +regulator on the backside of the board. + +No warranties: If you meddle with GPIOs you are old enough to know the +risks of wrong voltage levels. diff --git a/documentation/content/developers/rfid/template_reader.md b/documentation/content/developers/rfid/template_reader.md new file mode 100644 index 000000000..77f3008c2 --- /dev/null +++ b/documentation/content/developers/rfid/template_reader.md @@ -0,0 +1,41 @@ +# Template Reader + +*Template for creating and integrating a new RFID Reader* + +> [!NOTE] +> For developers only + +This template provides the skeleton API for a new Reader. If you follow +the conventions outlined below, your new reader will be picked up +automatically There is no extra need to register the reader module with +the Phoniebox. Just re-run `the reader config tool `. + +Follow the instructions in [template_new_reader.py] + +Also have a look at the other reader subpackages to see how stuff works +with an example + +## File structure + +Your new reader is a python subpackage with these three mandatory files + +``` bash +components/rfid/hardware/awesome_reader/ + +- awesome_reader.py <-- The actual reader module + +- description.py <-- A description module w/o dependencies. Do not change the filename! + +- README.rst <-- The Readme +``` + +The module documentation must go into a separate file, called README.ME. + +## Conventions + +- Single reader per directory / subpackage +- reader module directory name and reader module file name must be + identical +- Obviously awesome_reader will be replaced with something more + descriptive. The naming scheme for the subpackage is + - \\_\\_\ + - e.g. generic_usb/generic_usb.py + - e.g. pn532_spi/pn532_spi.py + - ... diff --git a/documentation/content/developers/to-be-deleted-status.md b/documentation/content/developers/to-be-deleted-status.md new file mode 100644 index 000000000..68bbe6864 --- /dev/null +++ b/documentation/content/developers/to-be-deleted-status.md @@ -0,0 +1,244 @@ +## Feature Status + +**This is where we are in a nutshell:** Playing music from local folders via RFID trigger. We also built a new WebUI to control the Jukebox from a browser. + +There are a few things that are specifically not integrated yet: playing streams, podcasts, or Spotify. + +In the following is the currently implemented feature list in more detail. It also shows some of the shortcomings. However, the list is _not complete in terms of planned features_, but probably _reflects more of where work is currently being put into_. + +**For new contributors:** If you want to port a feature from version 2.X or implement a new feature, contact us. Open an issue or join us in the chat room. You may pick topics marked as open below, but also any other topic missing in the list below. As mentioned, that list is not complete in terms of open features. Check the [Contribution guide](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/CONTRIBUTING.md). + +Topics marked _in progress_ are already in the process of implementation by community members. + +## Table of Contents + +- [Jukebox Core App](#jukebox-core-app) + - [Base](#base) + - [Via RPC](#via-rpc) + - [Config handler](#config-handler) + - [ZMQ Publisher](#zmq-publisher) + - [Playback](#playback) + - [MPD Player](#mpd-player) + - [RFID](#rfid) + - [Cards](#cards) + - [Timer](#timer) + - [Volume](#volume) + - [GPIO](#gpio) + - [WLAN](#wlan) + - [Spotify](#spotify) + - [Others](#others) + - [Start-up stuff](#start-up-stuff) +- [Debug Tools](#debug-tools) +- [WebUI](#webui) +- [Installation Procedure](#installation-procedure) +- [Documentation](#documentation) + +## Jukebox Core App + +### Base + +- [x] Clean up surplus files +- [x] Host interface (shutdown, reboot) +- [x] Temperature getter + - [x] Timer + Publisher +- [x] RPi is_throttled getter + - [x] Decode hex value to readable string (check version 2.x mqtt as reference?) + - [x] Timer + Publisher +- [x] Git hash log information + - [x] Log and publish this! +- [x] Version number getter (Version number should be stored in a python file) + - [x] Log and publish this +- [x] Exit via RPC +- [x] Service restart via RPC + - [x] Check if really running as a service +- [x] Storage space getter / publisher (shutil.disk_usage) +- [x] Getter for error logs to show in WebUI + - Get file location from FileHandlers (files may be stale!) + - Logger might be disabled or not connected +- [ ] Enable/Disable debug logging from RPC +- [x] Publisher of errors (specialized logger handler) + - This is a configurable logger handler in logger.yaml +- [x] Basic Logging Config should enable Publisher stream handler +- [ ] Disable Console Stream Handler (or set to warning) when running as a service +- [x] Log & publish start time +- [ ] Method to change configuration through WebUI + - The difficulty lies in bringing the running Jukebox to accept the changes. There probably won't be a catch-all solution but rather a custom implementation for a select few features +- [x] Strategy to post config changes via PubSub: Must be taken care of by the setter function modifying the property + +### Via RPC + +- [x] List of loaded / failed plugins +- [x] card action reference +- [x] Help command (available commands) + - which basically is a plugin reference +- [x] Simplified alias definitions for often used RPC commands (for RFID, GPIO, etc) + - [ ] Port all previous commands + - [x] Reference file write-out: now also included in Sphinx documentation + - [ ] Export available alias definitions to RPC + - [ ] Base quick select on yaml file (_in progress_) + - or write a yaml file as an artifact that contains all the meta information about the functions as well? + - or include a `get_signature` function that returns the meta information for a given alias + +### Config handler + +- [x] While saving config to disk: local file change detection +- [x] cfghandler creates setndefault() at an arbitrary depth + +### ZMQ Publisher + +- [x] Last Value Cache +- [x] Subscriber detection and initial status update +- [x] Port configuration option (WS and/or TCP) +- [ ] Callback registration option for plugin on topic send + - How to interact with threads? + +### Playback + +- [x] Playlist generator + - [x] Local folders + - [x] Non-recursive folder play + - [x] Recursive folder play + - [x] Podcast + - [x] Livestreams + - [x] NEW: Playback of m3u playlists (e.g., folder.m3u) ? + +- [ ] Folder configuration (_in progress_) + - [ ] [Reference](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/MANUAL#manage-playout-behaviour) + - [ ] Resume: Save and restore position (how interact with shuffle?) + - [ ] Single: Enable mpc single + - [ ] Shuffle: Enable mpc random (not shuffle) + - Rename to random, as this is mpc random + - [ ] Loop: Loop playlist + +### MPD Player + +- [ ] Thread safety for status information / configuration (_in progress_) +- [ ] Differential status post (_in progress_) +- [ ] Second swipe option setter via RPC (_in progress_) +- [ ] Before every music lib update, player should check user rights (not only after start-up) + +### RFID + +- [x] Test with Reader disabled +- [x] Start-up behavior with un-configured Reader +- [x] Command card -> is now parameter ignore_same_id_delay +- [x] Revised RFID reader user-query setup script + - [ ] Ask for place option +- [ ] Enable config flag ? +- [x] Place not swipe / Timer thread + - [x] Configurable card removal action +- [x] Readers support + - [x] USB (e.g., Neuftech) + - [x] RDM6300 + - [x] MFRC522 + - [x] RC532 + - [x] Multi-reader support + - [x] GUI Fake Reader for Development + - [ ] PC/SC Cards (what actually is this?) +- [x] Publish RFID Card ID via PubSub + - Needs to be thread safe +- [x] Card reference IF via RPC +- [x] Second Swipe Options -> must be part of player control (partially broken at the moment) + - Freely configurable with an RPC call + - Ignore (nothing) + - Toggle Pause/Play + - Skip to the next track + - Re-start playlist + +### Cards + +- [ ] Write a simplified card summary to + - [ ] file + - [x] RPC +- [ ] Card assignment function for WebUI + - [x] Via RPC command alias definitions + - [ ] Full custom RPC call +- [x] Remove card + +### Timer + +- [x] Shutdown timer +- [x] Play stop timer +- [x] Shutdown timer volume reduction + - Decreases volume every x min until zero, then shuts down + - Needs to be cancelable +- [x] Publish mechanism of timer status +- [x] Change multitimer function call interface such that endless timer etc. won't pass the `iteration` kwarg +- [ ] Make timer settings persistent +- [ ] Idle timer + - This needs clearer specification: Idle is when no music is playing and no user interaction is taking place + - i.e., needs information from RPC AND from player status. Let's do this when we see a little clearer about Spotify + +### Volume + +- [x] Jingle playback volume as a fixed value in config +- [x] Default volume setting after boot-up +- [x] Max Volume +- [x] PulseAudio integration with event handler +- [x] Bluetooth support +- [x] Automatic audio sink toggle + - [ ] Callbacks for audio sink change + +### GPIO + +- [x] All done! Read the docs at [GPIO Recipes](#userguide/gpioz:GPIO Recipes)! +- [ ] USB Buttons: It's a different category as it works similar to the RFID cards (in progress) + +### WLAN + +- [x] Ad-hoc WLAN Hotspot +- [x] IP address read-out + +### Spotify + +- [ ] Everything + +### Others + +- [ ] MQTT +- [ ] Record and Playback using a Mic +- [ ] Dot Matrix Displays + +### Start-up stuff + +- [x] check music folder permissions +- [x] mpc update / (mpc rescan) +- [x] sudo iwconfig wlan0 power off (need to be done after every restart) +- [x] Optional power down HDMI circuits: /usr/bin/tvservice -o + +## Debug Tools + +- [x] Publishing Sniffer + - [ ] Update mode vs. linear mode? +- [x] RPC command line client + - [x] with tab-completion and history + +## WebUI + +- [x] Playback Control +- [x] Cover Art +- [x] Register cards / Delete cards +- [x] Shutdown button +- [ ] Settings configuration page +- [ ] System information page + - [ ] Configure (one or multiple) WLANs + - [x] Enable/Disable Auto-Hotspot +- [x] `run_npm_build` script + - [x] Must consider `export NODE_OPTIONS=--max-old-space-size=512` + +## Installation Procedure + +- [x] Single call installation script +- [x] Query for settings vs. automatic version +- [x] IPQoS in SSH config +- [x] Separate static IP and IPv6 disable +- [ ] For all system config file changes, check prior to modification if modification already exists + +## Documentation + +- [x] Sphinx / Restructured Text tool flow +- [ ] What is the Phoniebox +- [x] Artifacts: Generate artifacts (on command line switch only) for + - [x] loaded plugins and rpc command aliases (to sphinx and shared/artifacts) + - [x] rpc command aliases (to sphinx and shared/artifacts) +- [ ] How to: Write a plugin diff --git a/documentation/content/userguide/audio.md b/documentation/content/userguide/audio.md new file mode 100644 index 000000000..fb3271197 --- /dev/null +++ b/documentation/content/userguide/audio.md @@ -0,0 +1,141 @@ +# Audio + +## Configuration + +The Jukebox supports 2 audio outputs, primary and secondary. The **primary output** is the default output and must +be available after system boot. This will typically be your sound card or the Pi's built-in headphone output. + +The **secondary output** is an optional alternative output where the audio stream can be routed to. +Stream transfer happens on user input or automatically on the connection of an audio device. +This is mainly targeted at Bluetooth Headsets/Speakers. + +Audio outputs run via PulseAudio and the basic configuration should be easy. +There is a [configuration tool](..developers/coreapps#run_configure_audio.py), +to setup the configuration for the Jukebox Core App. + +To set up the audio + +1. Follow the setup steps according to your sound card +2. Check that the sound output works [as described below](audio.md#checking-system-sound-output) +3. Run the the tool [run_configure_audio](developers/coreapps.md#run_configure_audio.py) +4. [Fine-tune audio parameters](audio.md#additional-options) + +## Checking system sound output + +Run the following steps in a console: + +```bash +# Check available PulseAudio sinks +$ pactl list sinks short +0 alsa_output.platform-soc_sound.stereo-fallback module-alsa-card.c s16le 2ch 48000Hz +1 bluez_sink.C4_FB_20_63_CO_FE.a2dp_sink module-bluez5-device.c s16le 2ch 44100Hz + +# Set the default sink (this will be reset at reboot) +$ pactl set-default-sink sink_name + +# Check default sink is correctly set +$ pactl info +.... +# Check volume level (exit with ESC) +$ alsamixer + +# Play a sound +$ paplay /usr/share/sounds/alsa/Front_Center.wav + +# This must also work when using an ALSA device +$ aplay /usr/share/sounds/alsa/Front_Center.wav +``` + +You can also try different PulseAudio sinks without setting the default sink. In this case the volume is the last used +volume level for this sink: + +```bash +$ paplay -d sink_name /usr/share/sounds/alsa/Front_Center.wav +``` + +# Bluetooth + +Bluetooth setup consists of three steps + +1. Pair and connect your Bluetooth device +2. Check the output works +3. Re-run the config tool + +To pair and connect, follow these steps. This will be a one-time setup. + +```bash +$ bluetoothctl +Agent registered +[CHG] Controller B8:27:EB:44:C4:33 Pairable: yes +#### Put your headset into pairing mode +[bluetooth]# scan on +Discovery started +#### Wait a few seconds for your device to appear +.... +[NEW] Device C4:FB:20:63:CO:FE PowerLocus Buddy +.... +[bluetooth]# scan off +.... +[bluetooth]# pair C4:FB:20:63:CO:FE +.... +Pairing successful +.... +[bluetooth]# trust C4:FB:20:63:CO:FE +.... +[bluetooth]# connect C4:FB:20:63:CO:FE +.... +[PowerLocus Buddy]# exit +``` + +Wait for a few seconds and then with `$ pactl list sinks short`, check wether the Bluetooth device shows up as an output. +Its name usually looks like this: `bluez_sink.C4_FB_20_63_CO_FE.a2dp_sink`. + +Run through steps above to check wether the output is working or not. +If it does not work immediately, turn your headset off and on to force a reconnect. + +Rerun the config tool to register the Bluetooth device with the Jukebox core app as its secondary audio output. + +## Additional options + +For other audio configuration options, please look at the `jukebox.yaml` for now. + +Directly edit `jukebox.yaml` following the steps: [Best practice procedure](.md#best-practice-procedure). + + +## Developer Information + +The optional processing stages *Equalizer* and *Mono down mix* are realized by PulseAudio plugins. The processing chain is +``` +player --> mono mix --> equalizer --> hardware sink +``` + +Both plugins (if enabled) appear in the PulseAudio sinks + +```bash +$ pactl list sinks short +``` + +Which means we can put any of these as sink into the jukebox configuration file (if there is any need). + +Mono down mix is enabled by the module `module-remap-sink` +for which documentation and an example can be found [here](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-remap-sink>). + +The equalizer is the PulseAudio module `module-ladspa-sink` with the [corresponding documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-ladspa-sink>). + +This in turn loads a [LADSPA plugin](https://www.ladspa.org/). +The LADSPA plugin in the `Eq10X2` plugin of the [CAPS Library](http://quitte.de/dsp/caps.html#Eq10>). The CAPS library is available as linux package `caps`. + +This is the same plugin which is used in the +[equalizer for pure ALSA](https://github.com/raedwulf/alsaequal>) +configurations which is part of the linux package `libasound2-plugin-equal`. + +You are, of course, free to modify the PulseAudio configuration to your needs. References + +1. [PulseAudio Documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User>) +2. [PulseAudio Examples](https://wiki.archlinux.org/title/PulseAudio/Examples>) + +In this case, run the configuration tool with below parameter to avoid touching the PulseAudio configuration file. + +```bash +$ ./run_configure_audio.py --ro_pulse +``` diff --git a/documentation/content/userguide/autohotspot.md b/documentation/content/userguide/autohotspot.md new file mode 100644 index 000000000..a283a94f2 --- /dev/null +++ b/documentation/content/userguide/autohotspot.md @@ -0,0 +1,107 @@ +# Auto-Hotspot + +The Auto-Hotspot function allows the Jukebox to switch between its +connection between a known WiFi and an automatically generated hotspot +so that you can still access via SSH or Webapp. + +> [!IMPORTANT] +> Please configure the WiFi connection to your home access point before enabling these feature! + +To create a hotspot and allow clients to connect +[hostapd](http://w1.fi/hostapd/) and [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html). + +## How to connect + +When the Jukebox is not able to connect to a known WiFi it will create a +hotspot named `Phoniebox_Hotspot`. You will be able to connect to this +hotspot using the given password in the installation or the default +password: `PlayItLoud!` + +### Webapp + +After connecting to the `Phoniebox_Hotspot` you are able to connect to +the webapp accessing the website [10.0.0.5](http://10.0.0.5/). + +### ssh + +After connecting to the `Phoniebox_Hotspot` you are able to connect via +ssh to your Jukebox + +``` bash +ssh pi@10.0.0.5 +``` + +## Changing basic configuration of the hotspot + +The whole hotspot configuration can be found at +`/etc/hostapd/hostapd.conf`. + +The following parameters are relevant: + +- `ssid` for the displayed hotspot name +- `wpa_passphrase` for the password of the hotspot +- `country_code` the country you are currently in + +``` bash +$ cat /etc/hostapd/hostapd.conf + +#2.4GHz setup wifi 80211 b,g,n +interface=wlan0 +driver=nl80211 +ssid=Phoniebox_Hotspot +hw_mode=g +channel=8 +wmm_enabled=0 +macaddr_acl=0 +auth_algs=1 +ignore_broadcast_ssid=0 +wpa=2 +wpa_passphrase==PlayItLoud! +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP TKIP +rsn_pairwise=CCMP + +#80211n - Change GB to your WiFi country code +country_code=DE +ieee80211n=1 +ieee80211d=1 +``` + +## Disabling automatism + +Auto-Hotspot can be enabled or disabled using the Webapp. + +> [!IMPORTANT] +> Disabling or enabling will keep the last state. + +## Troubleshooting + +### Phoniebox is not connecting to the known WiFi + +The script will fall back to the hotspot so you still have some type of +connection. + +Check your password in `/etc/wpa_supplicant/wpa_supplicant.conf`. + +### AutoHotspot functionality is not working + +You can check the output of the script by running the following script: + +``` bash +$ sudo /usr/bin/autohotspot +``` + +### You need to add a new wifi network to the Raspberry Pi + +Because it is in Auto-Hotspot mode, you won\'t be able to scan for new +wifi signals. + +You will need to add a new network to +`/etc/wpa_supplicant/wpa_supplicant.conf` manually. Enter the following +details replacing mySSID and myPassword with your details. If your WiFi +has a hidden SSID then include the line `scan_ssid=1`. + +## Resources + +[Raspberry Pi - Auto WiFi Hotspot Switch - Direct +Connection](https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection) diff --git a/documentation/content/userguide/bluetooth-audio-buttons.md b/documentation/content/userguide/bluetooth-audio-buttons.md new file mode 100644 index 000000000..ff419ccc4 --- /dev/null +++ b/documentation/content/userguide/bluetooth-audio-buttons.md @@ -0,0 +1,63 @@ +# Bluetooth + +## Bluetooth Audio Buttons + +When a bluetooth sound device (headphone, speakers) connects attempt to +automatically listen to it's buttons (play, next, etc.) + +The bluetooth input device name is matched automatically from the +bluetooth sound card device name. During boot up, it is uncertain if the +bluetooth device connects first, or the Jukebox service is ready first. +Therefore, after service initialization, already connected bluetooth +sound devices are scanned and an attempt is made to find their input +buttons. + +> [!NOTE] +> If the automatic matching fails, there currently is no manual configuration option. Open an issue ticket if you have problems with the automatic matching. + +Button key codes are standardized and by default the buttons play, +pause, next song, previous song are recognized. Volume up/down is +handled independently from this module by PulseAudio and the bluetooth +audio transmission protocol. + +The module needs to be enabled in the main configuration file with: + +``` yaml +bluetooth_audio_buttons: + enable: true +``` + +### Custom key bindings + +You may change or extend the actions assigned to a button in the +configuration. If the configuration contains a block 'mapping', the +default button-action mapping is *completely* replaced with the new +mapping. The definitions for each key looks like +`key-code: {rpc_command_definition}`. The RPC command follows the +regular RPC command rules as defined in +[RPC Commands](rpc-commands.md). + +``` yaml +bluetooth_audio_buttons: + enable: true + mapping: + # Play & pause both map to toggle which is also the usual behaviour of headsets + 200: + alias: toggle + 201: + alias: toggle + # Re-map next song button, to set defined output volume (for some fun) + 163: + package: volume + plugin: ctrl + method: set_volume + args: [18] + # Re-map prev song button to shutdown + 165: + alias: shutdown +``` + +Key codes can be found in the log files. Press the various buttons on +your headset, while watching the logs with e.g. +`tail -f shared/logs/app.log`. Look for entries like +`No callback registered for button ...`. diff --git a/documentation/content/userguide/card-database.md b/documentation/content/userguide/card-database.md new file mode 100644 index 000000000..66d4e3925 --- /dev/null +++ b/documentation/content/userguide/card-database.md @@ -0,0 +1,101 @@ +# Card Database + +In the card database, an RPC command is assigned to every card. + +This RPC command is called every time when the card is swiped (or +placed) on the reader. Every RPC callable function can be called. See +[RPC Commands](userguide/rpc_commands.md) for an introduction. + +The card database is stored in `shared\settings\cards.yaml`. Here are +some examples for RPC command assignments to cards \'0001\' to \'0003\' +using the alias option: + +> [!IMPORTANT] +> Card IDs **must** be strings! So, be sure to quote numbers! + +``` yaml +'0001': + # A RPC command using the alias definition without any arguments + # Here: pause playback + alias: pause +'0002': + # A RPC command using the alias definition with one arguments + # Here: Trigger music playback through the card interface + alias: play_card + args: [path/to/folder] +'0003': + # A RPC command using keyword arguments. Args and kwargs can also be mixed. + # Args and Kwargs translate directly into the function python call + # Some as in '0002' but using kwargs + alias: play_card + kwargs: + folder: path/to/folder +``` + +> [!NOTE] +> * Remember card ids must be strings! So, quote them! +> * *args* must be +a **list** of arguments to be passed! Even if ony a single argument is +passed. So, use *args: \[value\]*. We try catch mis-uses but that might +not always work. + +## Additional options + +In addition to the RPC commands, these options may be specified for +every card + +#### ignore_card_removal_action: true \| false (default: false) + +Only applies when using a place-capable reader and *place_not_swipe* is *true*. This option is ignored otherwise, so it does not hurt. + +Do not execute card removal action, when this card is removed from +the reader. Useful for command card, that e.g. enable the shutdown +timer + +``` yaml +'0004': + alias: timer_shutdown + ignore_card_removal_action: true +``` + +#### ignore_same_id_delay: true \| false (default: false) + +Override the `same_id_delay` parameter from the reader configuration +for this card. If true, the `same_id_delay` for this card is treated +as 0. This makes sense e.g., for an \"increase volume\" card in +combination with a place-capable RFID reader. As long as the card is +placed on the reader, the volume is increased. + +> [!NOTE] +> This parameter causes *ignore_card_removal_action* to be treated as true + +``` yaml +'0005': + alias: incr_volume + ignore_same_id_delay: true + ignore_card_removal_action: true +``` + +## Full RPC action specification + +You have seen some examples card actions using the *alias* +configuration. A full RPC action can also be specified using the +following syntax: + +``` yaml +'0006': + # Option 1: Omit the keyword 'alias' + # Here: Set the volume to level 12 + package: volume + plugin: ctrl + method: set_volume + args: [12] +'0007': + # Option 2: Set 'alias' to custom + # Here: Set the volume to level 12 + alias: custom + package: volume + plugin: ctrl + method: set_volume + args: [12] +``` diff --git a/documentation/content/userguide/concepts.md b/documentation/content/userguide/concepts.md new file mode 100644 index 000000000..509f474fc --- /dev/null +++ b/documentation/content/userguide/concepts.md @@ -0,0 +1,27 @@ +# Concepts + +The Jukebox is based on three concepts. Don't worry, we won't dive into all the juicy developer details (just yet). But a rough understanding is important as a foundation to understand the configuration files. + +## Plugin Interface + +The core app is centered around a plugin concept. This serves three purposes: + +1. Dynamically load packages with additional functionality based on configuration files. +2. Initialize and close these packages at app start/close. This happens automatically in the background. Failing packages (for any reason) are ignored during start-up. So when some functionality is not available, always check the logs to ensure all packages have loaded successfully! See [Troubleshooting](troubleshooting.md). +3. Register and present functions that can be called via the [Remote Procedure Call Server (RPC)](#remote-procedure-call-server-rpc). + +## Remote Procedure Call Server (RPC) + +The Remote Procedure Call (RPC) server allows remotely triggering actions (e.g., from the Webapp) within the Jukebox core application. Only Python functions registered by the plugin interface can be called. This simplifies external APIs and lets us focus on the relevant user functions. + +Why should you care? Because we use the same protocol when triggering actions from other inputs like a card swipe, a GPIO button press, etc. How that works is described in [RPC Commands](rpc-commands.md). + +You will find a full list of RPC callable functions in [RPC Command Reference](rpc-command-reference.md) and aliases for convenience in [RPC Command Alias Reference](rpc-command-alias-reference.md). + +We also have a tool to send RPC commands to the running Jukebox application: [run_rpc_tool.py](src/jukebox/run_rpc_tool.py). + +## Publishing Message Queue + +The Publishing Message Queue is the complementary part to the RPC where the core application publishes its status and status updates. As a user, you need not worry about it. + +If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming status messages: [run_publicity_sniffer.py](src/jukebox/run_rpc_tool.py). diff --git a/documentation/content/userguide/configuration.md b/documentation/content/userguide/configuration.md new file mode 100644 index 000000000..d23cce01f --- /dev/null +++ b/documentation/content/userguide/configuration.md @@ -0,0 +1,40 @@ +# Jukebox Configuration + +The Jukebox configuration is managed by set of files located in `../shared/settings`. +Some configuration changes can be made through the WebUI and take immediate effect. + +The majority of configuration options is only available by editing the config files - +*when the service is not running!* +Don't fear (overly), they contain commentaries. + +For several aspects we have :ref:`developer/coreapps:Configuration Tools` and detailed guides: + +* [Audio Configuration](userguide/audio.md#audio-configuration) +* [RFID Reader Configuration](rfid/basics.md#reader-configuration) + +Even after running the tools, certain aspects can only be changed by modifying the configuration files directly. + +## Best practice procedure + +```bash +# Make sure the Jukebox service is stopped +$ systemctl --user stop jukebox-daemon + +# Edit the file(s) +$ nano ./shared/settings/jukebox.yaml + +# Start Jukebox in console and check the log output (optional) +$ ./src/jukebox/run_jukebox.py +# and if OK, press Ctrl-C and restart the service + +# Restart the service +$ systemctl --user start jukebox-daemon +``` + +To try different configurations, you can start the Jukebox with a custom config file. +This could be useful if you want your Jukebox to only allow a lower volume when started +at night time when there is time to go to bed :-) + +```bash +$./run_jukebox.py --conf ../path/to/custom/config.yaml +``` diff --git a/documentation/content/userguide/installation.md b/documentation/content/userguide/installation.md new file mode 100644 index 000000000..e84b5de05 --- /dev/null +++ b/documentation/content/userguide/installation.md @@ -0,0 +1,93 @@ +# Installing Phoniebox future3 + +## Install Raspberry Pi OS Lite + +> [!IMPORTANT] +> Currently, the installation does only work on Raspberry Pi's with ARMv7 and ARMv8 architecture, so 2, 3 and 4! Pi 1 and Zero's are currently unstable and will require a bit more work! + +Before you can install the Phoniebox software, you need to prepare your Raspberry Pi. + +1. Connect a Micro SD card to your computer (preferable an SD card with high read throughput) +2. [Download](https://www.raspberrypi.org/software/) + the [Raspberry Pi Imager](https://www.raspberrypi.org/blog/raspberry-pi-imager-imaging-utility/>) and open it +3. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system. future3 does not support 64bit kernels (`aarch64`). You can use the settings menu of the Raspberry Pi Imager to configure SSH and WiFi in a more userfriendly way, or do it manually as described in the next step. In case you already have a 64bit system installed, [you can fix the issue like this](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041>). +4. Select your Micro SD card (your card will be formatted) +5. Click *Write* +6. Wait for the imaging process to be finished (it'll take a few minutes) + + +## Pre-boot preparation + +You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to proceed with the next steps. + +1. Open a terminal of your choice. +2. Insert your card again if it has been ejected automatically. +3. Navigate to your SD card e.g., `cd /Volumes/boot` for Mac or `D:` for Windows. +4. Enable SSH by adding a simple file. + + ```bash + $ touch ssh + ``` + + +5. Set up your Wifi connection. + + *Mac* + + ```bash + $ nano wpa_supplicant.conf + ``` + + *Windows* + + ```bash + D:\> notepad wpa_supplicant.conf + ``` + +6. Insert the following content, update your country, Wifi credentials and save the file. + + ``` + country=DE + ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev + update_config=1 + + network={ + ssid="network-name" + psk="network-password" + } + ``` + +7. Eject your SD card and insert it into your Raspberry Pi. +8. Start your Raspberry Pi by attaching a power supply. +9. Login into your Raspberry Pi, username is `pi` and password is `raspberry`. + If `raspberrypi.local` does not work, find out your Raspberry Pi's IP address from your router. + +## Install Phoniebox software + +Run the following command in your SSH terminal and follow the instructions + +```bash +cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh) +``` + +This will get the latest stable release from the branch future3/main. +To install directly from a specific branch and/or a different repository +specify the variables like this: + +```bash + +cd; GIT_USER='MiczFlor' GIT_BRANCH='future3/develop' bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) +``` + +This will switch directly to the specified feature branch during installation. + +> [!NOTE] +> For all branches *except* the current Release, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Steps to install](developers/development-environment#steps-to-install) + + +If you suspect an error you can monitor the installation-process with + +```bash +cd; tail -f INSTALL-.log +``` + diff --git a/docs/sphinx/userguide/mock_gpio.png b/documentation/content/userguide/mock_gpio.png similarity index 100% rename from docs/sphinx/userguide/mock_gpio.png rename to documentation/content/userguide/mock_gpio.png diff --git a/documentation/content/userguide/rfid.md b/documentation/content/userguide/rfid.md new file mode 100644 index 000000000..f8826f68f --- /dev/null +++ b/documentation/content/userguide/rfid.md @@ -0,0 +1,80 @@ +# RFID + +## Syncronisation RFID Cards + +This component handles the synchronisation of RFID cards (audiofolder +and card database entries). + +It allows to manage card database entries and audiofiles of one to many +Phonieboxes in a central place (e.g. NAS, primary Phoniebox etc.) in the +network, but allows to play the audio offline once the data has synced. +The synchronisation can be initiated with the command `sync-all` and +optionally on every RFID scan for a particular CardID and its +corresponding audiofolder. To execute the `sync-all` command, bind a +RFID card to the command. For the \"RFID scan sync\" feature, activate +the option in the configuration or bind a RFID card to the command for +dynamic activation or deactivation. + +### Synchronisation + +The synchronisation will be FROM a server TO the Phoniebox, overriding +existing files. A local configuration will be lost after the +synchronization. If you want to make the initial setup e.g. via WebUi +copy the files and use it as a base for the server. + +To access the files on the server, 2 modes are supported: SSH or MOUNT. +Please make sure you have the correct access rights to the source and +use key-based authentication for SSH. + +#### RFID scan sync + +If the feature \"RFID scan sync\" is activated, there will be a check on +every RFID scan against the server if a matching card entry and audiofolder is available. If so, changes will be synced. The playback +will be delayed for the time the data is transfered (see \"sync-all\" to +use a full synchronization if a lot of new files have been added). If +the server is not reachable, the check will be aborted after the +timeout. Therfore, an unreachable server will cause a delay (see +commands to toggle activation state). Deleted card entries / +audiofolders (not the contained items) will not be purged locally if +deleted on remote. This is also true for changed card entries (the old +audiofolder / -files will remain). To remove not existing items us a +\"sync-all\". + +### Configuration + +Set the corresponding setting in `shared\settings\jukebox.yaml` to +activate this feature. + +``` yaml +modules: + named: + ... + sync_rfidcards: synchronisation.rfidcards + +... +sync_rfidcards: + enable: false + config_file: ../../shared/settings/sync_rfidcards.yaml +``` + +The settings file (`shared\settings\sync_rfidcards.yaml`) contains the +following configuration + +``` yaml +sync_rfidcards: + # Holds the activation state of the optional feature "RFID scan sync". Values are "TRUE" or "FALSE" + on_rfid_scan_enabled: true # bool + # Server Access mode. MOUNT or SSH + mode: mount # 'mount' or 'ssh' + credentials: + # IP or hostname of the server (used to check connectivity and for SSH mode). e.g. "192.168.0.2" or "myhomeserver.local" + server: '' + # Port (used to check connectivity and for SSH mode). e.g. "80" or "22" + port: # int + # Timeout to reach the server (in seconds) (used to check connectivity). e.g. 1 + timeout: 1 # int + # Path to the shared files to sync (without trailing slash) (remote path for SSH mode or local path for MOUNT mode). e.g. "/mnt/Phoniebox" + path: '' + # Username if SSH mode is used. + username: '' +``` diff --git a/documentation/content/userguide/system.md b/documentation/content/userguide/system.md new file mode 100644 index 000000000..a9e4a162e --- /dev/null +++ b/documentation/content/userguide/system.md @@ -0,0 +1,112 @@ +# System Setup + +A few words on how the system is setup and interacts. + +The system consists of + +1. [Music Player Daemon (MPD)](system.md#music-player-daemon-mpd) which we use for all music playback (local, stream, podcast, ...) +2. [PulseAudio](system.md#pulseaudio) for flexible audio output support +3. [Jukebox Core Service](system.md#jukebox-core-service) for controlling MPD and PulseAudio and providing all the features +4. [Web UI](system.md#web-ui) which is served through an Nginx web server +5. A set of [Configuration Tools](../developer/coreapps.md#configuration-tools) and a set of [Developer Tools](../developer/coreapps.md#developer-tools) + +.. note:: The default install puts everything into the folder `/home/pi/RPi-Jukebox-RFID`. + Another folder might work, but is certainly not tested. Things are installed for the default user `pi`. Again, + another user might work, but is not tested. + +## Music Player Daemon (MPD) + +The Music Player Daemon runs as *user-local* service (not as system-wide service which is usually the default). +This is important for the interaction with PulseAudio. + +You will find the MPD configuration file under + +```text +$HOME/.config/mpd/mpd.conf +``` + +All MPD *var*-files are also located in `$HOME/.config/mpd`. + +The service can be controlled with the *systemctl*-command when adding the parameter `--user`: + +```bash +$ systemctl --user status mpd +$ systemctl --user start mpd +$ systemctl --user stop mpd +``` + +.. important:: Never start or enable the system-wide MPD service with `sudo systemctl start mpd`! + +To check if MPD is running or has issues, use + +```bash +$ systemctl --user status mpd +# or, if you need to get the full logs +$ journalctl --user -b -u mpd +``` + +The `systemd` service file is located at the default location for user services: + +``` +/usr/lib/systemd/user/mpd.service +``` + +## PulseAudio + +We use PulseAudio for the audio output configuration. Check out the Audio Configuration page for details. + +There is a number of reasons for that: + +* It is easier to support and setup different audio hardware. Over the years, many builders have tried many different ways to set up audio on their Jukebox so this become the most reliable and compatible solution +* We can cleanly control and switch between different audio outputs independent of the playback software +* The current Pi OS based on Bullseye does not allow another way to control Bluetooth based speakers, as Bluealsa is currently not working with Bluez 5 + +The PulseAudio configuration file is located at + +``` +~/.config/pulse/default.pa +``` + +Service control and service configuration file location is identical to MPD. + +## Jukebox Core Service + +The :ref:`developer/coreapps:Jukebox Core` runs as a *user-local* service with the name `jukebox-daemon`. +Similar to MPD, it's important that it does run as system-wide service to be able to interact with PulseAudio. + +The service can be controlled with the `systemctl`-command by adding the parameter `--user` + +```bash +$ systemctl --user start jukebox-daemon +$ systemctl --user stop jukebox-daemon +``` + +Check out the service with + +```bash +$ systemctl --user status jukebox-daemon +# and if you need to get the full log output +$ journalctl --user -b -u jukebox-daemon +``` + +The `systemd` service file is located at the default location for user services: + +``` +/usr/lib/systemd/user/jukebox-daemon.service +``` + +Starting and stopping the service can be useful for debugging or configuration checks. + +## Web UI + +The Web UI is served using nginx. Nginx runs as a system service. The home directory is localed at + +``` +/home/pi/RPi-Jukebox-RFID/src/webapp/build +``` + +The Nginx configuration is located at + +``` +/etc/nginx/sites-available/default +``` diff --git a/documentation/content/userguide/troubleshooting.md b/documentation/content/userguide/troubleshooting.md new file mode 100644 index 000000000..a0618ab5d --- /dev/null +++ b/documentation/content/userguide/troubleshooting.md @@ -0,0 +1,97 @@ +# Troubleshooting + +We have made a point of providing extensive log messages. +In full debug mode, this may become very verbose. In fact, better observability +has been one of the design goals for version 3. + +There are various options to get access to debug information. + +Debugging your setup runs in several steps + +1. Check that [audio output works](audio.md#checking-system-sound-output) +2. Check that [MPD works](system.md#music-player-daemon-mpd) +3. Checking log messages from the Jukebox Core App as described below + +## The short answer + +```bash +../shared/logs/app.log : Complete Debug Messages +../shared/logs/errors.log: Only Errors and Warnings +``` + +These files always contain the messages of the current run only. +The logs of previous runs are post-fixed with `.1`, e.g. `app.log.1`. This is useful for debugging issues during +shutdown of the service. + +The logs are also available via the Web Server: + +``` +http://ip.of.your.box/logs +``` + +> [!IMPORTANT] Always check the time modification date or the beginning of the log file to ensure you are not looking at an old log file! + +## The long answer: A few more details + +If started without parameters, the Jukebox checks for the existence of `../shared/settings/logger.yaml` +and if present, uses that configuration for logging. This file is created by the installation process. +The default configuration file is also provided in `../resources/default-settings/logger.default.yaml`. +We use Python's logging module to provide the debug messages which is configured through this file. + +**We are still in the Pre-Release phase which means full debug logging is enabled by default.** + +### Default logging configuration + +The default logging config does 2 things: + +1. It writes 2 log files: + +```bash +../shared/logs/app.log : Complete Debug Messages +../shared/logs/errors.log : Only Errors and Warnings +``` + +2. Prints logging messages to the console. If run as a service, only error messages are emitted to console to avoid spamming the system log files. + +### Debug logging in console + +For debugging, it is usually very helpful to observe the apps output directly +on the console log. + +``` bash +# Make sure the Jukebox service is stopped: +$ systemctl --user stop jukebox-daemon + +# Start the Jukebox in debug mode: +# with default logger: +$ ./run_jukebox.py + +# or with custom logger configuration: +$ ./run_jukebox.py --logger ../path/to/logger.yaml +``` + +### Fallback configuration + +It is possible to start the Jukebox with a catch-all debug enabler with a logger.yaml. +Attention: This only emits messages to the console and does not write to the log files! +This is more a fallback features: + +``` bash +$ ./run_jukebox.py -vv +``` + +### Extreme cases + +Sometimes, the Jukebox app might crash with an exception and stack trace which is +neither logged, nor caught and handled. + +If run locally from your console, you will see it immediately. No worries! + +If running as a service, you will probably not even notice immediately that something has +gone pear-shaped. Services are restarted automatically when they fail. + +Things are just not behaving as expected? Time to check the system logs: + +``` bash +$ journalctl --user -b -u jukebox-daemon +``` diff --git a/documentation/content/userguide/update.md b/documentation/content/userguide/update.md new file mode 100644 index 000000000..e09137ca3 --- /dev/null +++ b/documentation/content/userguide/update.md @@ -0,0 +1,39 @@ +# Update + +## Updating your Jukebox Version 3 + +Things on Version 3 are moving fast and you may want to keep up with recent changes. Since we are in Alpha Release stage, +a fair number of fixes are expected to be committed in the near future. + +You will need to do three things to update your version from develop (or the next release candidate version) + +1. Pull the newest code base from Github +2. Check for new entries in the configuration +3. Re-build the WebUI + +```bash +# Switch to develop (if desired) +$ git checkout future3/develop + +# Get latest code +$ git pull + +# Check if new (mandatory) options appeared in jukebox.yaml +# with your favourite diff tool and merge them +$ diff shared/settings/jukebox.yaml resources/default-settings/jukebox.default.yaml + +$ cd src/webapp +$ ./run_rebuild.sh +``` + +## Migration Path from Version 2 + +There is no update path coming from Version 2.x of the Jukebox. +You need to do a fresh install of Version 3 on a fresh Raspian Bullseye image. + +> [!IMPORTANT] +> Do start with a fresh SD card image! + +Do not just pull the future3 branch into you existing Version 2.x directory. +Do not run the installer on an system that had Version 2.x running before on it. +Stuff has changed too much to make this feasible. diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index 3af9274d5..5fe93c1b8 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -188,29 +188,6 @@ ${DISABLE_ONBOARD_AUDIO_BACKUP} if things go pear-shaped.) } -_option_build_local_docs() { - echo -e "Do you want to build the documentation locally and -make it available under http://ip.of.your.box/docs ? - -Note: - - This will force enable=true for the WebApp - - Up to date documentation is also always available online -Build and serve docs locally? [y/N] " 1>&3 - read -r response - case "$response" in - [yY]) - ENABLE_LOCAL_DOCS=true - ENABLE_WEBAPP=true - ;; - *) - ;; - esac - - echo "ENABLE_LOCAL_DOCS=${ENABLE_LOCAL_DOCS}" - echo "ENABLE_WEBAPP=${ENABLE_WEBAPP}" - -} - _option_webapp_devel_build() { # Let's detect if we are on the official release branch if [[ "$GIT_BRANCH" != "${GIT_BRANCH_RELEASE}" || "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then @@ -250,7 +227,6 @@ customize_options() { _option_disable_onboard_audio _option_samba _option_webapp - _option_build_local_docs if [[ $ENABLE_WEBAPP == true ]] ; then _option_kiosk_mode _option_webapp_devel_build diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 2014214fc..df8b64c69 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -70,12 +70,6 @@ _jukebox_webapp_register_as_system_service_with_nginx() { sudo service nginx restart } -_jukebox_build_local_docs() { - echo " Build docs locally" | tee /dev/fd/3 - "${INSTALLATION_PATH}/run_sphinx.sh" -c -} - - setup_jukebox_webapp() { echo "Install web application" | tee /dev/fd/3 @@ -88,9 +82,6 @@ setup_jukebox_webapp() { # Needs to be done after reboot! There will be a message at the end of the installation process # _jukebox_webapp_build fi - if [[ $ENABLE_LOCAL_DOCS == true ]]; then - _jukebox_build_local_docs - fi _jukebox_webapp_register_as_system_service_with_nginx echo "DONE: setup_jukebox_webapp" diff --git a/requirements.txt b/requirements.txt index 25fd7f4d3..1d9e9ae83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,9 +23,6 @@ tornado # On regular Linux PCs, Websocket is enabled in the Python package # pyzmq -# Documentation build flow -sphinx -sphinx_rtd_theme # Code quality flake8>=4.0.0 diff --git a/resources/default-settings/nginx.default b/resources/default-settings/nginx.default index 284819257..b949beb26 100644 --- a/resources/default-settings/nginx.default +++ b/resources/default-settings/nginx.default @@ -20,41 +20,16 @@ server { try_files $uri $uri/ =404; } + location /logs { + root /home/pi/RPi-Jukebox-RFID/shared; - location /docs { - root /home/pi/RPi-Jukebox-RFID/docs/sphinx/_build/html; - try_files $uri $uri/ =404; - location = /docs/index.html { - error_page 404 = @runsphinx; - log_not_found off; - } - location = /docs { - error_page 404 = @runsphinx; - log_not_found off; - } - location = /docs/ { - error_page 404 = @runsphinx; - log_not_found off; - } + autoindex on; + autoindex_exact_size off; + autoindex_localtime on; + default_type text/plain; } - location /logs { - root /home/pi/RPi-Jukebox-RFID/shared; - - autoindex on; - autoindex_exact_size off; - autoindex_localtime on; - - default_type text/plain; - } - - location @runsphinx { - root /home/pi/RPi-Jukebox-RFID/resources/html; - try_files /runsphinx.html =404; - internal; - } - location @buildwebui { root /home/pi/RPi-Jukebox-RFID/resources/html; try_files /runbuildui.html =404; diff --git a/resources/html/runsphinx.html b/resources/html/runsphinx.html deleted file mode 100755 index 10feb79f0..000000000 --- a/resources/html/runsphinx.html +++ /dev/null @@ -1,24 +0,0 @@ - - - -Phoniebox documentation not found: please build documentation - - - -
-

Ups! Looks like your documentation has not been build!

-

No reason to panic. Please run through the following steps: -

    -
  • cd /home/pi/RPi-Jukebox-RFID
  • -
  • ./run_sphinx.sh -c
  • -
  • Reload this page
  • -
-

Or check out the online documentation at Read The Docs. -

- - diff --git a/run_sphinx.sh b/run_sphinx.sh deleted file mode 100755 index 972860cbe..000000000 --- a/run_sphinx.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -usage() { - echo "Runner script for sphinx documentation build" - echo -e "\nWarnings are treated as errors as preparation for documentation releases.\n" - echo "Usage:" - echo " ./run_sphinx.sh : incremental build" - echo " ./run_sphinx.sh -c : clean re-build" - echo " ./run_sphinx.sh -n : Don't escalate warning to errors (for debug only!)" - exit 1 -} - -# Change working directory to location of script -SOURCE=${BASH_SOURCE[0]} -SCRIPT_DIR="$(dirname "$SOURCE")" -cd "$SCRIPT_DIR/docs/sphinx" || (echo "Could not change to docs directory" && exit 1) - -BUILD_DIR=_build - -CLEAN_BUILD=false -SPHINX_OPTS="-W --keep-going -T" -while getopts ":chn" opt; -do - case ${opt} in - c ) CLEAN_BUILD=true - ;; - h ) usage - ;; - n ) SPHINX_OPTS="-T" - ;; - \? ) usage - ;; - esac -done - - -if [[ $CLEAN_BUILD = true ]]; then - echo "Cleaning $BUILD_DIR" - rm -rf $BUILD_DIR - SPHINX_OPTS="$SPHINX_OPTS -a -E" -fi - -echo "Building docs [ sphinx-build $SPHINX_OPTS -b html . ${BUILD_DIR}/html/docs ]" -sphinx-build $SPHINX_OPTS -b html . ${BUILD_DIR}/html/docs - diff --git a/src/jukebox/components/controls/bluetooth_audio_buttons/README.rst b/src/jukebox/components/controls/bluetooth_audio_buttons/README.rst index 290b0c6b1..87a7eab2b 100644 --- a/src/jukebox/components/controls/bluetooth_audio_buttons/README.rst +++ b/src/jukebox/components/controls/bluetooth_audio_buttons/README.rst @@ -52,4 +52,4 @@ The RPC command follows the regular RPC command rules as defined in :ref:`usergu Key codes can be found in the log files. Press the various buttons on your headset, while watching the -logs with e.g. ``tail -f shared/logs/app.log``. Look for entries like ``No callback registered for button ...``. \ No newline at end of file +logs with e.g. ``tail -f shared/logs/app.log``. Look for entries like ``No callback registered for button ...``. diff --git a/src/jukebox/components/gpio/gpioz/README.rst b/src/jukebox/components/gpio/gpioz/README.rst index 9d6d7020b..46ab0250a 100644 --- a/src/jukebox/components/gpio/gpioz/README.rst +++ b/src/jukebox/components/gpio/gpioz/README.rst @@ -462,5 +462,3 @@ Simply change the header in the configuration file to enable it. Host is the IP pigpio.PiGPIOFactory: kwargs: host: 192.168.178.32 - - diff --git a/src/jukebox/components/rfid/hardware/template_new_reader/README.rst b/src/jukebox/components/rfid/hardware/template_new_reader/README.rst index 228f3039a..e20e4452b 100644 --- a/src/jukebox/components/rfid/hardware/template_new_reader/README.rst +++ b/src/jukebox/components/rfid/hardware/template_new_reader/README.rst @@ -27,8 +27,7 @@ Your new reader is a python subpackage with these three mandatory files +- description.py <-- A description module w/o dependencies. Do not change the filename! +- README.rst <-- The Readme -The module documentation must go into a separate file so we can import it into the Sphinx document generation flow -without loading the Python module. +The module documentation must go into a separate file, named README.MD. Conventions ^^^^^^^^^^^^^^^^^^ diff --git a/src/jukebox/jukebox/daemon.py b/src/jukebox/jukebox/daemon.py index 0c3805eeb..e847428e2 100755 --- a/src/jukebox/jukebox/daemon.py +++ b/src/jukebox/jukebox/daemon.py @@ -230,13 +230,10 @@ def run(self): if self.write_artifacts: # This writes out - # rpc_command_reference.rst # rpc_command_reference.txt - # rpc_command_alias_reference.rst # rpc_command_alias_reference.txt artifacts_dir = '../../shared/artifacts/' - sphinx_dir = '../../docs/sphinx/userguide' try: os.mkdir(artifacts_dir) @@ -250,14 +247,6 @@ def run(self): with open(os.path.join(artifacts_dir, 'rpc_command_alias_reference.txt'), 'w') as stream: jukebox.utils.generate_cmd_alias_reference(stream) - # Write RST files directly into Sphinx directory - - with open(os.path.join(sphinx_dir, 'rpc_command_reference.rst'), 'w') as stream: - plugin.generate_help_rst(stream) - - with open(os.path.join(sphinx_dir, 'rpc_command_alias_reference.rst'), 'w') as stream: - jukebox.utils.generate_cmd_alias_rst(stream) - # Start the RPC Server self.rpc_server.run() From 7406085aa8f960c202d933073390aef99c9f4a49 Mon Sep 17 00:00:00 2001 From: s-martin Date: Sat, 11 Nov 2023 21:43:55 +0100 Subject: [PATCH 009/121] Fix link to feature status (#2102) * Fix link to feature status * Rename to-be-deleted-status.md to status.md --- documentation/README.md | 2 +- .../content/developers/{to-be-deleted-status.md => status.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename documentation/content/developers/{to-be-deleted-status.md => status.md} (100%) diff --git a/documentation/README.md b/documentation/README.md index d71a57e0b..14c1de837 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -12,7 +12,7 @@ project check out the [documentation of Version 2](https://github.com/MiczFlor/R * [Installing Phoniebox future3](./content/userguide/installation.md) * [Update](./content/userguide/update.md) -* [Feature Status](status.md) +* [Feature Status](./content/developers/status.md) * [Known Issues](./content/developers/known-issues.md) * [User Guide](./content/userguide/) * [Developer Reference](developers) diff --git a/documentation/content/developers/to-be-deleted-status.md b/documentation/content/developers/status.md similarity index 100% rename from documentation/content/developers/to-be-deleted-status.md rename to documentation/content/developers/status.md From 4667fc13b37206c9398ef8dbb49d2eda1e10ac5a Mon Sep 17 00:00:00 2001 From: s-martin Date: Sat, 11 Nov 2023 21:47:04 +0100 Subject: [PATCH 010/121] Fix link --- documentation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/README.md b/documentation/README.md index 14c1de837..9705e54d2 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -15,7 +15,7 @@ project check out the [documentation of Version 2](https://github.com/MiczFlor/R * [Feature Status](./content/developers/status.md) * [Known Issues](./content/developers/known-issues.md) * [User Guide](./content/userguide/) -* [Developer Reference](developers) +* [Developer Reference](./content/developers) ## future3 From b7743da9c26338a1941ffefde3a71d9788a72a3b Mon Sep 17 00:00:00 2001 From: "Philipp S. Sommer" Date: Sun, 12 Nov 2023 00:04:23 +0100 Subject: [PATCH 011/121] Fix future3 installation for bookworm (#2100) * add --break-system-packages option to pip3 install as required for bookworm, see https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2050#issuecomment-1774101077 * install libasound2-dev to fix installation of pyalsaaudio via pip3 * install NodeJS and npm via official recommendation see https://github.com/nodesource/distributions * configure break-system-packages option globally see discussion in https://github.com/MiczFlor/RPi-Jukebox-RFID/pull/2100#pullrequestreview-1726040175 * allow nginx (and others) to access /home/pi --- installation/routines/setup_jukebox_core.sh | 8 ++++++++ installation/routines/setup_jukebox_webapp.sh | 19 ++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 45eb1610c..7fec72ab6 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -27,11 +27,19 @@ _jukebox_core_install_os_dependencies() { python3-rpi.gpio python3-gpiozero \ espeak ffmpeg mpg123 \ pulseaudio pulseaudio-module-bluetooth pulseaudio-utils caps \ + libasound2-dev \ --no-install-recommends \ --allow-downgrades \ --allow-remove-essential \ --allow-change-held-packages + # add configuration that we break the global python system packages + # (required for bookworm, see + # https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2050). This + # should be removed once the jukebox has been isolated to a dedicated + # venv + sudo python3 -m pip config set global.break-system-packages true + sudo pip3 install --upgrade pip } diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index df8b64c69..def263932 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -4,7 +4,7 @@ GD_ID_COMPILED_WEBAPP="1EE_1MdneGtKL5V7GyYZC0nb6ODQWTsPb" # https://drive.google.com/file/d/1EE_1MdneGtKL5V7GyYZC0nb6ODQWTsPb/view?usp=sharing # For ARMv7+ -NODE_SOURCE="https://deb.nodesource.com/setup_16.x" +NODE_MAJOR=16 # For ARMv6 # To update version, follow these links # https://github.com/sdesalas/node-pi-zero @@ -26,12 +26,18 @@ _jukebox_webapp_install_node() { # Zero and older versions of Pi with ARMv6 only # support experimental NodeJS if [[ $(uname -m) == "armv6l" ]]; then - NODE_SOURCE=${NODE_SOURCE_EXPERIMENTAL} + wget -O - ${NODE_SOURCE_EXPERIMENTAL} | sudo bash + sudo apt-get -qq -y install nodejs + sudo npm install --silent -g npm + else + # install NodeJS and npm as recommended in + # https://github.com/nodesource/distributions + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list + sudo apt-get update + sudo apt-get install nodejs npm -y fi - wget -O - ${NODE_SOURCE} | sudo bash - sudo apt-get -qq -y install nodejs - sudo npm install --silent -g npm fi } @@ -67,6 +73,9 @@ _jukebox_webapp_register_as_system_service_with_nginx() { sudo mv -f /etc/nginx/sites-available/default /etc/nginx/sites-available/default.orig sudo cp -f "${INSTALLATION_PATH}/resources/default-settings/nginx.default" /etc/nginx/sites-available/default + # make sure nginx can access the home directory of the user + sudo chmod o+x /home/pi + sudo service nginx restart } From 86d608cecb56ffa2488eacfda3a43c0fae65cfac Mon Sep 17 00:00:00 2001 From: s-martin Date: Sun, 12 Nov 2023 12:31:13 +0100 Subject: [PATCH 012/121] fix some more doc links (#2105) * fix doc links * add a readme to user guide * add readme to developers section * fix links * fix some links * fix links * fix some links * fix links * fix links * fix links * fix links * fix links * add new line --- README.md | 4 +-- documentation/content/developers/README.md | 13 ++++++++++ documentation/content/developers/coreapps.md | 2 +- .../developers/development-environment.md | 5 ++-- documentation/content/developers/docker.md | 2 +- documentation/content/userguide/README.md | 25 +++++++++++++++++++ documentation/content/userguide/audio.md | 18 ++++++------- documentation/content/userguide/concepts.md | 4 +-- .../content/userguide/configuration.md | 4 +-- .../content/userguide/installation.md | 4 +-- documentation/content/userguide/system.md | 2 +- 11 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 documentation/content/developers/README.md create mode 100644 documentation/content/userguide/README.md diff --git a/README.md b/README.md index e45ddaa26..b95b0e8c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # RFID Jukebox Version 3 (aka future3) -[![Documentation Status](https://readthedocs.org/projects/rpi-jukebox-rfid/badge/?version=latest)](https://rpi-jukebox-rfid.readthedocs.io/en/latest/?badge=latest) - ## What is this? The exiting, new **Version 3** of the RPi Jukebox RFID. A complete re-write of the Jukebox. @@ -11,7 +9,7 @@ project check out the [documentation of Version 2]( [!NOTE] > This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). Any manual modifications to the settings will have to be re-applied diff --git a/documentation/content/developers/development-environment.md b/documentation/content/developers/development-environment.md index 0b271ce38..6d60cdbd4 100644 --- a/documentation/content/developers/development-environment.md +++ b/documentation/content/developers/development-environment.md @@ -16,7 +16,7 @@ developing. 1. Install the latest Pi OS on a SD card. 2. Boot up your Raspberry Pi. -3. [Install](../installation.md) the Jukebox software as if you were building a +3. [Install](../userguide/installation.md) the Jukebox software as if you were building a Phoniebox. You can install from your own fork and feature branch if you wish which can be changed later as well. The original repository will be set as `upstream`. @@ -70,5 +70,4 @@ separately. The MPD usually runs as a service. ## Using Docker container -There is a complete setup -`docker workflow `. +There is a complete [Docker setup](./docker.md). diff --git a/documentation/content/developers/docker.md b/documentation/content/developers/docker.md index f3b9c825f..4384bbf46 100644 --- a/documentation/content/developers/docker.md +++ b/documentation/content/developers/docker.md @@ -52,7 +52,7 @@ They can be run individually or in combination. To do that, we use ### Linux Make sure you don\'t use `sudo` to run your `docker-compose`. Check out -Docker\'s [post-installation guide](https://docs.docker.com/engine/install/linux-postinstall/] for more information. +Docker\'s [post-installation guide](https://docs.docker.com/engine/install/linux-postinstall/) for more information. ``` bash // Build Images diff --git a/documentation/content/userguide/README.md b/documentation/content/userguide/README.md new file mode 100644 index 000000000..5a822a41f --- /dev/null +++ b/documentation/content/userguide/README.md @@ -0,0 +1,25 @@ +# User Guide + +## Getting started + +* [Installing Phoniebox future3](./installation.md) +* [Update](./update.md) +* [Configuring Phoniebox](./configuration.md) + +## Configuration + +* [Audio](./audio.md) +* [RFID](./rfid.md) +* [Card Database](./card-database.md) +* [Troubleshooting](./troubleshooting.md) + +## Advanced + +* [Bluetooth (and audio buttons)](./bluetooth-audio-buttons.md) +* [Auto Hotspot](./autohotspot.md) +* [Concepts](./concepts.md) +* [System](./system.md) +* [Feature Status](../developers/status.md) +* [Known Issues](../developers/known-issues.md) +* [Developer Reference](../developers) + diff --git a/documentation/content/userguide/audio.md b/documentation/content/userguide/audio.md index fb3271197..d948e1035 100644 --- a/documentation/content/userguide/audio.md +++ b/documentation/content/userguide/audio.md @@ -10,14 +10,14 @@ Stream transfer happens on user input or automatically on the connection of an a This is mainly targeted at Bluetooth Headsets/Speakers. Audio outputs run via PulseAudio and the basic configuration should be easy. -There is a [configuration tool](..developers/coreapps#run_configure_audio.py), +There is a [configuration tool](../developers/coreapps.md#run_configure_audio.py), to setup the configuration for the Jukebox Core App. To set up the audio 1. Follow the setup steps according to your sound card 2. Check that the sound output works [as described below](audio.md#checking-system-sound-output) -3. Run the the tool [run_configure_audio](developers/coreapps.md#run_configure_audio.py) +3. Run the the tool [run_configure_audio](../developers/coreapps.md#run_configure_audio.py) 4. [Fine-tune audio parameters](audio.md#additional-options) ## Checking system sound output @@ -99,7 +99,7 @@ Rerun the config tool to register the Bluetooth device with the Jukebox core app For other audio configuration options, please look at the `jukebox.yaml` for now. -Directly edit `jukebox.yaml` following the steps: [Best practice procedure](.md#best-practice-procedure). +Directly edit `jukebox.yaml` following the steps: [Best practice procedure](configuraton.md#best-practice-procedure). ## Developer Information @@ -118,21 +118,21 @@ $ pactl list sinks short Which means we can put any of these as sink into the jukebox configuration file (if there is any need). Mono down mix is enabled by the module `module-remap-sink` -for which documentation and an example can be found [here](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-remap-sink>). +for which documentation and an example can be found [here](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-remap-sink). -The equalizer is the PulseAudio module `module-ladspa-sink` with the [corresponding documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-ladspa-sink>). +The equalizer is the PulseAudio module `module-ladspa-sink` with the [corresponding documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-ladspa-sink). This in turn loads a [LADSPA plugin](https://www.ladspa.org/). -The LADSPA plugin in the `Eq10X2` plugin of the [CAPS Library](http://quitte.de/dsp/caps.html#Eq10>). The CAPS library is available as linux package `caps`. +The LADSPA plugin in the `Eq10X2` plugin of the [CAPS Library](http://quitte.de/dsp/caps.html#Eq10). The CAPS library is available as linux package `caps`. This is the same plugin which is used in the -[equalizer for pure ALSA](https://github.com/raedwulf/alsaequal>) +[equalizer for pure ALSA](https://github.com/raedwulf/alsaequal) configurations which is part of the linux package `libasound2-plugin-equal`. You are, of course, free to modify the PulseAudio configuration to your needs. References -1. [PulseAudio Documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User>) -2. [PulseAudio Examples](https://wiki.archlinux.org/title/PulseAudio/Examples>) +1. [PulseAudio Documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User) +2. [PulseAudio Examples](https://wiki.archlinux.org/title/PulseAudio/Examples) In this case, run the configuration tool with below parameter to avoid touching the PulseAudio configuration file. diff --git a/documentation/content/userguide/concepts.md b/documentation/content/userguide/concepts.md index 509f474fc..70f285973 100644 --- a/documentation/content/userguide/concepts.md +++ b/documentation/content/userguide/concepts.md @@ -18,10 +18,10 @@ Why should you care? Because we use the same protocol when triggering actions fr You will find a full list of RPC callable functions in [RPC Command Reference](rpc-command-reference.md) and aliases for convenience in [RPC Command Alias Reference](rpc-command-alias-reference.md). -We also have a tool to send RPC commands to the running Jukebox application: [run_rpc_tool.py](src/jukebox/run_rpc_tool.py). +We also have a tool to send RPC commands to the running Jukebox application: [run_rpc_tool.py](../../../src/jukebox/run_rpc_tool.py). ## Publishing Message Queue The Publishing Message Queue is the complementary part to the RPC where the core application publishes its status and status updates. As a user, you need not worry about it. -If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming status messages: [run_publicity_sniffer.py](src/jukebox/run_rpc_tool.py). +If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming status messages: [run_publicity_sniffer.py](../../../src/jukebox/run_rpc_tool.py). diff --git a/documentation/content/userguide/configuration.md b/documentation/content/userguide/configuration.md index d23cce01f..2aaa3184b 100644 --- a/documentation/content/userguide/configuration.md +++ b/documentation/content/userguide/configuration.md @@ -9,8 +9,8 @@ Don't fear (overly), they contain commentaries. For several aspects we have :ref:`developer/coreapps:Configuration Tools` and detailed guides: -* [Audio Configuration](userguide/audio.md#audio-configuration) -* [RFID Reader Configuration](rfid/basics.md#reader-configuration) +* [Audio Configuration](./audio.md#audio-configuration) +* [RFID Reader Configuration](./rfid/basics.md#reader-configuration) Even after running the tools, certain aspects can only be changed by modifying the configuration files directly. diff --git a/documentation/content/userguide/installation.md b/documentation/content/userguide/installation.md index e84b5de05..ef448e98f 100644 --- a/documentation/content/userguide/installation.md +++ b/documentation/content/userguide/installation.md @@ -9,7 +9,7 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr 1. Connect a Micro SD card to your computer (preferable an SD card with high read throughput) 2. [Download](https://www.raspberrypi.org/software/) - the [Raspberry Pi Imager](https://www.raspberrypi.org/blog/raspberry-pi-imager-imaging-utility/>) and open it + the [Raspberry Pi Imager](https://www.raspberrypi.org/blog/raspberry-pi-imager-imaging-utility/) and open it 3. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system. future3 does not support 64bit kernels (`aarch64`). You can use the settings menu of the Raspberry Pi Imager to configure SSH and WiFi in a more userfriendly way, or do it manually as described in the next step. In case you already have a 64bit system installed, [you can fix the issue like this](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041>). 4. Select your Micro SD card (your card will be formatted) 5. Click *Write* @@ -82,7 +82,7 @@ cd; GIT_USER='MiczFlor' GIT_BRANCH='future3/develop' bash <(wget -qO- https://ra This will switch directly to the specified feature branch during installation. > [!NOTE] -> For all branches *except* the current Release, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Steps to install](developers/development-environment#steps-to-install) +> For all branches *except* the current Release, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Steps to install](../developers/development-environment.md#steps-to-install) If you suspect an error you can monitor the installation-process with diff --git a/documentation/content/userguide/system.md b/documentation/content/userguide/system.md index a9e4a162e..5b1ce5c0c 100644 --- a/documentation/content/userguide/system.md +++ b/documentation/content/userguide/system.md @@ -8,7 +8,7 @@ The system consists of 2. [PulseAudio](system.md#pulseaudio) for flexible audio output support 3. [Jukebox Core Service](system.md#jukebox-core-service) for controlling MPD and PulseAudio and providing all the features 4. [Web UI](system.md#web-ui) which is served through an Nginx web server -5. A set of [Configuration Tools](../developer/coreapps.md#configuration-tools) and a set of [Developer Tools](../developer/coreapps.md#developer-tools) +5. A set of [Configuration Tools](../developers/coreapps.md#configuration-tools) and a set of [Developer Tools](../developers/coreapps.md#developer-tools) .. note:: The default install puts everything into the folder `/home/pi/RPi-Jukebox-RFID`. Another folder might work, but is certainly not tested. Things are installed for the default user `pi`. Again, From 1ca31ddc6e39dbf391be8ae9e6655014f9ecb164 Mon Sep 17 00:00:00 2001 From: s-martin Date: Sun, 12 Nov 2023 20:49:45 +0100 Subject: [PATCH 013/121] fix #2101 (#2107) * fix #2101 * fix #2101 for de --- src/webapp/public/locales/de/translation.json | 3 ++- src/webapp/public/locales/en/translation.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/webapp/public/locales/de/translation.json b/src/webapp/public/locales/de/translation.json index e2424b649..25b11b703 100644 --- a/src/webapp/public/locales/de/translation.json +++ b/src/webapp/public/locales/de/translation.json @@ -177,7 +177,8 @@ "rebooting": "Das System wird neu gestartet.", "button-title": "Neustart", "description-confirm": "Möchtest Du jetzt wirklich neu starten?", - "failed": "Neustart fehlgeschlagen" + "failed": "Neustart fehlgeschlagen", + "bye": "👋 Bis bald" }, "shutdown": { "title": "Herunterfahren", diff --git a/src/webapp/public/locales/en/translation.json b/src/webapp/public/locales/en/translation.json index d2e89adf1..d03941eb6 100644 --- a/src/webapp/public/locales/en/translation.json +++ b/src/webapp/public/locales/en/translation.json @@ -177,7 +177,8 @@ "rebooting": "Rebooting", "button-title": "Reboot", "description-confirm": "Are you sure you want to reboot now?", - "failed": "Reboot failed" + "failed": "Reboot failed", + "bye": "👋 Good bye" }, "shutdown": { "title": "Shut Down", From 13bf5e5ba399ca4e2813041dacb718a530584017 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:27:35 +0100 Subject: [PATCH 014/121] Future3/update dependencies (#2103) Update python venv Update Bookworm Update node to v20 (lts) and npm minor dependencies Update docker to all of the above --- .githooks/post-merge | 2 +- docker/armv7/jukebox.Dockerfile | 12 +- docker/docker-compose.yml | 2 +- docker/jukebox.Dockerfile | 10 +- docker/webapp.Dockerfile | 2 +- .../developers/development-environment.md | 2 +- .../content/userguide/installation.md | 35 +- installation/includes/03_welcome.sh | 15 +- installation/install-jukebox.sh | 4 +- installation/routines/customize_options.sh | 38 +- installation/routines/set_raspi_config.sh | 2 +- installation/routines/setup_jukebox_core.sh | 22 +- installation/routines/setup_jukebox_webapp.sh | 2 +- installation/routines/setup_rfid_reader.sh | 2 +- .../default-services/jukebox-daemon.service | 2 +- .../components/rfid/configure/__init__.py | 8 +- .../hardware/pn532_i2c_py532/requirements.txt | 2 +- .../rfid/hardware/rc522_spi/requirements.txt | 2 +- src/jukebox/jukebox/NvManager.py | 2 +- src/jukebox/jukebox/playlistgenerator.py | 2 +- src/jukebox/run_configure_audio.py | 2 +- src/jukebox/run_jukebox.py | 2 +- src/jukebox/run_publicity_sniffer.py | 2 +- src/jukebox/run_register_rfid_reader.py | 2 +- src/jukebox/run_rpc_tool.py | 2 +- src/webapp/package-lock.json | 5286 ++++++++++------- src/webapp/package.json | 34 +- 27 files changed, 3128 insertions(+), 2370 deletions(-) diff --git a/.githooks/post-merge b/.githooks/post-merge index 8b9a31e30..3b838c87c 100755 --- a/.githooks/post-merge +++ b/.githooks/post-merge @@ -43,7 +43,7 @@ warn_python_requirements() { echo "ATTENTION: Python requirements have changed since last pull!" echo "" echo "To update python requirements on the RPi run" - echo "$ sudo python3 -m pip install --upgrade -r requirements.txt" + echo "$ python -m pip install --upgrade -r requirements.txt" echo "************************************************************" echo -e "\n" } diff --git a/docker/armv7/jukebox.Dockerfile b/docker/armv7/jukebox.Dockerfile index b4f9168e2..a0e872325 100644 --- a/docker/armv7/jukebox.Dockerfile +++ b/docker/armv7/jukebox.Dockerfile @@ -25,7 +25,7 @@ RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ at wget gcc \ mpc mpg123 git ffmpeg spi-tools netcat alsa-tools \ - python3 python3-dev python3-pip python3-setuptools python3-mutagen python3-gpiozero + python3 python3-venv python3-dev python3-pip python3-setuptools python3-mutagen python3-gpiozero #samba samba-common-bin #raspberrypi-kernel-headers #resolvconf @@ -57,12 +57,16 @@ RUN mkdir -p ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ # zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts; \ # make && make install; +ENV VIRTUAL_ENV=/opt/venv +RUN python3 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + USER ${USER} WORKDIR ${HOME} COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ -RUN pip3 install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt -RUN pip3 install --no-cache-dir --pre --no-binary pyzmq pyzmq +RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt +RUN pip install --no-cache-dir --pre --no-binary pyzmq pyzmq EXPOSE 5555 5556 @@ -70,4 +74,4 @@ WORKDIR ${INSTALLATION_PATH}/src/jukebox # Run Jukebox # CMD bash -CMD python3 ${INSTALLATION_PATH}/src/jukebox/run_jukebox.py +CMD python ${INSTALLATION_PATH}/src/jukebox/run_jukebox.py diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index ed94e3139..b2216894e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -42,7 +42,7 @@ services: volumes: - ../shared:/root/RPi-Jukebox-RFID/shared - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf - command: python3 run_jukebox.py + command: python run_jukebox.py webapp: build: diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index 317ea81f9..a455ec9d2 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -25,14 +25,18 @@ RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ gcc at wget \ espeak mpc mpg123 git ffmpeg spi-tools netcat \ - python3 python3-dev python3-pip python3-mutagen python3-gpiozero + python3 python3-venv python3-dev python3-pip python3-mutagen python3-gpiozero + +ENV VIRTUAL_ENV=/opt/venv +RUN python3 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" USER ${USER} WORKDIR ${HOME} COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ -RUN pip3 install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt -RUN pip3 install pyzmq +RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt +RUN pip install pyzmq EXPOSE 5555 5556 diff --git a/docker/webapp.Dockerfile b/docker/webapp.Dockerfile index 88ea1813a..6c23d2abd 100644 --- a/docker/webapp.Dockerfile +++ b/docker/webapp.Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-bullseye-slim +FROM node:20-alpine USER node RUN mkdir -p /home/node/webapp diff --git a/documentation/content/developers/development-environment.md b/documentation/content/developers/development-environment.md index 6d60cdbd4..04689be2d 100644 --- a/documentation/content/developers/development-environment.md +++ b/documentation/content/developers/development-environment.md @@ -62,7 +62,7 @@ Github, which is taken care of by the installation script. For regular machines, the normal package can be installed: ``` bash -pip3 install pyzmq +pip install pyzmq ``` You will have to start Jukebox core application and the WebUI diff --git a/documentation/content/userguide/installation.md b/documentation/content/userguide/installation.md index ef448e98f..4b58ea4a9 100644 --- a/documentation/content/userguide/installation.md +++ b/documentation/content/userguide/installation.md @@ -8,15 +8,29 @@ Before you can install the Phoniebox software, you need to prepare your Raspberry Pi. 1. Connect a Micro SD card to your computer (preferable an SD card with high read throughput) -2. [Download](https://www.raspberrypi.org/software/) - the [Raspberry Pi Imager](https://www.raspberrypi.org/blog/raspberry-pi-imager-imaging-utility/) and open it -3. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system. future3 does not support 64bit kernels (`aarch64`). You can use the settings menu of the Raspberry Pi Imager to configure SSH and WiFi in a more userfriendly way, or do it manually as described in the next step. In case you already have a 64bit system installed, [you can fix the issue like this](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041>). -4. Select your Micro SD card (your card will be formatted) -5. Click *Write* -6. Wait for the imaging process to be finished (it'll take a few minutes) - - -## Pre-boot preparation +2. Download the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) and run it +3. Click on "Raspberry Pi Device" and select "No filtering" +4. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system. `future3` does not support 64bit kernels (`aarch64`). +5. Select your Micro SD card (your card will be formatted) +6. After you click `Next`, a prompt will ask you if you like to customize the OS settings + * Click `Edit Settings` + * Switch to the `General` tab + * Provide a hostname. (When on Mac, you will be able to use it to connect via SSH) + * Username currently MUST be `pi`. Other usernames are currently not supported. + * Password + * Wifi + * Set locale settings + * Switch to the `Services` tab. Enable SSH with "Use password authentication" + * Click `Save` +6. In the same dialog, click `Yes` +7. Confirm the next warning about erasing the SD card with `Yes` +8. Wait for the imaging process to be finished (it'll take a few minutes) + +
+ +In case you forgot to customize the OS settings, follow these instructions after RPi OS has been written to the SD card. + +### Pre-boot preparation You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to proceed with the next steps. @@ -62,6 +76,8 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to 9. Login into your Raspberry Pi, username is `pi` and password is `raspberry`. If `raspberrypi.local` does not work, find out your Raspberry Pi's IP address from your router. +
+ ## Install Phoniebox software Run the following command in your SSH terminal and follow the instructions @@ -90,4 +106,3 @@ If you suspect an error you can monitor the installation-process with ```bash cd; tail -f INSTALL-.log ``` - diff --git a/installation/includes/03_welcome.sh b/installation/includes/03_welcome.sh index 3ef15d811..1b2de1600 100644 --- a/installation/includes/03_welcome.sh +++ b/installation/includes/03_welcome.sh @@ -16,14 +16,15 @@ You are turning your Raspberry Pi into a Phoniebox. Good choice! Depending on your hardware, this installation might last -around 60 minutes. It updates OS packages, installs -Phoniebox dependencies and registers settings. Be patient -and don't let your computer go to sleep. It might -disconnect your SSH connection causing the interruption of -the installation process. +around 60 minutes (usually it's faster). It updates OS +packages, installs Phoniebox dependencies and registers +settings. Be patient and don't let your computer go to +sleep. It might disconnect your SSH connection causing the +interruption of the installation process. -By the way, we write a log file to: -${INSTALLATION_LOGFILE} +By the way, you can follow the installation details here +in a separate SSH session: +cd; tail -f ${INSTALLATION_LOGFILE} Let's set up your Phoniebox now?! [Y/n]" 1>&3 diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 57c81fb7d..44b03fca0 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -32,7 +32,7 @@ checkPrerequisite() { #currently the user 'pi' is mandatory #https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1785 if [ "${CURRENT_USER}" != "pi" ]; then - echo + echo echo "ERROR: User must be 'pi'!" echo " Other usernames are currently not supported." echo " Please check the wiki for further information" @@ -40,7 +40,7 @@ checkPrerequisite() { fi if [ "${HOME_PATH}" != "/home/pi" ]; then - echo + echo echo "ERROR: HomeDir must be '/home/pi'!" echo " Other usernames are currently not supported." echo " Please check the wiki for further information" diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index 5fe93c1b8..d12b42390 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -35,7 +35,7 @@ _option_autohotspot() { # ENABLE_AUTOHOTSPOT echo "Do you want to enable a WiFi hotspot on demand? When enabled, this service spins up a WiFi hotspot -when the Phonbox is unable to connect to a known +when the Phoniebox is unable to connect to a known WiFi. This way you can still access it. [y/N] " 1>&3 read -r response @@ -196,25 +196,29 @@ _option_webapp_devel_build() { if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = "release-only" ]]; then ENABLE_WEBAPP_PROD_DOWNLOAD=false fi - echo -e "Your are installing from a non-release branch. + + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = false ]]; then + echo -e "Your are installing from a non-release branch. This means, you will need to build the web app locally. For that you'll need Node. -Do you want to install Node now? [Y/n] " 1>&3 - read -r response - case "$response" in - [nN]) - ENABLE_INSTALL_NODE=false - ;; - *) - ;; - esac - # This message will be displayed at the end of the installation process - FIN_MESSAGE="$FIN_MESSAGE\n\nATTENTION: You need to build the web app locally with - $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u - This must be done after reboot, due to memory restrictions. - Read the documentation regarding local Web App builds!" - fi +Do you want to install Node? [Y/n] " 1>&3 + read -r response + case "$response" in + [nN]) + ENABLE_INSTALL_NODE=false + ;; + *) + ;; + esac + # This message will be displayed at the end of the installation process + FIN_MESSAGE="$FIN_MESSAGE\n\nATTENTION: You need to build the web app locally with + $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u + This must be done after reboot, due to memory restrictions. + Read the documentation regarding local Web App builds!" + ENABLE_WEBAPP_PROD_DOWNLOAD=false + fi + fi } customize_options() { diff --git a/installation/routines/set_raspi_config.sh b/installation/routines/set_raspi_config.sh index c43b73d1d..1b1c3ee55 100644 --- a/installation/routines/set_raspi_config.sh +++ b/installation/routines/set_raspi_config.sh @@ -6,7 +6,7 @@ set_raspi_config() { # Source: https://raspberrypi.stackexchange.com/a/66939 # Autologin - echo " * Enable Autologin for 'pi' user" + echo " * Enable Autologin for user" sudo raspi-config nonint do_boot_behaviour B2 # Wait for network at boot diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 7fec72ab6..ab5f0a9bd 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -23,7 +23,7 @@ _jukebox_core_install_os_dependencies() { sudo apt-get -y update; sudo apt-get -y install \ at \ alsa-utils \ - python3 python3-dev python3-pip python3-setuptools \ + python3 python3-venv python3-dev python3-pip python3-setuptools \ python3-rpi.gpio python3-gpiozero \ espeak ffmpeg mpg123 \ pulseaudio pulseaudio-module-bluetooth pulseaudio-utils caps \ @@ -33,14 +33,12 @@ _jukebox_core_install_os_dependencies() { --allow-remove-essential \ --allow-change-held-packages - # add configuration that we break the global python system packages - # (required for bookworm, see - # https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2050). This - # should be removed once the jukebox has been isolated to a dedicated - # venv - sudo python3 -m pip config set global.break-system-packages true + VIRTUAL_ENV="${HOME_PATH}/.venv" + python3 -m venv $VIRTUAL_ENV + PATH="$VIRTUAL_ENV/bin:$PATH" + source "$VIRTUAL_ENV/bin/activate" - sudo pip3 install --upgrade pip + pip install --upgrade pip } _jukebox_core_configure_pulseaudio() { @@ -86,7 +84,7 @@ _jukebox_core_build_and_install_pyzmq() { # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md echo " Build and install pyzmq with WebSockets Support" - if ! sudo pip3 list | grep -F pyzmq >> /dev/null; then + if ! pip list | grep -F pyzmq >> /dev/null; then # Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it echo " Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it" @@ -106,8 +104,8 @@ _jukebox_core_build_and_install_pyzmq() { _jukebox_core_download_prebuilt_libzmq_with_drafts fi - sudo ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ - pip3 install --no-cache-dir --no-binary "pyzmq" --pre pyzmq + ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ + pip install --no-cache-dir --no-binary "pyzmq" --pre pyzmq else echo " Skipping. pyzmq already installed" fi @@ -116,7 +114,7 @@ _jukebox_core_build_and_install_pyzmq() { _jukebox_core_install_python_requirements() { echo " Install requirements" cd "${INSTALLATION_PATH}" || exit_on_error - sudo pip3 install --no-cache-dir -r "${INSTALLATION_PATH}/requirements.txt" + pip install --no-cache-dir -r "${INSTALLATION_PATH}/requirements.txt" } _jukebox_core_install_settings() { diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index def263932..a2f9e014a 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -4,7 +4,7 @@ GD_ID_COMPILED_WEBAPP="1EE_1MdneGtKL5V7GyYZC0nb6ODQWTsPb" # https://drive.google.com/file/d/1EE_1MdneGtKL5V7GyYZC0nb6ODQWTsPb/view?usp=sharing # For ARMv7+ -NODE_MAJOR=16 +NODE_MAJOR=20 # For ARMv6 # To update version, follow these links # https://github.com/sdesalas/node-pi-zero diff --git a/installation/routines/setup_rfid_reader.sh b/installation/routines/setup_rfid_reader.sh index e52c9067d..4ae693076 100644 --- a/installation/routines/setup_rfid_reader.sh +++ b/installation/routines/setup_rfid_reader.sh @@ -3,7 +3,7 @@ setup_rfid_reader() { echo "Install RFID Reader" | tee /dev/fd/3 - python3 "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" | tee /dev/fd/3 + python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" | tee /dev/fd/3 echo "DONE: setup_rfid_reader" } diff --git a/resources/default-services/jukebox-daemon.service b/resources/default-services/jukebox-daemon.service index cbdce1adb..f2557480e 100644 --- a/resources/default-services/jukebox-daemon.service +++ b/resources/default-services/jukebox-daemon.service @@ -10,8 +10,8 @@ After=network.target sound.target mpd.service pulseaudio.service Requires=mpd.service pulseaudio.service [Service] -ExecStart=/usr/bin/python3 run_jukebox.py WorkingDirectory=/home/pi/RPi-Jukebox-RFID/src/jukebox +ExecStart=/bin/bash -c 'source /home/pi/.venv/bin/activate && /home/pi/.venv/bin/python run_jukebox.py' StandardOutput=inherit StandardError=inherit Restart=always diff --git a/src/jukebox/components/rfid/configure/__init__.py b/src/jukebox/components/rfid/configure/__init__.py index ac48dfaa0..bf8dec4a9 100755 --- a/src/jukebox/components/rfid/configure/__init__.py +++ b/src/jukebox/components/rfid/configure/__init__.py @@ -27,17 +27,17 @@ def reader_install_dependencies(reader_path: str, dependency_install: str) -> No # The python dependencies (if any) print("\nInstalling/Checking Python dependencies ...\n") print("IMPORTANT for developers: Python dependencies will be installed using " - " $ sudo pip3 install --upgrade -r requirements.txt'\n" + " $ pip install --upgrade -r requirements.txt'\n" " i.e. on system level. This is target for the default RPI setup. " "If you do not want that, but rather have them in a local or virtual environment, " "hit No here and manually install the dependencies from your virtual environment\n" - " $ pip3 install --upgrade -r requirements.txt'\n" + " $ pip install --upgrade -r requirements.txt'\n" "It is no problem to install them after running this script.\n\n") if dependency_install == 'auto' or pyil.input_yesno("Install Python dependencies?", blank=True, prompt_color=Colors.lightgreen, prompt_hint=True): print(f"{'=' * 80}") quiet_level = '-q' if logger.isEnabledFor(logging.DEBUG) else '' - subprocess.run(f"sudo pip3 install --upgrade {quiet_level} -r requirements.txt", cwd=reader_path, + subprocess.run(f"pip install --upgrade {quiet_level} -r requirements.txt", cwd=reader_path, shell=True, check=False) print(f"\n{'=' * 80}\nInstalling dependencies ... done!") if os.path.exists(reader_path + '/setup.inc.sh'): @@ -78,7 +78,7 @@ def reader_load_module(reader_name): "If this script is called with -d a, an attempt will be made to install the dependencies " "automatically\n" "You may install the dependencies manually before re-executing this script by:\n" - "'$ pip3 install -r requirements.txt' in the reader's submodule directory and \n" + "'$ pip install -r requirements.txt' in the reader's submodule directory and \n" "'$ ./setup.inc.sh'\n" "In case of doubt reboot!\n\n" f"{'=' * 80}\n") diff --git a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt index 26f7bf884..9b156854b 100644 --- a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt +++ b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt @@ -1,3 +1,3 @@ # PN532 related requirements -# You need to install these with `sudo python3 -m pip install --upgrade --force-reinstall -q -r requirements.txt` +# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` py532lib diff --git a/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt b/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt index 78a3b459a..56b9ca4dc 100644 --- a/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt +++ b/src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt @@ -1,5 +1,5 @@ # RC522 related requirements -# You need to install these with `sudo python3 -m pip install --upgrade --force-reinstall -q -r requirements.txt` +# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` pi-rc522==2.3.0 diff --git a/src/jukebox/jukebox/NvManager.py b/src/jukebox/jukebox/NvManager.py index 24389a9ec..c49a3222e 100644 --- a/src/jukebox/jukebox/NvManager.py +++ b/src/jukebox/jukebox/NvManager.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # -*- coding: utf-8 -*- import json import os diff --git a/src/jukebox/jukebox/playlistgenerator.py b/src/jukebox/jukebox/playlistgenerator.py index 13dcc0f7d..db64d3eff 100755 --- a/src/jukebox/jukebox/playlistgenerator.py +++ b/src/jukebox/jukebox/playlistgenerator.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ Playlists are build from directory content in the following way: a directory is parsed and files are added to the playlist in the following way diff --git a/src/jukebox/run_configure_audio.py b/src/jukebox/run_configure_audio.py index 093b46b4e..6bb0e70f6 100755 --- a/src/jukebox/run_configure_audio.py +++ b/src/jukebox/run_configure_audio.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. diff --git a/src/jukebox/run_jukebox.py b/src/jukebox/run_jukebox.py index 2af0fc86a..0735e2b8e 100755 --- a/src/jukebox/run_jukebox.py +++ b/src/jukebox/run_jukebox.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ This is the main app and starts the Jukebox Core. diff --git a/src/jukebox/run_publicity_sniffer.py b/src/jukebox/run_publicity_sniffer.py index d3e28f28b..f56e5abe4 100755 --- a/src/jukebox/run_publicity_sniffer.py +++ b/src/jukebox/run_publicity_sniffer.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ A command line tool that monitors all messages being sent out from the Jukebox via the publishing interface. Received messages are printed in the console. diff --git a/src/jukebox/run_register_rfid_reader.py b/src/jukebox/run_register_rfid_reader.py index 1df80364c..3aa69735e 100755 --- a/src/jukebox/run_register_rfid_reader.py +++ b/src/jukebox/run_register_rfid_reader.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ Setup tool to configure the RFID Readers. diff --git a/src/jukebox/run_rpc_tool.py b/src/jukebox/run_rpc_tool.py index 9a7cc6a0a..5f84702d4 100755 --- a/src/jukebox/run_rpc_tool.py +++ b/src/jukebox/run_rpc_tool.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ Command Line Interface to the Jukebox RPC Server diff --git a/src/webapp/package-lock.json b/src/webapp/package-lock.json index 328fa269e..814090555 100644 --- a/src/webapp/package-lock.json +++ b/src/webapp/package-lock.json @@ -8,32 +8,51 @@ "name": "webapp", "version": "0.1.0", "dependencies": { - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.12.0", - "@mui/styles": "^5.12.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^12.1.2", - "@testing-library/user-event": "^14.4.3", - "i18next": "^22.4.14", - "i18next-browser-languagedetector": "^7.0.1", - "i18next-http-backend": "^2.2.0", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.14.16", + "@mui/material": "^5.14.17", + "@mui/styles": "^5.14.17", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.5.1", + "i18next": "^22.5.1", + "i18next-browser-languagedetector": "^7.2.0", + "i18next-http-backend": "^2.4.1", "jszmq": "^0.1.2", - "ramda": "^0.29.0", + "ramda": "^0.29.1", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-i18next": "^12.2.0", - "react-router-dom": "^6.10.0", - "react-scripts": "5.0.0", - "url": "^0.11.0", - "uuid": "^9.0.0" + "react-i18next": "^12.3.1", + "react-router-dom": "^6.18.0", + "react-scripts": "^5.0.1", + "url": "^0.11.3", + "uuid": "^9.0.1" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/@adobe/css-tools": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", - "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", + "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/@ampproject/remapping": { "version": "2.2.1", @@ -48,44 +67,109 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", - "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", - "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-compilation-targets": "^7.21.4", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.4", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.4", - "@babel/types": "^7.21.4", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.3", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -95,28 +179,33 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/eslint-parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.21.3.tgz", - "integrity": "sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.23.3.tgz", + "integrity": "sha512-9bTuNlyx7oSstodm1cR1bECj4fkiknsDa1YniISkJemMY3DGhJNYBECbe6QD/q54mp2J8VO66jW3/7uP//iFCw==", "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || >=14.0.0" }, "peerDependencies": { - "@babel/core": ">=7.11.0", + "@babel/core": "^7.11.0", "eslint": "^7.5.0 || ^8.0.0" } }, @@ -129,19 +218,19 @@ } }, "node_modules/@babel/eslint-parser/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dependencies": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -151,67 +240,64 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", - "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dependencies": { - "@babel/compat-data": "^7.21.4", - "@babel/helper-validator-option": "^7.21.0", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz", - "integrity": "sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-member-expression-to-functions": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/helper-split-export-declaration": "^7.18.6" + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -220,13 +306,22 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz", - "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.3.1" + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -235,140 +330,127 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.14.2" }, "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dependencies": { - "@babel/types": "^7.18.6" - }, + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", - "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dependencies": { - "@babel/types": "^7.21.0" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", - "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dependencies": { - "@babel/types": "^7.21.4" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -378,112 +460,111 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dependencies": { - "@babel/types": "^7.20.2" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "dependencies": { - "@babel/types": "^7.20.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -555,9 +636,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -566,11 +647,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -580,13 +661,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -595,27 +676,26 @@ "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -627,92 +707,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", - "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.21.0.tgz", - "integrity": "sha512-MfgX49uRrFUTL/HvWtmx3zmpyzMMr4MTj3d527MLlr/4RTT9G/ytFFP7qet2uM2Ve03b+BkpWUpK+lRXnQ+v9w==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/plugin-syntax-decorators": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.3.tgz", + "integrity": "sha512-u8SwzOcP0DYSsa++nHd/9exlHb0NAlHCb890qtZZbSwPX2bFv8LBEztxwN7Xg/dS8oAFFidhrI9PBcLBJSkGRQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/plugin-syntax-decorators": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -725,6 +729,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -740,6 +745,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -751,43 +757,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", @@ -804,6 +778,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -816,15 +791,9 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", - "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "engines": { "node": ">=6.9.0" }, @@ -832,21 +801,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -895,11 +849,11 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.21.0.tgz", - "integrity": "sha512-tIoPpGBR8UuM4++ccWN3gifhVvQu7ZizuR1fklhRJrd5ewgbkUS+0KVFeWWxELtn18NTLoW32XV7zyOgIAiz+w==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.23.3.tgz", + "integrity": "sha512-cf7Niq4/+/juY67E0PbgH0TDhLQ5J7zS8C/Q5FFx+DWyrRa9sUQdTXkjqKu8zGvuqr7vw1muKiukseihU+PJDA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -931,11 +885,11 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz", - "integrity": "sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", + "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -945,11 +899,25 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -981,11 +949,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", - "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1005,67 +973,296 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", + "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", + "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", + "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", + "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1074,12 +1271,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", + "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1088,12 +1286,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", - "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1102,12 +1301,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", + "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1116,14 +1316,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", + "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1132,12 +1331,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1146,12 +1345,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", - "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1160,20 +1361,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", - "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", + "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1182,13 +1376,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1197,12 +1390,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", - "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", + "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { "node": ">=6.9.0" @@ -1211,13 +1405,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1226,12 +1419,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1240,13 +1434,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1255,13 +1450,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz", - "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-flow": "^7.18.6" + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1270,12 +1467,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz", - "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1284,28 +1482,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1314,12 +1511,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", + "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1328,13 +1526,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", + "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { "node": ">=6.9.0" @@ -1343,14 +1541,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", - "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", + "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", "dependencies": { - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1359,15 +1559,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1376,13 +1574,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", + "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1391,27 +1589,28 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", + "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1420,13 +1619,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1435,12 +1634,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", - "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", + "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -1450,11 +1652,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1464,11 +1666,11 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.21.3.tgz", - "integrity": "sha512-4DVcFeWe/yDYBLp0kBmOGFJ6N2UYg7coGid1gdxb4co62dy/xISDMaYBXBVXEDhfgMk7qkbcYiGtwd5Q/hwDDQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.23.3.tgz", + "integrity": "sha512-zP0QKq/p6O42OL94udMgSfKXyse4RyJ0JqbQ34zDAONWjyrEsghYEyTSK5FIpmXmCpB55SHokL1cRRKHv8L2Qw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1478,11 +1680,11 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", + "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1492,15 +1694,15 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", - "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz", + "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.21.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -1510,11 +1712,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.18.6" + "@babel/plugin-transform-react-jsx": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1524,12 +1726,12 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz", + "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1539,12 +1741,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" }, "engines": { "node": ">=6.9.0" @@ -1554,11 +1756,11 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1568,16 +1770,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz", - "integrity": "sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.3.tgz", + "integrity": "sha512-XcQ3X58CKBdBnnZpPaQjgVMePsXtSZzHoku70q9tUAQp02ggPQNM04BF3RvlW1GSM/McbSOQAzEK4MXbS7/JFg==", "dependencies": { - "@babel/helper-module-imports": "^7.21.4", - "@babel/helper-plugin-utils": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1587,19 +1789,19 @@ } }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1609,12 +1811,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1624,11 +1826,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1638,11 +1840,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1652,11 +1854,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1666,14 +1868,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz", - "integrity": "sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.3.tgz", + "integrity": "sha512-ogV0yWnq38CFwH20l2Afz0dfKuZBx9o/Y2Rmh5vuSS0YD1hswgEgTfyTzuSrT2q9btmHRSqYoSfwFUVaC1M1Jw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1683,11 +1885,26 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1697,12 +1914,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1711,38 +1928,42 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.4.tgz", - "integrity": "sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==", + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", "dependencies": { - "@babel/compat-data": "^7.21.4", - "@babel/helper-compilation-targets": "^7.21.4", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", - "@babel/plugin-proposal-async-generator-functions": "^7.20.7", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.21.0", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.21.0", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.21.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", + "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -1752,45 +1973,61 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.20.7", - "@babel/plugin-transform-async-to-generator": "^7.20.7", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.21.0", - "@babel/plugin-transform-classes": "^7.21.0", - "@babel/plugin-transform-computed-properties": "^7.20.7", - "@babel/plugin-transform-destructuring": "^7.21.3", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.21.0", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.20.11", - "@babel/plugin-transform-modules-commonjs": "^7.21.2", - "@babel/plugin-transform-modules-systemjs": "^7.20.11", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.21.3", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.20.5", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.20.7", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.21.4", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.3", + "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.3", + "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", + "@babel/plugin-transform-numeric-separator": "^7.23.3", + "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.3", + "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1800,39 +2037,37 @@ } }, "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz", + "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-react-display-name": "^7.23.3", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1842,15 +2077,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.4.tgz", - "integrity": "sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", + "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-syntax-jsx": "^7.21.4", - "@babel/plugin-transform-modules-commonjs": "^7.21.2", - "@babel/plugin-transform-typescript": "^7.21.3" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1865,42 +2100,42 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1909,12 +2144,12 @@ } }, "node_modules/@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2197,65 +2432,65 @@ } }, "node_modules/@emotion/babel-plugin": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", - "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", - "stylis": "4.1.3" + "stylis": "4.2.0" } }, "node_modules/@emotion/cache": { - "version": "11.10.7", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.7.tgz", - "integrity": "sha512-VLl1/2D6LOjH57Y8Vem1RoZ9haWF4jesHDGiHtKozDQuBIkJm2gimVo0I02sWCuzZtVACeixTVB4jeE8qvCBoQ==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", "dependencies": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" } }, "node_modules/@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", "dependencies": { - "@emotion/memoize": "^0.8.0" + "@emotion/memoize": "^0.8.1" } }, "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", - "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -2268,33 +2503,33 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", "dependencies": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, "node_modules/@emotion/styled": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz", - "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -2307,27 +2542,27 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", @@ -2344,21 +2579,21 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -2379,9 +2614,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dependencies": { "type-fest": "^0.20.2" }, @@ -2415,19 +2650,53 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -2448,9 +2717,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -2571,9 +2840,9 @@ } }, "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -2675,9 +2944,9 @@ } }, "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -2747,19 +3016,19 @@ } }, "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2797,9 +3066,9 @@ } }, "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -2868,9 +3137,9 @@ } }, "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -3011,9 +3280,9 @@ } }, "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -3043,11 +3312,11 @@ } }, "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3104,9 +3373,9 @@ } }, "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -3166,9 +3435,9 @@ } }, "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -3198,11 +3467,11 @@ } }, "node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -3227,9 +3496,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "engines": { "node": ">=6.0.0" } @@ -3243,9 +3512,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -3257,37 +3526,31 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, "node_modules/@mui/base": { - "version": "5.0.0-alpha.125", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.125.tgz", - "integrity": "sha512-hAHJJ97SATu6SrkLH/HsAayK1zMZt89lrWyKuAInBKVyn363H78d1MnwyZwre9vDK5MrPoDL/NnZxtAXhwTnBA==", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.12.0", - "@popperjs/core": "^2.11.7", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "version": "5.0.0-beta.23", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.23.tgz", + "integrity": "sha512-9L8SQUGAWtd/Qi7Qem26+oSSgpY7f2iQTuvcz/rsGpyZjSomMMO6lwYeQSA0CpWM7+aN7eGoSY/WV6wxJiIxXw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" }, "engines": { "node": ">=12.0.0" @@ -3308,20 +3571,20 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.0.tgz", - "integrity": "sha512-1hoFIdlLI0sG+mkJgm70FjgIVpfLcE1vxPtNolg1tLFXrvbXGUYp9NHy3d6c41nDkg2OajuVS+Mn6A8UirFuMw==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.17.tgz", + "integrity": "sha512-eE0uxrpJAEL2ZXkeGLKg8HQDafsiXY+6eNpP4lcv3yIjFfGbU6Hj9/P7Adt8jpU+6JIhmxvILGj2r27pX+zdrQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" } }, "node_modules/@mui/icons-material": { - "version": "5.11.16", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.16.tgz", - "integrity": "sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==", + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.16.tgz", + "integrity": "sha512-wmOgslMEGvbHZjFLru8uH5E+pif/ciXAvKNw16q6joK6EWVWU5rDYWFknDaZhCvz8ZE/K8ZnJQ+lMG6GgHzXbg==", "dependencies": { - "@babel/runtime": "^7.21.0" + "@babel/runtime": "^7.23.2" }, "engines": { "node": ">=12.0.0" @@ -3342,18 +3605,18 @@ } }, "node_modules/@mui/material": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.12.0.tgz", - "integrity": "sha512-IMellv153zJ6+xfhLWgXpAm/9hsX8qE6gP66xWcW/Pf2B8ubyVhmkTXsp8pAJxk81D6p/EyYcnAjo5DiDVkj9g==", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@mui/base": "5.0.0-alpha.125", - "@mui/core-downloads-tracker": "^5.12.0", - "@mui/system": "^5.12.0", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.12.0", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.17.tgz", + "integrity": "sha512-+y0VeOLWfEA4Z98We/UH6KCo8+f2HLZDK45FY+sJf8kSojLy3VntadKtC/u0itqnXXb1Pr4wKB2tSIBW02zY4Q==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "5.0.0-beta.23", + "@mui/core-downloads-tracker": "^5.14.17", + "@mui/system": "^5.14.17", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1", "react-is": "^18.2.0", @@ -3386,12 +3649,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.12.0.tgz", - "integrity": "sha512-w5dwMen1CUm1puAtubqxY9BIzrBxbOThsg2iWMvRJmWyJAPdf3Z583fPXpqeA2lhTW79uH2jajk5Ka4FuGlTPg==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.17.tgz", + "integrity": "sha512-u4zxsCm9xmQrlhVPug+Ccrtsjv7o2+rehvrgHoh0siSguvVgVQq5O3Hh10+tp/KWQo2JR4/nCEwquSXgITS1+g==", "dependencies": { - "@babel/runtime": "^7.21.0", - "@mui/utils": "^5.12.0", + "@babel/runtime": "^7.23.2", + "@mui/utils": "^5.14.17", "prop-types": "^15.8.1" }, "engines": { @@ -3412,12 +3675,12 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.12.0.tgz", - "integrity": "sha512-frh8L7CRnvD0RDmIqEv6jFeKQUIXqW90BaZ6OrxJ2j4kIsiVLu29Gss4SbBvvrWwwatR72sBmC3w1aG4fjp9mQ==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.17.tgz", + "integrity": "sha512-AqpVjBEA7wnBvKPW168bNlqB6EN7HxTjLOY7oi275AzD/b1C7V0wqELy6NWoJb2yya5sRf7ENf4iNi3+T5cOgw==", "dependencies": { - "@babel/runtime": "^7.21.0", - "@emotion/cache": "^11.10.7", + "@babel/runtime": "^7.23.2", + "@emotion/cache": "^11.11.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" }, @@ -3443,16 +3706,16 @@ } }, "node_modules/@mui/styles": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.12.0.tgz", - "integrity": "sha512-X7obkgZTd9X+7igqwKKe8pEncyXYdUCNmyJfHruV9TSc6LThoI29OYs6hkN6n+7ueNli+YDKdZ+TCoC1GpJuOw==", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@emotion/hash": "^0.9.0", - "@mui/private-theming": "^5.12.0", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.12.0", - "clsx": "^1.2.1", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.14.17.tgz", + "integrity": "sha512-CsftJtCxF3ABigNXikmBJEsI6XQDmeTQNmjaA6wbjYA+29xPMUEQD4Coe+VXGkcc2jrE6Vy8qZ3ytO8QaM9KAw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@emotion/hash": "^0.9.1", + "@mui/private-theming": "^5.14.17", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "clsx": "^2.0.0", "csstype": "^3.1.2", "hoist-non-react-statics": "^3.3.2", "jss": "^10.10.0", @@ -3473,7 +3736,7 @@ "url": "https://opencollective.com/mui" }, "peerDependencies": { - "@types/react": "^17.0.0", + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0" }, "peerDependenciesMeta": { @@ -3483,16 +3746,16 @@ } }, "node_modules/@mui/system": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.12.0.tgz", - "integrity": "sha512-Zi+WHuiJfK1ya+9+oeJQ1rLIBdY8CGDYT5oVlQg/6kIuyiCaE6SnN9PVzxBxfY77wHuOPwz4kxcPe9srdZc12Q==", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@mui/private-theming": "^5.12.0", - "@mui/styled-engine": "^5.12.0", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.12.0", - "clsx": "^1.2.1", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.17.tgz", + "integrity": "sha512-Ccz3XlbCqka6DnbHfpL3o3TfOeWQPR+ewvNAgm8gnS9M0yVMmzzmY6z0w/C1eebb+7ZP7IoLUj9vojg/GBaTPg==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/private-theming": "^5.14.17", + "@mui/styled-engine": "^5.14.17", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" }, @@ -3522,11 +3785,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", + "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -3535,13 +3798,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.12.0.tgz", - "integrity": "sha512-RmQwgzF72p7Yr4+AAUO6j1v2uzt6wr7SWXn68KBsnfVpdOHyclCzH2lr/Xu6YOw9su4JRtdAIYfJFXsS6Cjkmw==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.17.tgz", + "integrity": "sha512-yxnWgSS4J6DMFPw2Dof85yBkG02VTbEiqsikymMsnZnXDurtVGTIhlNuV24GTmFTuJMzEyTTU9UF+O7zaL8LEQ==", "dependencies": { - "@babel/runtime": "^7.21.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.2", + "@types/prop-types": "^15.7.9", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3553,7 +3815,13 @@ "url": "https://opencollective.com/mui" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { @@ -3617,9 +3885,9 @@ } }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz", - "integrity": "sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", + "integrity": "sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==", "dependencies": { "ansi-html-community": "^0.0.8", "common-path-prefix": "^3.0.0", @@ -3638,7 +3906,7 @@ "@types/webpack": "4.x || 5.x", "react-refresh": ">=0.10.0 <1.0.0", "sockjs-client": "^1.4.0", - "type-fest": ">=0.17.0 <4.0.0", + "type-fest": ">=0.17.0 <5.0.0", "webpack": ">=4.43.0 <6.0.0", "webpack-dev-server": "3.x || 4.x", "webpack-hot-middleware": "2.x", @@ -3674,20 +3942,20 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.7", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", - "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, "node_modules/@remix-run/router": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", - "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz", + "integrity": "sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==", "engines": { - "node": ">=14" + "node": ">=14.0.0" } }, "node_modules/@rollup/plugin-babel": { @@ -3765,14 +4033,14 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", + "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==" }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "node_modules/@sinonjs/commons": { "version": "1.8.6", @@ -4009,15 +4277,15 @@ } }, "node_modules/@testing-library/dom": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz", - "integrity": "sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", + "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", - "aria-query": "^5.0.0", + "aria-query": "5.1.3", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", @@ -4028,9 +4296,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", + "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", "dependencies": { "@adobe/css-tools": "^4.0.1", "@babel/runtime": "^7.9.2", @@ -4078,17 +4346,17 @@ } }, "node_modules/@testing-library/react/node_modules/@testing-library/dom": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", - "integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", - "aria-query": "^5.0.0", + "aria-query": "5.1.3", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", + "lz-string": "^1.5.0", "pretty-format": "^27.0.2" }, "engines": { @@ -4096,9 +4364,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.4.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", - "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", + "version": "14.5.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", + "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", "engines": { "node": ">=12", "npm": ">=6" @@ -4124,14 +4392,14 @@ } }, "node_modules/@types/aria-query": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==" }, "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz", + "integrity": "sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -4141,91 +4409,91 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.6.7", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", + "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", + "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.3.tgz", + "integrity": "sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA==", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "node_modules/@types/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "version": "8.44.7", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", + "integrity": "sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -4234,19 +4502,20 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.33", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", - "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", "dependencies": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" } }, "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dependencies": { "@types/node": "*" } @@ -4256,39 +4525,44 @@ "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, "node_modules/@types/http-proxy": { - "version": "1.17.10", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz", - "integrity": "sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g==", + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "version": "29.5.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", + "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -4306,11 +4580,11 @@ } }, "node_modules/@types/jest/node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -4319,9 +4593,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -4329,49 +4603,60 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==" + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.9.tgz", + "integrity": "sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==" + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.10", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz", + "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==" }, "node_modules/@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", + "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==" }, "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" }, "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "17.0.58", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.58.tgz", - "integrity": "sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==", + "version": "17.0.70", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.70.tgz", + "integrity": "sha512-yqYMK49/cnqw+T8R9/C+RNjRddYmPDGI5lKHi3bOYceQCBAh8X2ngSbZP0gnVeyvHr0T7wEgIIGKT1usNol08w==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4379,25 +4664,17 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", - "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.23.tgz", + "integrity": "sha512-lnJAZfMEDxfvELeeT24w4rnUYwpzUzQAOTfJQbWYnLcx8AEfz+fXJDCbowIBqNK/Bi4D6j8ovT8Qsda2OtDApA==", "dependencies": { "@types/react": "^17" } }, - "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.9.tgz", + "integrity": "sha512-ZVNmWumUIh5NhH8aMD9CR2hdW0fNuYInlocZHaZ+dgk/1K49j1w/HoAuK1ki+pgscQrOFRTlXeoURtuzEkV3dg==", "dependencies": { "@types/react": "*" } @@ -4416,90 +4693,100 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", + "integrity": "sha512-Vlktnchmkylvc9SnwwwozTv04L/e1NykF5vgoQ0XTmI8DD+wxfjQuHuvHS3p0r2jz2x2ghPs2h1FVeDirIteWA==" }, "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", "dependencies": { + "@types/http-errors": "*", "@types/mime": "*", "@types/node": "*" } }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "version": "5.14.9", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", + "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", "dependencies": { "@types/jest": "*" } }, "node_modules/@types/trusted-types": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", - "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.6.tgz", + "integrity": "sha512-HYtNooPvUY9WAVRBr4u+4Qa9fYD1ze2IUlAD3HoA6oehn1taGwBx3Oa52U4mTslTS+GAExKpaFu39Y5xUEwfjg==" }, "node_modules/@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", + "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", + "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", - "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.58.0", - "@typescript-eslint/type-utils": "5.58.0", - "@typescript-eslint/utils": "5.58.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", @@ -4523,11 +4810,11 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.58.0.tgz", - "integrity": "sha512-LA/sRPaynZlrlYxdefrZbMx8dqs/1Kc0yNG+XOk5CwwZx7tTv263ix3AJNioF0YBVt7hADpAUR20owl6pv4MIQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", "dependencies": { - "@typescript-eslint/utils": "5.58.0" + "@typescript-eslint/utils": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4541,13 +4828,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz", - "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dependencies": { - "@typescript-eslint/scope-manager": "5.58.0", - "@typescript-eslint/types": "5.58.0", - "@typescript-eslint/typescript-estree": "5.58.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "engines": { @@ -4567,12 +4854,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz", - "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dependencies": { - "@typescript-eslint/types": "5.58.0", - "@typescript-eslint/visitor-keys": "5.58.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4583,12 +4870,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz", - "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dependencies": { - "@typescript-eslint/typescript-estree": "5.58.0", - "@typescript-eslint/utils": "5.58.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -4609,9 +4896,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz", - "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4621,12 +4908,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz", - "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dependencies": { - "@typescript-eslint/types": "5.58.0", - "@typescript-eslint/visitor-keys": "5.58.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4647,16 +4934,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz", - "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.58.0", - "@typescript-eslint/types": "5.58.0", - "@typescript-eslint/typescript-estree": "5.58.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -4692,11 +4979,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz", - "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dependencies": { - "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -4707,134 +4994,139 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -4866,9 +5158,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "bin": { "acorn": "bin/acorn" }, @@ -4897,9 +5189,9 @@ } }, "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "peerDependencies": { "acorn": "^8" } @@ -5113,14 +5405,14 @@ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" }, "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "is-string": "^1.0.7" }, "engines": { @@ -5138,14 +5430,32 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -5156,13 +5466,13 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -5173,13 +5483,13 @@ } }, "node_modules/array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.6.tgz", + "integrity": "sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-array-method-boxes-properly": "^1.0.0", "is-string": "^1.0.7" }, @@ -5191,15 +5501,35 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", + "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.1.3" + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/asap": { @@ -5208,25 +5538,34 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" } }, "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==" }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dependencies": { + "has-symbols": "^1.0.3" + } }, "node_modules/asynckit": { "version": "0.4.0", @@ -5242,9 +5581,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "version": "10.4.16", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", "funding": [ { "type": "opencollective", @@ -5253,12 +5592,16 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -5285,19 +5628,19 @@ } }, "node_modules/axe-core": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", - "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", "engines": { "node": ">=4" } }, "node_modules/axobject-query": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", - "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/babel-jest": { @@ -5337,9 +5680,9 @@ } }, "node_modules/babel-jest/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -5431,47 +5774,47 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", + "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.33.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" + "@babel/helper-define-polyfill-provider": "^0.4.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-transform-react-remove-prop-types": { @@ -5569,13 +5912,14 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" }, "node_modules/bfj": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", - "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz", + "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==", "dependencies": { - "bluebird": "^3.5.5", - "check-types": "^11.1.1", + "bluebird": "^3.7.2", + "check-types": "^11.2.3", "hoopy": "^0.1.4", + "jsonpath": "^1.1.1", "tryer": "^1.0.1" }, "engines": { @@ -5658,6 +6002,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bonjour-service": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", @@ -5700,9 +6058,9 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "funding": [ { "type": "opencollective", @@ -5711,13 +6069,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -5782,12 +6144,13 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5841,9 +6204,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001478", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz", - "integrity": "sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw==", + "version": "1.0.30001561", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", + "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", "funding": [ { "type": "opencollective", @@ -5891,9 +6254,9 @@ } }, "node_modules/check-types": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", - "integrity": "sha512-HBiYvXvn9Z70Z88XKjz3AEKd4HJhBXsa3j7xFnITAzoS8+q6eIGi8qDB8FKPBAjtuxjI/zFpwuiCb8oDtKOYrA==" + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", + "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==" }, "node_modules/chokidar": { "version": "3.5.3", @@ -5941,9 +6304,9 @@ } }, "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "funding": [ { "type": "github", @@ -5955,9 +6318,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" }, "node_modules/clean-css": { "version": "5.3.2", @@ -5989,9 +6352,9 @@ } }, "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", "engines": { "node": ">=6" } @@ -6083,9 +6446,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==" }, "node_modules/color-convert": { "version": "2.0.1", @@ -6109,9 +6472,9 @@ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -6252,9 +6615,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-js": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.0.tgz", - "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.2.tgz", + "integrity": "sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6262,11 +6625,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.0.tgz", - "integrity": "sha512-P5A2h/9mRYZFIAP+5Ab8ns6083IyVpSclU74UNvbGVQ8VM7n3n3/g2yF3AkKQ9NXz2O+ioxLbEWKnDtgsFamhg==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", + "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", "dependencies": { - "browserslist": "^4.21.5" + "browserslist": "^4.22.1" }, "funding": { "type": "opencollective", @@ -6274,9 +6637,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.30.0.tgz", - "integrity": "sha512-+2KbMFGeBU0ln/csoPqTe0i/yfHbrd2EUhNMObsGtXMKS/RTtlkYyi+/3twLcevbgNR0yM/r0Psa3TEoQRpFMQ==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.33.2.tgz", + "integrity": "sha512-a8zeCdyVk7uF2elKIGz67AjcXOxjRbwOLz8SbklEso1V+2DoW4OkAMZN9S9GBgvZIaqQi/OemFX4OiSoQEmg1Q==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6304,11 +6667,11 @@ } }, "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dependencies": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.12" } }, "node_modules/cross-spawn": { @@ -6350,9 +6713,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", - "integrity": "sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", "engines": { "node": "^10 || ^12 || >=14" }, @@ -6378,14 +6741,14 @@ } }, "node_modules/css-loader": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", - "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.19", + "postcss": "^8.4.21", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-local-by-default": "^4.0.3", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", @@ -6471,14 +6834,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -6576,13 +6939,19 @@ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" }, "node_modules/cssdb": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.5.4.tgz", - "integrity": "sha512-fGD+J6Jlq+aurfE1VDXlLS4Pt0VtNlu2+YgfGOdMxRyl/HQ9bDiHTwSck1Yz8A97Dt/82izSK6Bp/4nVqacOsg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.9.0.tgz", + "integrity": "sha512-WPMT9seTQq6fPAa1yN4zjgZZeoTriSN2LqW9C+otjar12DQIWA4LuSfFrvFJiKp4oD0xIk1vumDLw8K9ur4NBw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ] }, "node_modules/cssesc": { "version": "3.0.0", @@ -6799,15 +7168,16 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" }, "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dependencies": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", + "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", @@ -6815,11 +7185,14 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6849,6 +7222,19 @@ "node": ">= 10" } }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -6858,10 +7244,11 @@ } }, "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -6888,6 +7275,14 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -6945,9 +7340,9 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -6974,9 +7369,9 @@ "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" }, "node_modules/dns-packet": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.5.0.tgz", - "integrity": "sha512-USawdAUzRkV6xrqTjiAEp6M9YagZEzWcSUaZTcIFAiyQWW1SoI6KyId8y2+/71wbgHKQAKd+iupLv4YvEwYWvA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -7134,9 +7529,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.360", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.360.tgz", - "integrity": "sha512-EP/jdF15S+l3iSSzgUpUqeazvkbVFXNuVxwwLMVUSie3lUeH1HH70gKe0IS7TASB/0h5QPG2bLMzv2jelSztIQ==" + "version": "1.4.581", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.581.tgz", + "integrity": "sha512-6uhqWBIapTJUxgPTCHH9sqdbxIMPt7oXl0VcAL1kOtlU6aECdcMncCrX5Z7sHQ/invtrC9jUQUef7+HhO8vVFw==" }, "node_modules/emittery": { "version": "0.8.1", @@ -7171,9 +7566,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -7207,24 +7602,25 @@ } }, "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", "dependencies": { "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.5", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has": "^1.0.3", "has-property-descriptors": "^1.0.0", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", + "hasown": "^2.0.0", "internal-slot": "^1.0.5", "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", @@ -7232,19 +7628,23 @@ "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", + "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -7277,30 +7677,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", + "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, "node_modules/es-module-lexer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", - "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.0.tgz", + "integrity": "sha512-lcCr3v3OLezdfFyx9r5NRYHOUTQNnFEQ9E87Mx8Kc+iqyJNkO7MJoB4GQRTlIMw9kLLTwGw0OAkm4BQQud/d9g==" }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -7319,11 +7740,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -7349,60 +7765,23 @@ } }, "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" } }, "node_modules/escodegen/node_modules/source-map": { @@ -7414,38 +7793,28 @@ "node": ">=0.10.0" } }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -7453,22 +7822,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -7509,13 +7875,13 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -7527,9 +7893,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dependencies": { "debug": "^3.2.7" }, @@ -7568,25 +7934,27 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" }, "engines": { "node": ">=4" @@ -7615,9 +7983,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -7646,26 +8014,26 @@ } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", - "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", - "dependencies": { - "@babel/runtime": "^7.20.7", - "aria-query": "^5.1.3", - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.6.2", - "axobject-query": "^3.1.1", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.3", - "language-tags": "=1.0.5", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "semver": "^6.3.0" + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" }, "engines": { "node": ">=4.0" @@ -7674,23 +8042,24 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": { + "dequal": "^2.0.3" } }, "node_modules/eslint-plugin-react": { - "version": "7.32.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", - "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", "dependencies": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", "array.prototype.tosorted": "^1.1.1", "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", @@ -7700,7 +8069,7 @@ "object.values": "^1.1.6", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.4", - "semver": "^6.3.0", + "semver": "^6.3.1", "string.prototype.matchall": "^4.0.8" }, "engines": { @@ -7733,11 +8102,11 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -7749,19 +8118,19 @@ } }, "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/eslint-plugin-testing-library": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.10.2.tgz", - "integrity": "sha512-f1DmDWcz5SDM+IpCkEX0lbFqrrTs8HRsEElzDEqN/EBI0hpRj8Cns5+IVANXswE8/LeybIJqPAOQIFu2j5Y5sw==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", "dependencies": { - "@typescript-eslint/utils": "^5.43.0" + "@typescript-eslint/utils": "^5.58.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0", @@ -7772,21 +8141,24 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -7862,14 +8234,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -7899,9 +8271,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dependencies": { "type-fest": "^0.20.2" }, @@ -7935,13 +8307,13 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -8057,15 +8429,15 @@ } }, "node_modules/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8130,15 +8502,29 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -8341,26 +8727,27 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=12.0.0" } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "funding": [ { "type": "individual", @@ -8498,15 +8885,15 @@ } }, "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, "node_modules/fresh": { @@ -8531,9 +8918,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -8541,9 +8928,9 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "optional": true, "os": [ @@ -8554,19 +8941,22 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -8600,13 +8990,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8778,10 +9169,10 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/gzip-size": { "version": "6.0.0", @@ -8807,17 +9198,6 @@ "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -8835,11 +9215,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "get-intrinsic": "^1.2.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8881,6 +9261,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -8965,9 +9356,19 @@ } }, "node_modules/html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] }, "node_modules/html-escaper": { "version": "2.0.2", @@ -9003,9 +9404,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", + "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -9142,9 +9543,9 @@ "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, "node_modules/i18next": { - "version": "22.4.14", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.14.tgz", - "integrity": "sha512-VtLPtbdwGn0+DAeE00YkiKKXadkwg+rBUV+0v8v0ikEjwdiJ0gmYChVE4GIa9HXymY6wKapkL93vGT7xpq6aTw==", + "version": "22.5.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.1.tgz", + "integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==", "funding": [ { "type": "individual", @@ -9164,19 +9565,19 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.1.tgz", - "integrity": "sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz", + "integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==", "dependencies": { - "@babel/runtime": "^7.19.4" + "@babel/runtime": "^7.23.2" } }, "node_modules/i18next-http-backend": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.2.0.tgz", - "integrity": "sha512-Z4sM7R6tzdLknSPER9GisEBxKPg5FkI07UrQniuroZmS15PHQrcCPLyuGKj8SS68tf+O2aEDYSUnmy1TZqZSbw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.4.1.tgz", + "integrity": "sha512-CZHzFGDvF8zN7ya1W2lHbgLj2ejPUvPD836+vA3eNXc9eKGUM3MSF6SA2TKBXKBZ2cNG3nxzycCXeM6n/46KWQ==", "dependencies": { - "cross-fetch": "3.1.5" + "cross-fetch": "4.0.0" } }, "node_modules/iconv-lite": { @@ -9322,12 +9723,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -9335,9 +9736,9 @@ } }, "node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", "engines": { "node": ">= 10" } @@ -9375,6 +9776,20 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -9424,11 +9839,11 @@ } }, "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9470,6 +9885,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -9699,15 +10125,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -9782,9 +10204,9 @@ } }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "engines": { "node": ">=8" } @@ -9805,24 +10227,38 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/istanbul-lib-source-maps": { @@ -9847,9 +10283,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -9858,15 +10294,27 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" @@ -9928,9 +10376,9 @@ } }, "node_modules/jest-changed-files/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -9980,9 +10428,9 @@ } }, "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10129,9 +10577,9 @@ } }, "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10210,9 +10658,9 @@ } }, "node_modules/jest-config/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10242,14 +10690,14 @@ } }, "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -10267,11 +10715,11 @@ } }, "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -10321,9 +10769,9 @@ } }, "node_modules/jest-each/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10385,9 +10833,9 @@ } }, "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10440,9 +10888,9 @@ } }, "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10464,9 +10912,9 @@ } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -10512,9 +10960,9 @@ } }, "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10578,9 +11026,9 @@ } }, "node_modules/jest-jasmine2/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10699,14 +11147,14 @@ } }, "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -10724,11 +11172,11 @@ } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -10737,17 +11185,17 @@ } }, "node_modules/jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -10767,11 +11215,11 @@ } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -10807,9 +11255,9 @@ } }, "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10887,9 +11335,9 @@ } }, "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10910,9 +11358,9 @@ } }, "node_modules/jest-resolve/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -10980,9 +11428,9 @@ } }, "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -11070,9 +11518,9 @@ } }, "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -11172,9 +11620,9 @@ } }, "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -11273,11 +11721,11 @@ } }, "node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -11320,9 +11768,9 @@ } }, "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -11588,9 +12036,9 @@ } }, "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -11645,9 +12093,9 @@ } }, "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.8.tgz", + "integrity": "sha512-1GwLEkmFafeb/HbE6pC7tFlgYSQ4Iqh2qlWCq8xN+Qfaiaxr2PcLfuhfRFRYqI6XJyeFoLYyKnhFbNsst9FMtQ==", "dependencies": { "@types/yargs-parser": "*" } @@ -11696,22 +12144,13 @@ } }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "bin": { "jiti": "bin/jiti.js" } }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11809,6 +12248,11 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -11851,6 +12295,28 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/jsonpointer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", @@ -11942,12 +12408,14 @@ } }, "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { "node": ">=4.0" @@ -11967,6 +12435,14 @@ "ws": "^7.1.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -11997,20 +12473,23 @@ "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" }, "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dependencies": { - "language-subtag-registry": "~0.3.2" + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", "dependencies": { "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" + "shell-quote": "^1.8.1" } }, "node_modules/leven": { @@ -12169,9 +12648,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -12198,11 +12677,11 @@ } }, "node_modules/memfs": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.0.tgz", - "integrity": "sha512-yK6o8xVJlQerz57kvPROwTMgx5WtGwC2ZxDtOUsnGl49rHjYkfQoPNZPCKH73VdLE1BwBu/+Fx/NL8NYMUw2aA==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", "dependencies": { - "fs-monkey": "^1.0.3" + "fs-monkey": "^1.0.4" }, "engines": { "node": ">= 4.0.0" @@ -12293,9 +12772,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.5.tgz", - "integrity": "sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==", + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", + "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", "dependencies": { "schema-utils": "^4.0.0" }, @@ -12342,14 +12821,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -12422,9 +12901,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -12471,9 +12950,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -12503,9 +12982,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -12557,9 +13036,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz", - "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==" + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -12578,9 +13057,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12626,26 +13105,26 @@ } }, "node_modules/object.entries": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -12655,14 +13134,15 @@ } }, "node_modules/object.getownpropertydescriptors": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", - "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.7.tgz", + "integrity": "sha512-PrJz0C2xJ58FNn11XV2lr4Jt5Gzl94qpy9Lu0JlfEj14z88sqbSBJCBEzdlNUCzY2gburhbrwOZ5BHCmuNUy0g==", "dependencies": { - "array.prototype.reduce": "^1.0.5", + "array.prototype.reduce": "^1.0.6", "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "safe-array-concat": "^1.0.0" }, "engines": { "node": ">= 0.8" @@ -12671,26 +13151,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, "node_modules/object.hasown": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", - "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -12762,16 +13253,16 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -12956,9 +13447,9 @@ } }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "engines": { "node": ">= 6" } @@ -13090,9 +13581,9 @@ } }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -13101,10 +13592,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -13481,16 +13976,16 @@ } }, "node_modules/postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.0.0" }, "peerDependencies": { "postcss": "^8.0.0" @@ -13542,15 +14037,15 @@ } }, "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", "dependencies": { "lilconfig": "^2.0.5", - "yaml": "^1.10.2" + "yaml": "^2.1.1" }, "engines": { - "node": ">= 10" + "node": ">= 14" }, "funding": { "type": "opencollective", @@ -13569,6 +14064,14 @@ } } }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-loader": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", @@ -13716,9 +14219,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", @@ -13760,11 +14263,11 @@ } }, "node_modules/postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.11" }, "engines": { "node": ">=12.0" @@ -14158,9 +14661,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", - "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -14379,9 +14882,9 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -14396,9 +14899,9 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "dependencies": { "side-channel": "^1.0.4" }, @@ -14406,16 +14909,7 @@ "node": ">=0.6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/querystringify": { @@ -14442,17 +14936,6 @@ } ] }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -14462,9 +14945,9 @@ } }, "node_modules/ramda": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", - "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/ramda" @@ -14547,6 +15030,11 @@ "node": ">=14" } }, + "node_modules/react-app-polyfill/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -14608,9 +15096,9 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, "node_modules/react-i18next": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz", - "integrity": "sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.3.1.tgz", + "integrity": "sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==", "dependencies": { "@babel/runtime": "^7.20.6", "html-parse-stringify": "^3.0.1" @@ -14642,29 +15130,29 @@ } }, "node_modules/react-router": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", - "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz", + "integrity": "sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==", "dependencies": { - "@remix-run/router": "1.5.0" + "@remix-run/router": "1.11.0" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", - "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.18.0.tgz", + "integrity": "sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==", "dependencies": { - "@remix-run/router": "1.5.0", - "react-router": "6.10.0" + "@remix-run/router": "1.11.0", + "react-router": "6.18.0" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8", @@ -14672,9 +15160,9 @@ } }, "node_modules/react-scripts": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz", - "integrity": "sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", + "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", "dependencies": { "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", @@ -14692,7 +15180,7 @@ "dotenv": "^10.0.0", "dotenv-expand": "^5.1.0", "eslint": "^8.3.0", - "eslint-config-react-app": "^7.0.0", + "eslint-config-react-app": "^7.0.1", "eslint-webpack-plugin": "^3.1.1", "file-loader": "^6.2.0", "fs-extra": "^10.0.0", @@ -14709,7 +15197,7 @@ "postcss-preset-env": "^7.0.1", "prompts": "^2.4.2", "react-app-polyfill": "^3.0.0", - "react-dev-utils": "^12.0.0", + "react-dev-utils": "^12.0.1", "react-refresh": "^0.11.0", "resolve": "^1.20.0", "resolve-url-loader": "^4.0.0", @@ -14813,15 +15301,34 @@ "node": ">=8" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", "dependencies": { "regenerate": "^1.4.2" }, @@ -14830,14 +15337,14 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "dependencies": { "@babel/runtime": "^7.8.4" } @@ -14848,13 +15355,13 @@ "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" }, "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -14940,11 +15447,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -15149,6 +15656,23 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -15254,9 +15778,9 @@ } }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -15276,10 +15800,11 @@ "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" }, "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dependencies": { + "@types/node-forge": "^1.3.0", "node-forge": "^1" }, "engines": { @@ -15287,9 +15812,9 @@ } }, "node_modules/semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -15449,6 +15974,33 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setasap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/setasap/-/setasap-2.0.1.tgz", @@ -15662,6 +16214,99 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "dependencies": { + "escodegen": "^1.8.1" + } + }, + "node_modules/static-eval/node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/static-eval/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/static-eval/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-eval/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -15725,17 +16370,18 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string.prototype.matchall": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", - "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", "side-channel": "^1.0.4" }, "funding": { @@ -15743,13 +16389,13 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -15759,26 +16405,26 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15855,9 +16501,9 @@ } }, "node_modules/style-loader": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.2.tgz", - "integrity": "sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", + "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", "engines": { "node": ">= 12.13.0" }, @@ -15885,14 +16531,14 @@ } }, "node_modules/stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, "node_modules/sucrase": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", - "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -16126,44 +16772,39 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "node_modules/tailwindcss": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz", - "integrity": "sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", + "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", "dependencies": { + "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", - "color-name": "^1.1.4", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.17.2", - "lilconfig": "^2.0.6", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.0.9", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1", - "sucrase": "^3.29.0" + "resolve": "^1.22.2", + "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" }, "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "postcss": "^8.0.9" + "node": ">=14.0.0" } }, "node_modules/tapable": { @@ -16226,12 +16867,12 @@ } }, "node_modules/terser": { - "version": "5.16.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", - "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -16243,15 +16884,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" + "terser": "^5.16.8" }, "engines": { "node": ">= 10.13.0" @@ -16365,9 +17006,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -16432,9 +17073,9 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -16497,6 +17138,54 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -16519,16 +17208,16 @@ } }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { @@ -16545,6 +17234,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -16593,9 +17292,9 @@ } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "engines": { "node": ">= 10.0.0" } @@ -16623,9 +17322,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "funding": [ { "type": "opencollective", @@ -16634,6 +17333,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { @@ -16641,7 +17344,7 @@ "picocolors": "^1.0.0" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -16656,12 +17359,12 @@ } }, "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "punycode": "^1.4.1", + "qs": "^6.11.2" } }, "node_modules/url-parse": { @@ -16674,9 +17377,9 @@ } }, "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "node_modules/util": { "version": "0.12.5", @@ -16723,9 +17426,13 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -16824,20 +17531,20 @@ } }, "node_modules/webpack": { - "version": "5.79.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.79.0.tgz", - "integrity": "sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg==", + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", + "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -16847,7 +17554,7 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", @@ -16923,14 +17630,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -16941,9 +17648,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz", - "integrity": "sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -16951,7 +17658,7 @@ "@types/serve-index": "^1.9.1", "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", + "@types/ws": "^8.5.5", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -17030,14 +17737,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -17048,9 +17755,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "engines": { "node": ">=10.0.0" }, @@ -17171,9 +17878,9 @@ } }, "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" }, "node_modules/whatwg-mimetype": { "version": "2.3.0", @@ -17223,6 +17930,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-collection": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", @@ -17238,16 +17970,15 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", "dependencies": { "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.4", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -17257,34 +17988,34 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "engines": { "node": ">=0.10.0" } }, "node_modules/workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", "dependencies": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", "@babel/core": "^7.11.1", @@ -17308,21 +18039,21 @@ "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" }, "engines": { "node": ">=10.0.0" @@ -17413,117 +18144,118 @@ } }, "node_modules/workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", + "deprecated": "workbox-background-sync@6.6.0", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==" }, "node_modules/workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", "dependencies": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", "dependencies": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", "dependencies": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" } }, "node_modules/workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==" }, "node_modules/workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", "dependencies": { "fast-json-stable-stringify": "^2.1.0", "pretty-bytes": "^5.4.1", "upath": "^1.2.0", "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" + "workbox-build": "6.6.0" }, "engines": { "node": ">=10.0.0" @@ -17550,12 +18282,12 @@ } }, "node_modules/workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/wrap-ansi": { diff --git a/src/webapp/package.json b/src/webapp/package.json index 5bbbda278..115b66852 100644 --- a/src/webapp/package.json +++ b/src/webapp/package.json @@ -3,26 +3,26 @@ "version": "0.1.0", "private": true, "dependencies": { - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.12.0", - "@mui/styles": "^5.12.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^12.1.2", - "@testing-library/user-event": "^14.4.3", - "i18next": "^22.4.14", - "i18next-browser-languagedetector": "^7.0.1", - "i18next-http-backend": "^2.2.0", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.14.16", + "@mui/material": "^5.14.17", + "@mui/styles": "^5.14.17", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.5.1", + "i18next": "^22.5.1", + "i18next-browser-languagedetector": "^7.2.0", + "i18next-http-backend": "^2.4.1", "jszmq": "^0.1.2", - "ramda": "^0.29.0", + "ramda": "^0.29.1", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-i18next": "^12.2.0", - "react-router-dom": "^6.10.0", - "react-scripts": "5.0.0", - "url": "^0.11.0", - "uuid": "^9.0.0" + "react-i18next": "^12.3.1", + "react-router-dom": "^6.18.0", + "react-scripts": "^5.0.1", + "url": "^0.11.3", + "uuid": "^9.0.1" }, "scripts": { "start": "react-scripts start", From b12c94145d395c581b5204606c1007832c7771a9 Mon Sep 17 00:00:00 2001 From: s-martin Date: Tue, 14 Nov 2023 10:15:18 +0100 Subject: [PATCH 015/121] markdown file --- .../hardware/template_new_reader/README.rst | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/jukebox/components/rfid/hardware/template_new_reader/README.rst b/src/jukebox/components/rfid/hardware/template_new_reader/README.rst index e20e4452b..77f3008c2 100644 --- a/src/jukebox/components/rfid/hardware/template_new_reader/README.rst +++ b/src/jukebox/components/rfid/hardware/template_new_reader/README.rst @@ -1,42 +1,41 @@ -Template Reader ----------------- +# Template Reader -Or in full: *Template for creating and integrating a new RFID Reader* +*Template for creating and integrating a new RFID Reader* -.. note:: For developers only +> [!NOTE] +> For developers only -This template provides the skeleton API for a new Reader. -If you follow the conventions outlined below, your new reader will be picked up automatically -There is no extra need to register the reader module with the Phoniebox. -Just re-run :ref:`the reader config tool `. +This template provides the skeleton API for a new Reader. If you follow +the conventions outlined below, your new reader will be picked up +automatically There is no extra need to register the reader module with +the Phoniebox. Just re-run `the reader config tool `. -Follow the instructions in `template_new_reader.py` +Follow the instructions in [template_new_reader.py] -Also have a look at the other reader subpackages to see how stuff works with an example +Also have a look at the other reader subpackages to see how stuff works +with an example - -File structure -^^^^^^^^^^^^^^^^^^^^ +## File structure Your new reader is a python subpackage with these three mandatory files -.. code-block:: bash - - components/rfid/hardware/awesome_reader/ - +- awesome_reader.py <-- The actual reader module - +- description.py <-- A description module w/o dependencies. Do not change the filename! - +- README.rst <-- The Readme - -The module documentation must go into a separate file, named README.MD. - -Conventions -^^^^^^^^^^^^^^^^^^ - -* Single reader per directory / subpackage -* reader module directory name and reader module file name must be identical -* Obviously awesome_reader will be replaced with something more descriptive. The naming scheme for the subpackage is - - * __ - * e.g. generic_usb/generic_usb.py - * e.g. pn532_spi/pn532_spi.py - * ... +``` bash +components/rfid/hardware/awesome_reader/ + +- awesome_reader.py <-- The actual reader module + +- description.py <-- A description module w/o dependencies. Do not change the filename! + +- README.rst <-- The Readme +``` + +The module documentation must go into a separate file, called README.ME. + +## Conventions + +- Single reader per directory / subpackage +- reader module directory name and reader module file name must be + identical +- Obviously awesome_reader will be replaced with something more + descriptive. The naming scheme for the subpackage is + - \\_\\_\ + - e.g. generic_usb/generic_usb.py + - e.g. pn532_spi/pn532_spi.py + - ... From e3bc59fd5ac1cf3f609f70894dc6919c2d138408 Mon Sep 17 00:00:00 2001 From: s-martin Date: Thu, 16 Nov 2023 21:35:53 +0100 Subject: [PATCH 016/121] More links fixed (#2106) * fix link * fix links * fix indentation * fix link * fix links * fix links * fix link * Update template_reader.md * Make only link to docs * Fix link * Update and rename README.rst to README.md * Update and rename README.rst to README.md * Update and rename README.rst to README.md * Update and rename README.rst to README.md --- .../content/developers/rfid/README.md | 6 +- .../content/developers/rfid/basics.md | 8 +- .../content/developers/rfid/genericusb.md | 2 +- .../content/developers/rfid/mfrc522_spi.md | 2 +- .../developers/rfid/template_reader.md | 4 +- .../rfid/hardware/fake_reader_gui/README.md | 2 + .../rfid/hardware/fake_reader_gui/README.rst | 35 ------- .../rfid/hardware/generic_usb/README.md | 2 + .../rfid/hardware/generic_usb/README.rst | 16 ---- .../rfid/hardware/pn532_i2c_py532/README.md | 2 + .../rfid/hardware/pn532_i2c_py532/README.rst | 61 ------------ .../rfid/hardware/rc522_spi/README.md | 2 + .../rfid/hardware/rc522_spi/README.rst | 94 ------------------- .../rfid/hardware/rdm6300_serial/README.md | 2 + .../rfid/hardware/rdm6300_serial/README.rst | 19 ---- 15 files changed, 21 insertions(+), 236 deletions(-) create mode 100644 src/jukebox/components/rfid/hardware/fake_reader_gui/README.md delete mode 100644 src/jukebox/components/rfid/hardware/fake_reader_gui/README.rst create mode 100644 src/jukebox/components/rfid/hardware/generic_usb/README.md delete mode 100644 src/jukebox/components/rfid/hardware/generic_usb/README.rst create mode 100644 src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md delete mode 100644 src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.rst create mode 100644 src/jukebox/components/rfid/hardware/rc522_spi/README.md delete mode 100644 src/jukebox/components/rfid/hardware/rc522_spi/README.rst create mode 100644 src/jukebox/components/rfid/hardware/rdm6300_serial/README.md delete mode 100644 src/jukebox/components/rfid/hardware/rdm6300_serial/README.rst diff --git a/documentation/content/developers/rfid/README.md b/documentation/content/developers/rfid/README.md index 0717ef661..ad1cc4bd8 100644 --- a/documentation/content/developers/rfid/README.md +++ b/documentation/content/developers/rfid/README.md @@ -1,11 +1,11 @@ # RFID Readers * [Basics](basics.md) -* [Reader Types](basics.md#reader-types) -* [Reader Configuration](basics.md#reader-configuration) + * [Reader Types](basics.md#reader-types) + * [Reader Configuration](basics.md#reader-configuration) * Readers * [Generic USB Reader](genericusb.md) - * [RDM6300 Reader](rdm63000.md) + * [RDM6300 Reader](rdm6300.md) * [MFRC522 SPI Reader](mfrc522_spi.md) * [PN532 I2C Reader](pn532_i2c.md) * [Mock Reader](mock_reader.md) diff --git a/documentation/content/developers/rfid/basics.md b/documentation/content/developers/rfid/basics.md index f1e1bc499..5cd1d1e9f 100644 --- a/documentation/content/developers/rfid/basics.md +++ b/documentation/content/developers/rfid/basics.md @@ -6,7 +6,7 @@ Cards placed on the reader trigger an action. An action may be any callable plugin function through the RPC with any arguments. Typically, this would be "play some folder", but can also be "activate shutdown timer", or "increase volume". This is configured in the -[Card Database](../userguide/card-database.md). +[Card Database](../../userguide/card-database.md). You may configure a single or even multiple parallel readers (of different or identical types). @@ -26,7 +26,7 @@ when a card is placed and to pause it when it's removed. Generally, **not** all [USB-based RFID readers](genericusb.md) are place-capable. -The known place-capable readers are [RDM6300 Reader](rdm6300:RDM6300), [MFRC522 SPI Reader](mfrc522_spi:MFRC522) or [PN532 I2C Reader](pn532_i2c:PN532). +The known place-capable readers are [RDM6300 Reader](rdm6300.md), [MFRC522 SPI Reader](mfrc522_spi.md) or [PN532 I2C Reader](pn532_i2c.md). #### Frequency: @@ -36,7 +36,7 @@ Readers operate on one of two different frequencies: 125kHz or 13.56 MHz. Make s During the installation process, you can already configure a RFID reader. To manually configure RFID reader(s), -[please run the tool](../developer/coreapps.md#run_register_rfid_reader.py), (`src/jukebox/run_register_rfid_reader.py`). +[please run the tool](../coreapps.md#run_register_rfid_reader.py), (`src/jukebox/run_register_rfid_reader.py`). It will generate a reader configuration file at `shared/settings/rfid.yaml`. You can re-run the tool to change the @@ -67,7 +67,7 @@ Indicates the Python package used for this reader. Filled by the RFID configurat #### config: -Filled by the [RFID configuration tool](../developer/coreapps.md#run_register_rfid_reader.py) (`src/jukebox/run_register_rfid_reader.py`) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. +Filled by the [RFID configuration tool](../coreapps.md#run_register_rfid_reader.py) (`src/jukebox/run_register_rfid_reader.py`) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. #### same_id_delay: float \| integer diff --git a/documentation/content/developers/rfid/genericusb.md b/documentation/content/developers/rfid/genericusb.md index 2f049291d..a9a6565b5 100644 --- a/documentation/content/developers/rfid/genericusb.md +++ b/documentation/content/developers/rfid/genericusb.md @@ -4,7 +4,7 @@ This module covers all types of USB-based RFID input readers. If you plan to connect multiple USB-based RFID readers to the Jukebox, make -sure to connect all of them before running the registration tool [run_register_rfid_reader.py](../developers/coreapps.md). +sure to connect all of them before running the registration tool [run_register_rfid_reader.py](../coreapps.md). > [!NOTE] > The user needs to be part of the group \'input\' for evdev to work. This should usually be the case. However, a user can be added with: diff --git a/documentation/content/developers/rfid/mfrc522_spi.md b/documentation/content/developers/rfid/mfrc522_spi.md index 726136d61..3aefbc221 100644 --- a/documentation/content/developers/rfid/mfrc522_spi.md +++ b/documentation/content/developers/rfid/mfrc522_spi.md @@ -6,7 +6,7 @@ RC522 RFID reader via SPI connection. ## Installation -Run the ref:[run_rfid_configuration]{.title-ref} tool for guided +Run the [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_reader.py) tool for guided installation. ## Options diff --git a/documentation/content/developers/rfid/template_reader.md b/documentation/content/developers/rfid/template_reader.md index 77f3008c2..2ace724ea 100644 --- a/documentation/content/developers/rfid/template_reader.md +++ b/documentation/content/developers/rfid/template_reader.md @@ -8,10 +8,10 @@ This template provides the skeleton API for a new Reader. If you follow the conventions outlined below, your new reader will be picked up automatically There is no extra need to register the reader module with -the Phoniebox. Just re-run `the reader config tool `. +the Phoniebox. Just re-run the reader config tool [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_reader.py). -Follow the instructions in [template_new_reader.py] +Follow the instructions in [template_new_reader.py](../../../../src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py) Also have a look at the other reader subpackages to see how stuff works with an example diff --git a/src/jukebox/components/rfid/hardware/fake_reader_gui/README.md b/src/jukebox/components/rfid/hardware/fake_reader_gui/README.md new file mode 100644 index 000000000..9ba73355a --- /dev/null +++ b/src/jukebox/components/rfid/hardware/fake_reader_gui/README.md @@ -0,0 +1,2 @@ + +For documentation see [documentation/content/developers/rfid/mock_reader.md](../../../../../../documentation/content/developers/rfid/mock_reader.md). diff --git a/src/jukebox/components/rfid/hardware/fake_reader_gui/README.rst b/src/jukebox/components/rfid/hardware/fake_reader_gui/README.rst deleted file mode 100644 index 0bece9b4e..000000000 --- a/src/jukebox/components/rfid/hardware/fake_reader_gui/README.rst +++ /dev/null @@ -1,35 +0,0 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder - -Mock Reader ------------------ - -A fake reader using a TK GUI for development purposes. This target software development which does not happen -on the RPi but on another machine - probably in a Python virtual environment. - -**place-capable**: yes - -If you :ref:`mock the GPIO pins `, this GUI will show the GPIO devices. - -.. image:: mock_reader.png - :width: 50 % - :align: center - -.. note:: When using Anaconda, the GUI will look horrible! - That's because Anaconda's TK is compiled without FreeType support. - - There is a very quick and very dirty - `fix `_. - Replacing the TK lib in Anaconda's environment with the system ``libtk``. However, this depends - on an exact version match of the ``libtk``. - - .. code-block:: bash - - cd /path/to/anaconda3/envs/rpi/lib - mv ./libtk8.6.so ./libtk8.6.so.bak - ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so libtk8.6.so - - - An alternative is to simply use Python's ``venv`` module to create a virtual environment. This uses your - systems Python version and thus will work with your system ``libtk``. It might not be the RPi's - Python version - but that should not matter in most cases. \ No newline at end of file diff --git a/src/jukebox/components/rfid/hardware/generic_usb/README.md b/src/jukebox/components/rfid/hardware/generic_usb/README.md new file mode 100644 index 000000000..e82e9005b --- /dev/null +++ b/src/jukebox/components/rfid/hardware/generic_usb/README.md @@ -0,0 +1,2 @@ + +For documentation see [documentation/content/developers/rfid/genericusb.md](../../../../../../documentation/content/developers/rfid/genericusb.md). diff --git a/src/jukebox/components/rfid/hardware/generic_usb/README.rst b/src/jukebox/components/rfid/hardware/generic_usb/README.rst deleted file mode 100644 index 59841f5a1..000000000 --- a/src/jukebox/components/rfid/hardware/generic_usb/README.rst +++ /dev/null @@ -1,16 +0,0 @@ -Generic USB Reader -------------------------- - -**place-capable**: typically no - -This module covers all types of USB-based RFID input readers. If you plan to connect multiple USB-based -RFID readers to the Jukebox, make sure to connect all of them before running -the registration tool :ref:`developer/coreapps:run_register_rfid_reader.py`. - -.. note:: The user needs to be part of the group 'input' for evdev to work. - This should usually be the case. However, a user can be added with: - - .. code-block:: bash - - sudo usermod -a -G input USER - diff --git a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md new file mode 100644 index 000000000..d92a17c37 --- /dev/null +++ b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md @@ -0,0 +1,2 @@ + +For documentation see [documentation/content/developers/rfid/pn532_i2c.md](../../../../../../documentation/content/developers/rfid/pn532_i2c.md). diff --git a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.rst b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.rst deleted file mode 100644 index b57a4a749..000000000 --- a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.rst +++ /dev/null @@ -1,61 +0,0 @@ -PN532 I2C Reader -------------------------------------- - -The PN532-based readers connected via I2C - -This reader module is based on the `py532lib library `_ -and uses the I2C bus. -It utilizes a polling mechanism. It only needs 2 pins, but has a certain performance overhead due to polling. - -Options -^^^^^^^^^^^^ - -There are no configurable options for this module. - -Hardware -^^^^^^^^^^^^^^^^^^^^^ - -This reader module has been tested with the excellent and highly recommendable -`Adafruit PN532 breakout board `_. It also comes -with `heaps of documentation and even schematics `_. - -I have used various RFID boards and this one has an excellent reading range and no problem with various types of -cards and stickers. - -You can usually pick up a board at - - * https://www.berrybase.de/sensoren-module/rfid-nfc/pn532-nfc/rfid-controller-breakout-board - * https://shop.pimoroni.com/products/adafruit-pn532-nfc-rfid-controller-shield-for-arduino-extras - -Board Connections -^^^^^^^^^^^^^^^^^^^^^ - -.. table:: Default wiring - - ===== ============ ========== - PN532 RPI GPIO RPI Pin - ===== ============ ========== - 5V 5V 4 - GND GND 6 - SDA GPIO 2 (SDA) 3 - SCL GPIO 3 (SCL) 5 - ===== ============ ========== - - -Other pins from the Adafruit board need not be connected. Especially do **not** connect the 3.3V pin, if connecting -the 5.0V pin. It is recommended to use the 5.0V because that does not draw power from the PI's own voltage regulator. - - -Jumpers -"""""""""""""""""""" - -.. table:: Jumper settings for I2C protocol - :widths: auto - - ======= ========= - Jumper Position - ======= ========= - SEL0 ON - SEL1 OFF - ======= ========= - diff --git a/src/jukebox/components/rfid/hardware/rc522_spi/README.md b/src/jukebox/components/rfid/hardware/rc522_spi/README.md new file mode 100644 index 000000000..a78a7a890 --- /dev/null +++ b/src/jukebox/components/rfid/hardware/rc522_spi/README.md @@ -0,0 +1,2 @@ + +For documentation see [documentation/content/developers/rfid/mfrc522_spi.md](../../../../../../documentation/content/developers/rfid/mfrc522_spi.md). diff --git a/src/jukebox/components/rfid/hardware/rc522_spi/README.rst b/src/jukebox/components/rfid/hardware/rc522_spi/README.rst deleted file mode 100644 index 4a15c6934..000000000 --- a/src/jukebox/components/rfid/hardware/rc522_spi/README.rst +++ /dev/null @@ -1,94 +0,0 @@ -MFRC522 SPI Reader --------------------- - -.. |fileonly| replace:: This setting can only be changed by directly modifying the reader.yaml configuration file after the configuration tool has been run. - -RC522 RFID reader via SPI connection. - -**place-capable**: yes - -Installation -^^^^^^^^^^^^^^ - -Run the ref:`run_rfid_configuration` tool for guided installation. - -Options -^^^^^^^^^^^^^^ - -In principle Raspberry PIs support multiple SPI interfaces. The reader class is based on pi-rc522 which -uses spidev. This allows to use different SPI bus configurations. The below parameters regarding pin-out are -just routed through to spidev. Have a look at the spidev documentation for details if you really want to -use a different SPI bus. The default setup makes most sense for almost everyone. - -spi_bus *(default=0)* - The SPI Bus ID. The default bus is 0. For other bus IDs, the RPi also needs to re-configured. For that reason - we set this to zero. |fileonly| - -spi_ce *(default=0)* - SPI chip enable pin. On default SPI bus 0, this can be - - * 0 = GPIO8 (Pin 24) - * 1 = GPIO7 (Pin 26) - - For other SPI buses refer to RPi documentation. - -pin_irq - Mandatory IRQ pin. This can be any GPIO pin. - -pin_rst *(default=0)* - Reset pin for hardware reset. This is an optional pin. - If not used, - - * hardware reset will only be performed by power-on-reset. This has been tested on works fine. - * you **must** tie the reset pin of the MFRC522 board **high**! - -mode_legacy *(default=false)* - 4-byte-only legacy mode: previously the pirc522 library could only read the lower 4 bytes of a card UID. - It can now read 4-byte and full 7-byte UIDs. - Legacy mode turns back to the old behaviour. This only makes sense, if you already have an large RFID collection - and do not want to re-assign every card - -antenna_gain *(default=4)* - Antenna gain factor of the RFID reader chip on the MFRC522 board. |fileonly| - -log_all_cards *(default=false)* - If true all card read-outs will be logged, even when card is permanently on reader. - Only for debugging. |fileonly| - - -Board Connections -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The following pin-out is for the default SPI Bus 0 on Raspberry Pins. - -.. table:: MFRC522 default wiring (spi_bus=0, spi_ce=0) - :widths: auto - - =============== ======== ========= ========= - Pin Board Name Function RPI GPIO RPI Pin - =============== ======== ========= ========= - SDA CE GPIO8 24 - SCK SCLK GPIO11 23 - MOSI MOSI GPIO10 19 - MISO MISO GPIO9 21 - IRQ IRQ GPIO24 18 - GND - RST RST GPIO25 22 - 3.3V - =============== ======== ========= ========= - -Some RC522 boards use reversed labeling for MOSI and MISO pins. The good thing is, no harm is done to -the card reader when incorrectly connected. In case no cards are read, try swapping the connections -for MOSI and MISO. - -Hardware -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -MFRC522 boards can be picked up from many places for little money. - -Good quality ones can be found e.g. here - -.. A word of caution: If you by directly from east asian sellers, be aware of highly fluctuating quality. meaning that - not all RFID cards or stickers can be read out. - -.. https://www.berrybase.de/sensoren-module/rfid-nfc/rfid-leseger-228-t-mit-spi-schnittstelle-inkl.-karte-dongle diff --git a/src/jukebox/components/rfid/hardware/rdm6300_serial/README.md b/src/jukebox/components/rfid/hardware/rdm6300_serial/README.md new file mode 100644 index 000000000..895c24bbf --- /dev/null +++ b/src/jukebox/components/rfid/hardware/rdm6300_serial/README.md @@ -0,0 +1,2 @@ + +For documentation see [documentation/content/developers/rfid/rdm6300.md](../../../../../../documentation/content/developers/rfid/rdm6300.md). diff --git a/src/jukebox/components/rfid/hardware/rdm6300_serial/README.rst b/src/jukebox/components/rfid/hardware/rdm6300_serial/README.rst deleted file mode 100644 index a2eddc855..000000000 --- a/src/jukebox/components/rfid/hardware/rdm6300_serial/README.rst +++ /dev/null @@ -1,19 +0,0 @@ -RDM6300 Reader ---------------- - -The RDM6300 / RDM630 connected via serial UART port - -**place-capable**: yes - -Options -^^^^^^^^^^^^^ - -Number Format - -Board Connections -^^^^^^^^^^^^^^^^^^^^^^ - -The voltage level of the RX/TX is 3.3V despite the wide-spread belief that it is 5V. (At least on the boards I have). -I did check with an oscilloscope. And one can easily identify the AMS1117 LDO voltage regulator on the backside of the board. - -No warranties: If you meddle with GPIOs you are old enough to know the risks of wrong voltage levels. From 26ca404156c568a6f91793e89a0ea834a74ac71b Mon Sep 17 00:00:00 2001 From: s-martin Date: Thu, 16 Nov 2023 22:06:23 +0100 Subject: [PATCH 017/121] * keep docs for template reader at src (#2113) * fix markdown warnings --- CODE_OF_CONDUCT.md | 2 +- .../content/developers/developer-issues.md | 4 +- .../developers/development-environment.md | 18 +++--- documentation/content/developers/docker.md | 57 ++++++++++--------- .../content/developers/rfid/README.md | 3 +- .../content/developers/rfid/mfrc522_spi.md | 9 ++- .../developers/rfid/template_reader.md | 41 +------------ documentation/content/developers/status.md | 44 +++++++------- documentation/content/userguide/README.md | 1 - documentation/content/userguide/audio.md | 4 +- .../content/userguide/autohotspot.md | 6 +- .../content/userguide/installation.md | 8 +-- 12 files changed, 80 insertions(+), 117 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5e8fc7288..332baee88 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -45,7 +45,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at micz.flor@web.de. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/documentation/content/developers/developer-issues.md b/documentation/content/developers/developer-issues.md index c20cff5ec..4b1bd4794 100644 --- a/documentation/content/developers/developer-issues.md +++ b/documentation/content/developers/developer-issues.md @@ -29,10 +29,10 @@ Not enough memory for Node Prior to building set the node memory environment variable. -1. Make sure the value is less than the total available space on the +1. Make sure the value is less than the total available space on the system, or you may run into the next issue. (Not always though!) Check memory availability with `free -mt`. -2. We also experience trouble, when the space is set too small a +2. We also experience trouble, when the space is set too small a value. 512 always works, 256 sometimes does, sometimes does not. If your free memory is small, consider increasing the swap size of your system! diff --git a/documentation/content/developers/development-environment.md b/documentation/content/developers/development-environment.md index 04689be2d..44b748ebb 100644 --- a/documentation/content/developers/development-environment.md +++ b/documentation/content/developers/development-environment.md @@ -14,18 +14,18 @@ We recommend to use at least a Pi 3 or Pi Zero 2 for development. This hardware won\'t be needed in production, but it can be slow while developing. -1. Install the latest Pi OS on a SD card. -2. Boot up your Raspberry Pi. -3. [Install](../userguide/installation.md) the Jukebox software as if you were building a +1. Install the latest Pi OS on a SD card. +2. Boot up your Raspberry Pi. +3. [Install](../userguide/installation.md) the Jukebox software as if you were building a Phoniebox. You can install from your own fork and feature branch if you wish which can be changed later as well. The original repository will be set as `upstream`. -4. Once the installation has successfully ran, reboot your Pi. -5. Due to some resource constraints, the Webapp does not build the +4. Once the installation has successfully ran, reboot your Pi. +5. Due to some resource constraints, the Webapp does not build the latest changes and instead consumes the latest official release. To change that, you need to install NodeJS and build the Webapp locally. -6. Install NodeJS using the existing installer +6. Install NodeJS using the existing installer ``` bash cd ~/RPi-Jukebox-RFID/installation/routines; \ @@ -33,8 +33,8 @@ developing. _jukebox_webapp_install_node ``` -7. To free up RAM, reboot your Pi. -8. Build the Webapp using the existing build command. If the build +7. To free up RAM, reboot your Pi. +8. Build the Webapp using the existing build command. If the build fails, you might have forgotten to reboot. ``` bash @@ -42,7 +42,7 @@ developing. ./run_rebuild.sh -u ``` -9. The Webapp should now be updated. +9. The Webapp should now be updated. 10. To continuously update Webapp, pull the latest changes from your repository and rerun the command above. diff --git a/documentation/content/developers/docker.md b/documentation/content/developers/docker.md index 4384bbf46..012b8d3db 100644 --- a/documentation/content/developers/docker.md +++ b/documentation/content/developers/docker.md @@ -14,7 +14,7 @@ need to adapt some of those commands to your needs. ## Prerequisites -1. Install required software +1. Install required software * Linux * [Docker](https://docs.docker.com/engine/install/debian/) * [Compose](https://docs.docker.com/compose/install/) @@ -26,19 +26,22 @@ need to adapt some of those commands to your needs. * [pulseaudio (Windows)](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) 2. Pull the Jukebox repository: - ``` + + ```bash $ git clone https://github.com/MiczFlor/RPi-Jukebox-RFID.git - `````` + ``` 3. Create a jukebox.yaml file * Copy the `./resources/default-settings/jukebox.default.yaml` to `./shared/settings` and rename the file to `jukebox.yaml`. - ``` + + ```bash $ cp ./resources/default-settings/jukebox.default.yaml ./shared/settings/jukebox.yaml ``` + * Override/Merge the values from the following [Override file](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/docker/config/jukebox.overrides.yaml) in your `jukebox.yaml`. * **\[Currently required\]** Update all relative paths (`../..`) in to `/home/pi/RPi-Jukebox-RFID`. -4. Change directory into the `./RPi-Jukebox-RFID/shared/audiofolders` +4. Change directory into the `./RPi-Jukebox-RFID/shared/audiofolders` and copy a set of MP3 files into this folder (for more fun when testing). @@ -54,7 +57,7 @@ They can be run individually or in combination. To do that, we use Make sure you don\'t use `sudo` to run your `docker-compose`. Check out Docker\'s [post-installation guide](https://docs.docker.com/engine/install/linux-postinstall/) for more information. -``` bash +```bash // Build Images $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build @@ -104,29 +107,29 @@ $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml d ### Windows -1. Download +1. Download [pulseaudio](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) -2. Uncompress somewhere in your user folder +2. Uncompress somewhere in your user folder -3. Edit `$INSTALL_DIR/etc/pulse/default.pa` +3. Edit `$INSTALL_DIR/etc/pulse/default.pa` -4. Add the following line +4. Add the following line ``` bash load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 ``` -5. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the +5. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the following line and change it to: ``` bash exit-idle-time = -1 ``` -6. Execute `$INSTALL_DIR/bin/pulseaudio.exe` +6. Execute `$INSTALL_DIR/bin/pulseaudio.exe` -7. Run `cocker-compose` +7. Run `docker-compose` ``` bash // Build Images @@ -237,29 +240,29 @@ $ docker run -it --rm \ **Mac** -- -- -- +* +* +* **Windows** -- -- -- -- +* +* +* +* **Audio** -- -- -- +* +* +* **MPD** -- -- -- +* +* +* **ZMQ** -- +* diff --git a/documentation/content/developers/rfid/README.md b/documentation/content/developers/rfid/README.md index ad1cc4bd8..c1412a90b 100644 --- a/documentation/content/developers/rfid/README.md +++ b/documentation/content/developers/rfid/README.md @@ -9,4 +9,5 @@ * [MFRC522 SPI Reader](mfrc522_spi.md) * [PN532 I2C Reader](pn532_i2c.md) * [Mock Reader](mock_reader.md) - * [Template Reader](template_reader.md) \ No newline at end of file + * [Template Reader](template_reader.md) + \ No newline at end of file diff --git a/documentation/content/developers/rfid/mfrc522_spi.md b/documentation/content/developers/rfid/mfrc522_spi.md index 3aefbc221..f7710b2b1 100644 --- a/documentation/content/developers/rfid/mfrc522_spi.md +++ b/documentation/content/developers/rfid/mfrc522_spi.md @@ -26,8 +26,8 @@ The SPI Bus ID. The default bus is 0. For other bus IDs, the RPi also needs to r SPI chip enable pin. On default SPI bus 0, this can be -- 0 = GPIO8 (Pin 24) -- 1 = GPIO7 (Pin 26) +- 0 = GPIO8 (Pin 24) +- 1 = GPIO7 (Pin 26) For other SPI buses refer to RPi documentation. @@ -39,8 +39,8 @@ Mandatory IRQ pin. This can be any GPIO pin. Reset pin for hardware reset. This is an optional pin. If not used, -- hardware reset will only be performed by power-on-reset. This has been tested on works fine. -- you **must** tie the reset pin of the MFRC522 board **high**! +- hardware reset will only be performed by power-on-reset. This has been tested on works fine. +- you **must** tie the reset pin of the MFRC522 board **high**! #### mode_legacy *(default=false)* @@ -68,7 +68,6 @@ The following pin-out is for the default SPI Bus 0 on Raspberry Pins. |IRQ GND |IRQ |GPIO24 |18 | |RST 3.3V |RST |GPIO25 |22 | - Some RC522 boards use reversed labeling for MOSI and MISO pins. The good thing is, no harm is done to the card reader when incorrectly connected. In case no cards are read, try swapping the connections for MOSI and diff --git a/documentation/content/developers/rfid/template_reader.md b/documentation/content/developers/rfid/template_reader.md index 2ace724ea..48275d619 100644 --- a/documentation/content/developers/rfid/template_reader.md +++ b/documentation/content/developers/rfid/template_reader.md @@ -1,41 +1,2 @@ -# Template Reader -*Template for creating and integrating a new RFID Reader* - -> [!NOTE] -> For developers only - -This template provides the skeleton API for a new Reader. If you follow -the conventions outlined below, your new reader will be picked up -automatically There is no extra need to register the reader module with -the Phoniebox. Just re-run the reader config tool [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_reader.py). - - -Follow the instructions in [template_new_reader.py](../../../../src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py) -Also have a look at the other reader subpackages to see how stuff works -with an example - -## File structure - -Your new reader is a python subpackage with these three mandatory files - -``` bash -components/rfid/hardware/awesome_reader/ - +- awesome_reader.py <-- The actual reader module - +- description.py <-- A description module w/o dependencies. Do not change the filename! - +- README.rst <-- The Readme -``` - -The module documentation must go into a separate file, called README.ME. - -## Conventions - -- Single reader per directory / subpackage -- reader module directory name and reader module file name must be - identical -- Obviously awesome_reader will be replaced with something more - descriptive. The naming scheme for the subpackage is - - \\_\\_\ - - e.g. generic_usb/generic_usb.py - - e.g. pn532_spi/pn532_spi.py - - ... +For documentation see [src/jukebox/components/rfid/hardware/template_new_reader/README.md](../../../../src/jukebox/components/rfid/hardware/template_new_reader/README.md). diff --git a/documentation/content/developers/status.md b/documentation/content/developers/status.md index 68bbe6864..3cc8d7f47 100644 --- a/documentation/content/developers/status.md +++ b/documentation/content/developers/status.md @@ -1,4 +1,4 @@ -## Feature Status +# Feature Status **This is where we are in a nutshell:** Playing music from local folders via RFID trigger. We also built a new WebUI to control the Jukebox from a browser. @@ -12,26 +12,28 @@ Topics marked _in progress_ are already in the process of implementation by comm ## Table of Contents -- [Jukebox Core App](#jukebox-core-app) - - [Base](#base) - - [Via RPC](#via-rpc) - - [Config handler](#config-handler) - - [ZMQ Publisher](#zmq-publisher) - - [Playback](#playback) - - [MPD Player](#mpd-player) - - [RFID](#rfid) - - [Cards](#cards) - - [Timer](#timer) - - [Volume](#volume) - - [GPIO](#gpio) - - [WLAN](#wlan) - - [Spotify](#spotify) - - [Others](#others) - - [Start-up stuff](#start-up-stuff) -- [Debug Tools](#debug-tools) -- [WebUI](#webui) -- [Installation Procedure](#installation-procedure) -- [Documentation](#documentation) +- [Feature Status](#feature-status) + - [Table of Contents](#table-of-contents) + - [Jukebox Core App](#jukebox-core-app) + - [Base](#base) + - [Via RPC](#via-rpc) + - [Config handler](#config-handler) + - [ZMQ Publisher](#zmq-publisher) + - [Playback](#playback) + - [MPD Player](#mpd-player) + - [RFID](#rfid) + - [Cards](#cards) + - [Timer](#timer) + - [Volume](#volume) + - [GPIO](#gpio) + - [WLAN](#wlan) + - [Spotify](#spotify) + - [Others](#others) + - [Start-up stuff](#start-up-stuff) + - [Debug Tools](#debug-tools) + - [WebUI](#webui) + - [Installation Procedure](#installation-procedure) + - [Documentation](#documentation) ## Jukebox Core App diff --git a/documentation/content/userguide/README.md b/documentation/content/userguide/README.md index 5a822a41f..445ba9421 100644 --- a/documentation/content/userguide/README.md +++ b/documentation/content/userguide/README.md @@ -22,4 +22,3 @@ * [Feature Status](../developers/status.md) * [Known Issues](../developers/known-issues.md) * [Developer Reference](../developers) - diff --git a/documentation/content/userguide/audio.md b/documentation/content/userguide/audio.md index d948e1035..d4b06b481 100644 --- a/documentation/content/userguide/audio.md +++ b/documentation/content/userguide/audio.md @@ -1,4 +1,4 @@ -# Audio +# Audio ## Configuration @@ -101,10 +101,10 @@ For other audio configuration options, please look at the `jukebox.yaml` for now Directly edit `jukebox.yaml` following the steps: [Best practice procedure](configuraton.md#best-practice-procedure). - ## Developer Information The optional processing stages *Equalizer* and *Mono down mix* are realized by PulseAudio plugins. The processing chain is + ``` player --> mono mix --> equalizer --> hardware sink ``` diff --git a/documentation/content/userguide/autohotspot.md b/documentation/content/userguide/autohotspot.md index a283a94f2..7f064cb53 100644 --- a/documentation/content/userguide/autohotspot.md +++ b/documentation/content/userguide/autohotspot.md @@ -38,9 +38,9 @@ The whole hotspot configuration can be found at The following parameters are relevant: -- `ssid` for the displayed hotspot name -- `wpa_passphrase` for the password of the hotspot -- `country_code` the country you are currently in +- `ssid` for the displayed hotspot name +- `wpa_passphrase` for the password of the hotspot +- `country_code` the country you are currently in ``` bash $ cat /etc/hostapd/hostapd.conf diff --git a/documentation/content/userguide/installation.md b/documentation/content/userguide/installation.md index 4b58ea4a9..109423faf 100644 --- a/documentation/content/userguide/installation.md +++ b/documentation/content/userguide/installation.md @@ -22,9 +22,9 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr * Set locale settings * Switch to the `Services` tab. Enable SSH with "Use password authentication" * Click `Save` -6. In the same dialog, click `Yes` -7. Confirm the next warning about erasing the SD card with `Yes` -8. Wait for the imaging process to be finished (it'll take a few minutes) +7. In the same dialog, click `Yes` +8. Confirm the next warning about erasing the SD card with `Yes` +9. Wait for the imaging process to be finished (it'll take a few minutes)
@@ -43,7 +43,6 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to $ touch ssh ``` - 5. Set up your Wifi connection. *Mac* @@ -100,7 +99,6 @@ This will switch directly to the specified feature branch during installation. > [!NOTE] > For all branches *except* the current Release, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Steps to install](../developers/development-environment.md#steps-to-install) - If you suspect an error you can monitor the installation-process with ```bash From b7480b8845be343f111d83a78a02e1dd96f985e2 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 16 Nov 2023 22:10:07 +0100 Subject: [PATCH 018/121] fix rename --- .../rfid/hardware/template_new_reader/{README.rst => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/jukebox/components/rfid/hardware/template_new_reader/{README.rst => README.md} (100%) diff --git a/src/jukebox/components/rfid/hardware/template_new_reader/README.rst b/src/jukebox/components/rfid/hardware/template_new_reader/README.md similarity index 100% rename from src/jukebox/components/rfid/hardware/template_new_reader/README.rst rename to src/jukebox/components/rfid/hardware/template_new_reader/README.md From acf6ec0b4832db6bea2855a2d0fba4079037ad18 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Thu, 16 Nov 2023 22:49:57 +0100 Subject: [PATCH 019/121] Future3 fix venv usage (#2111) * fix node setup nodejs includes npm. fails on extra installation folder keyrings might not be created yet * install python packages via pip in venv * move venv to project root * remove outdated comments / messages about venv * remove hardwired path. replace during installation * fix dead variable * #2112 fix 'not tagged as plugin callable' --- .github/workflows/pythonpackage_future3.yml | 2 -- docker/armv7/jukebox.Dockerfile | 5 ++- docker/jukebox.Dockerfile | 4 +-- .../developers/development-environment.md | 3 +- installation/includes/00_constants.sh | 2 +- installation/routines/setup_jukebox_core.sh | 32 ++++++++++--------- installation/routines/setup_jukebox_webapp.sh | 4 ++- installation/routines/setup_mpd.sh | 2 +- requirements.txt | 14 +++++--- .../default-services/jukebox-daemon.service | 4 +-- .../components/rfid/configure/__init__.py | 7 ---- src/jukebox/jukebox/multitimer.py | 1 + 12 files changed, 39 insertions(+), 41 deletions(-) diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index 757edb6f8..48e27b15d 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -34,8 +34,6 @@ jobs: sudo apt-get update sudo apt-get install -y libasound2-dev pulseaudio python3 -m pip install --upgrade pip - pip3 install wheel - pip3 install spidev pip3 install -r requirements.txt # For operation of the Jukebox, ZMQ must be compiled from sources due to Websocket support # Also install all optional dependencies diff --git a/docker/armv7/jukebox.Dockerfile b/docker/armv7/jukebox.Dockerfile index a0e872325..d81c447de 100644 --- a/docker/armv7/jukebox.Dockerfile +++ b/docker/armv7/jukebox.Dockerfile @@ -25,11 +25,10 @@ RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ at wget gcc \ mpc mpg123 git ffmpeg spi-tools netcat alsa-tools \ - python3 python3-venv python3-dev python3-pip python3-setuptools python3-mutagen python3-gpiozero + python3 python3-venv python3-dev python3-mutagen #samba samba-common-bin #raspberrypi-kernel-headers #resolvconf -#python3-spidev # Install Jukebox # Install libzmq with Websocket support from pre-compiled source @@ -57,7 +56,7 @@ RUN mkdir -p ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ # zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts; \ # make && make install; -ENV VIRTUAL_ENV=/opt/venv +ENV VIRTUAL_ENV=${INSTALLATION_PATH}/.venv RUN python3 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index a455ec9d2..5e7a08915 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -25,9 +25,9 @@ RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ gcc at wget \ espeak mpc mpg123 git ffmpeg spi-tools netcat \ - python3 python3-venv python3-dev python3-pip python3-mutagen python3-gpiozero + python3 python3-venv python3-dev python3-mutagen -ENV VIRTUAL_ENV=/opt/venv +ENV VIRTUAL_ENV=${INSTALLATION_PATH}/.venv RUN python3 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" diff --git a/documentation/content/developers/development-environment.md b/documentation/content/developers/development-environment.md index 44b748ebb..62eaeaec6 100644 --- a/documentation/content/developers/development-environment.md +++ b/documentation/content/developers/development-environment.md @@ -50,8 +50,7 @@ developing. The jukebox also runs on any Linux machine. The Raspberry Pi specific stuff will not work of course. That is no issue depending our your -development area. USB RFID Readers, however, will work. You may setup a -Python virtual environment or a conda virtual environment. You will have +development area. USB RFID Readers, however, will work. You will have to install and configure [MPD (Music Player Daemon)](https://www.musicpd.org/). diff --git a/installation/includes/00_constants.sh b/installation/includes/00_constants.sh index f643cdc47..e19dd1327 100644 --- a/installation/includes/00_constants.sh +++ b/installation/includes/00_constants.sh @@ -1,7 +1,7 @@ RPI_BOOT_CONFIG_FILE="/boot/config.txt" SHARED_PATH="${INSTALLATION_PATH}/shared" SETTINGS_PATH="${SHARED_PATH}/settings" -SYSTEMD_USR_PATH="/usr/lib/systemd/user/" +SYSTEMD_USR_PATH="/usr/lib/systemd/user" # The default upstream user, release branch, and develop branch # These are used to prepare the repo for developers diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index ab5f0a9bd..e6dfa6d8f 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -19,12 +19,11 @@ echo " -------------------------------------------------------------------- # Functions _jukebox_core_install_os_dependencies() { - echo "Install Jukebox OS dependencies" - sudo apt-get -y update; sudo apt-get -y install \ + echo " Install Jukebox OS dependencies" + sudo apt-get -y update && sudo apt-get -y install \ at \ alsa-utils \ - python3 python3-venv python3-dev python3-pip python3-setuptools \ - python3-rpi.gpio python3-gpiozero \ + python3 python3-venv python3-dev \ espeak ffmpeg mpg123 \ pulseaudio pulseaudio-module-bluetooth pulseaudio-utils caps \ libasound2-dev \ @@ -32,13 +31,19 @@ _jukebox_core_install_os_dependencies() { --allow-downgrades \ --allow-remove-essential \ --allow-change-held-packages +} + +_jukebox_core_install_python_requirements() { + echo " Install Python requirements" - VIRTUAL_ENV="${HOME_PATH}/.venv" + cd "${INSTALLATION_PATH}" || exit_on_error + + VIRTUAL_ENV="${INSTALLATION_PATH}/.venv" python3 -m venv $VIRTUAL_ENV - PATH="$VIRTUAL_ENV/bin:$PATH" source "$VIRTUAL_ENV/bin/activate" pip install --upgrade pip + pip install --no-cache-dir -r "${INSTALLATION_PATH}/requirements.txt" } _jukebox_core_configure_pulseaudio() { @@ -111,12 +116,6 @@ _jukebox_core_build_and_install_pyzmq() { fi } -_jukebox_core_install_python_requirements() { - echo " Install requirements" - cd "${INSTALLATION_PATH}" || exit_on_error - pip install --no-cache-dir -r "${INSTALLATION_PATH}/requirements.txt" -} - _jukebox_core_install_settings() { echo " Register Jukebox settings" cp -f "${INSTALLATION_PATH}/resources/default-settings/jukebox.default.yaml" "${SETTINGS_PATH}/jukebox.yaml" @@ -125,8 +124,11 @@ _jukebox_core_install_settings() { _jukebox_core_register_as_service() { echo " Register Jukebox Core user service" - sudo cp -f "${INSTALLATION_PATH}/resources/default-services/jukebox-daemon.service" "${SYSTEMD_USR_PATH}" - sudo chmod 644 "${SYSTEMD_USR_PATH}/jukebox-daemon.service" + + local jukebox_service="${SYSTEMD_USR_PATH}/jukebox-daemon.service" + sudo cp -f "${INSTALLATION_PATH}/resources/default-services/jukebox-daemon.service" "${jukebox_service}" + sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${jukebox_service}" + sudo chmod 644 "${jukebox_service}" systemctl --user daemon-reload systemctl --user enable jukebox-daemon.service @@ -136,8 +138,8 @@ setup_jukebox_core() { echo "Install Jukebox Core" | tee /dev/fd/3 _jukebox_core_install_os_dependencies - _jukebox_core_configure_pulseaudio _jukebox_core_install_python_requirements + _jukebox_core_configure_pulseaudio _jukebox_core_build_and_install_pyzmq _jukebox_core_install_settings _jukebox_core_register_as_service diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index a2f9e014a..a0e195622 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -32,10 +32,12 @@ _jukebox_webapp_install_node() { else # install NodeJS and npm as recommended in # https://github.com/nodesource/distributions + sudo apt-get install -y ca-certificates curl gnupg + sudo mkdir -p /etc/apt/keyrings curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list sudo apt-get update - sudo apt-get install nodejs npm -y + sudo apt-get install -y nodejs fi fi diff --git a/installation/routines/setup_mpd.sh b/installation/routines/setup_mpd.sh index b3a1c3473..6b72b5f9a 100644 --- a/installation/routines/setup_mpd.sh +++ b/installation/routines/setup_mpd.sh @@ -36,7 +36,7 @@ setup_mpd() { local MPD_EXECUTE_INSTALL=true - if [[ -f ${MPD_CONF_PATH} || -f ${SYSTEMD_PATH}/mpd.service ]]; then + if [[ -f ${MPD_CONF_PATH} || -f ${SYSTEMD_USR_PATH}/mpd.service ]]; then echo " It seems there is a MPD already installed." echo " Note: It is important that MPD runs as a user service!" echo " Would you like to overwrite your configuration? [Y/n] " | tee /dev/fd/3 diff --git a/requirements.txt b/requirements.txt index 1d9e9ae83..f9f452799 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,9 @@ +# Common python packages +# You need to install these with `python -m pip install --no-cache-dir -r requirements.txt` + +setuptools +wheel + # Note: # sudo apt install libasound2-dev required on some machines @@ -13,10 +19,9 @@ requests # For the publisher event reactor loop: tornado -# RPi's GPIO packages: these are installed via APT on the PI -# On regular machines, install them manually if needed for development -# RPi.GPIO -# gpiozero +# RPi's GPIO packages: +RPi.GPIO +gpiozero # PyZMQ is a special case: # On the PI, it needs to be compiled with special options to enable Websocket support @@ -28,4 +33,3 @@ tornado flake8>=4.0.0 pytest mock - diff --git a/resources/default-services/jukebox-daemon.service b/resources/default-services/jukebox-daemon.service index f2557480e..050897e14 100644 --- a/resources/default-services/jukebox-daemon.service +++ b/resources/default-services/jukebox-daemon.service @@ -10,8 +10,8 @@ After=network.target sound.target mpd.service pulseaudio.service Requires=mpd.service pulseaudio.service [Service] -WorkingDirectory=/home/pi/RPi-Jukebox-RFID/src/jukebox -ExecStart=/bin/bash -c 'source /home/pi/.venv/bin/activate && /home/pi/.venv/bin/python run_jukebox.py' +WorkingDirectory=%%INSTALLATION_PATH%%/src/jukebox +ExecStart=/bin/bash -c 'source %%INSTALLATION_PATH%%/.venv/bin/activate && python run_jukebox.py' StandardOutput=inherit StandardError=inherit Restart=always diff --git a/src/jukebox/components/rfid/configure/__init__.py b/src/jukebox/components/rfid/configure/__init__.py index bf8dec4a9..7f9e232f2 100755 --- a/src/jukebox/components/rfid/configure/__init__.py +++ b/src/jukebox/components/rfid/configure/__init__.py @@ -26,13 +26,6 @@ def reader_install_dependencies(reader_path: str, dependency_install: str) -> No if os.path.exists(reader_path + '/requirements.txt'): # The python dependencies (if any) print("\nInstalling/Checking Python dependencies ...\n") - print("IMPORTANT for developers: Python dependencies will be installed using " - " $ pip install --upgrade -r requirements.txt'\n" - " i.e. on system level. This is target for the default RPI setup. " - "If you do not want that, but rather have them in a local or virtual environment, " - "hit No here and manually install the dependencies from your virtual environment\n" - " $ pip install --upgrade -r requirements.txt'\n" - "It is no problem to install them after running this script.\n\n") if dependency_install == 'auto' or pyil.input_yesno("Install Python dependencies?", blank=True, prompt_color=Colors.lightgreen, prompt_hint=True): print(f"{'=' * 80}") diff --git a/src/jukebox/jukebox/multitimer.py b/src/jukebox/jukebox/multitimer.py index 455b8ba75..b03aae83f 100644 --- a/src/jukebox/jukebox/multitimer.py +++ b/src/jukebox/jukebox/multitimer.py @@ -249,6 +249,7 @@ def start(self, iterations=None, wait_seconds_per_iteration=None): self._function = self._callee(*self.class_args, iterations=self._iterations, **self.class_kwargs) super().start(wait_seconds_per_iteration) + @plugin.tag def get_state(self): return {'enabled': self.is_alive(), 'wait_seconds_per_iteration': self.get_timeout(), From babb237a34cf4765523082d7845c428d41240d0d Mon Sep 17 00:00:00 2001 From: s-martin Date: Thu, 16 Nov 2023 23:57:28 +0100 Subject: [PATCH 020/121] fix docker description for windows (#2114) --- documentation/content/developers/docker.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/documentation/content/developers/docker.md b/documentation/content/developers/docker.md index 012b8d3db..0def11610 100644 --- a/documentation/content/developers/docker.md +++ b/documentation/content/developers/docker.md @@ -129,24 +129,26 @@ $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml d 6. Execute `$INSTALL_DIR/bin/pulseaudio.exe` -7. Run `docker-compose` +7. Make sure Docker is running (e.g. start Docker Desktop) + +8. Run `docker-compose` ``` bash // Build Images - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml build + $ docker-compose -f docker/docker-compose.yml build // Run Docker Environment - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml up + $ docker-compose -f docker/docker-compose.yml up // Shuts down Docker containers and Docker network - $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.windows.yml down + $ docker-compose -f docker/docker-compose.yml down ``` ## Test & Develop The Dockerfile is defined to start all Phoniebox related services. -Open in your browser to see the web application. +Open in your browser to see the web application. While the `webapp` container does not require a reload while working on it (hot-reload is enabled), you will have to restart your `jukebox` From dfb9e931fb0ed99750b30c3a52bbcf16139727f0 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sat, 18 Nov 2023 14:40:10 +0100 Subject: [PATCH 021/121] Cleanup installation messages (#2109) * cleanup installation messages use clear for each new option added headers use correct urls in finish message fixed some typos and wording * fix usage with multiple ip addresses (ipv4/ipv6) use single call to get all information during installation show and read out only first ip in webui * fix typo in translation * fix order for gateway / interface * harmonize read answer option * fix FIN_MESSAGE * fix line break on OS check --- installation/includes/02_helpers.sh | 2 +- installation/includes/03_welcome.sh | 12 +- installation/includes/05_finish.sh | 19 ++- installation/routines/customize_options.sh | 142 ++++++++++++------ installation/routines/install.sh | 1 + installation/routines/optimize_boot_time.sh | 19 +-- installation/routines/setup_mpd.sh | 6 +- .../components/hostif/linux/__init__.py | 3 + src/webapp/public/locales/de/translation.json | 2 +- 9 files changed, 125 insertions(+), 81 deletions(-) diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index d2e1162f1..9ad8e71b8 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -45,7 +45,7 @@ check_os_type() { local os_type os_type=$(uname -m) - echo "Checking OS type ... $os_type" | tee /dev/fd/3 + echo -e "\nChecking OS type '$os_type'" | tee /dev/fd/3 if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then echo -e " ... OK!\n" | tee /dev/fd/3 diff --git a/installation/includes/03_welcome.sh b/installation/includes/03_welcome.sh index 1b2de1600..7aeaa56ab 100644 --- a/installation/includes/03_welcome.sh +++ b/installation/includes/03_welcome.sh @@ -19,16 +19,18 @@ Depending on your hardware, this installation might last around 60 minutes (usually it's faster). It updates OS packages, installs Phoniebox dependencies and registers settings. Be patient and don't let your computer go to -sleep. It might disconnect your SSH connection causing the -interruption of the installation process. +sleep. It might disconnect your SSH connection causing +the interruption of the installation process. +Consider starting the installation in a terminal +multiplexer like 'screen' or 'tmux' to avoid this. By the way, you can follow the installation details here in a separate SSH session: cd; tail -f ${INSTALLATION_LOGFILE} -Let's set up your Phoniebox now?! [Y/n]" 1>&3 - - read -rp "Do you want to install? [Y/n] " response +Let's set up your Phoniebox. +Do you want to start the installation? [Y/n]" 1>&3 + read -r response case "$response" in [nN][oO]|[nN]) exit diff --git a/installation/includes/05_finish.sh b/installation/includes/05_finish.sh index 513d757d2..50c2f6d9d 100644 --- a/installation/includes/05_finish.sh +++ b/installation/includes/05_finish.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash finish() { - echo -e " ---- +local local_hostname=$(hostname) + echo -e "####################### FINISHED ######################## Installation complete! @@ -11,14 +11,13 @@ ${FIN_MESSAGE} In order to start, you need to reboot your Raspberry Pi. Your SSH connection will disconnect. -After the reboot, open either http://raspberrypi.local -(for Mac / iOS) or http://[IP-ADDRESS] (for Android / Windows) -in a browser to get started. Don't forget to upload files -via Samba. +After the reboot, you can access the WebApp in your browser at +http://${local_hostname}.local or http://${CURRENT_IP_ADDRESS} +Don't forget to upload files. Do you want to reboot now? [Y/n]" 1>&3 - read -rp "Do you want to reboot now? [Y/n] " response + read -r response case "$response" in [nN][oO]|[nN]) echo "Reboot aborted" | tee /dev/fd/3 @@ -41,14 +40,14 @@ Do you want to reboot now? [Y/n]" 1>&3 exit_on_error () { echo -e "\n****************************************" | tee /dev/fd/3 - echo -e "ERROR OCCURRED! + echo "ERROR OCCURRED! A non-recoverable error occurred. Check install log for details:" | tee /dev/fd/3 echo "$INSTALLATION_LOGFILE" | tee /dev/fd/3 - echo -e "****************************************" | tee /dev/fd/3 + echo "****************************************" | tee /dev/fd/3 if [[ -n $1 ]]; then echo "$1" | tee /dev/fd/3 - echo -e "****************************************" | tee /dev/fd/3 + echo "****************************************" | tee /dev/fd/3 fi echo "Abort!" exit 1 diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index d12b42390..d5ac7c547 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -2,10 +2,21 @@ _option_static_ip() { # ENABLE_STATIC_IP - CURRENT_IP_ADDRESS=$(hostname -I) - echo "Would you like to set a static IP (will be ${CURRENT_IP_ADDRESS})? -It'll save a lot of start up time. This can be changed later. -[Y/n] " 1>&3 + # Using the dynamically assigned IP address as it is the best guess to be free + # Reference: https://unix.stackexchange.com/a/505385 + CURRENT_ROUTE=$(ip route get 8.8.8.8) + CURRENT_GATEWAY=$(echo "${CURRENT_ROUTE}" | awk '{ print $3; exit }') + CURRENT_INTERFACE=$(echo "${CURRENT_ROUTE}" | awk '{ print $5; exit }') + CURRENT_IP_ADDRESS=$(echo "${CURRENT_ROUTE}" | awk '{ print $7; exit }') + clear 1>&3 + echo "----------------------- STATIC IP ----------------------- + +Setting a static IP will save a lot of start up time. +The static adress will be '${CURRENT_IP_ADDRESS}' +from interface '${CURRENT_INTERFACE}' +with the gateway '${CURRENT_GATEWAY}'. + +Set a static IP? [Y/n]" 1>&3 read -r response case "$response" in [nN][oO]|[nN]) @@ -19,7 +30,13 @@ It'll save a lot of start up time. This can be changed later. _option_ipv6() { # DISABLE_IPv6 - echo "Do you want to disable IPv6? [Y/n] " 1>&3 + clear 1>&3 + echo "------------------------- IP V6 ------------------------- + +IPv6 is only needed if you intend to use it. +Otherwise it can be disabled. + +Do you want to disable IPv6? [Y/n]" 1>&3 read -r response case "$response" in [nN][oO]|[nN]) @@ -33,14 +50,17 @@ _option_ipv6() { _option_autohotspot() { # ENABLE_AUTOHOTSPOT - echo "Do you want to enable a WiFi hotspot on demand? + clear 1>&3 + echo "---------------------- AUTOHOTSPOT ---------------------- + When enabled, this service spins up a WiFi hotspot when the Phoniebox is unable to connect to a known WiFi. This way you can still access it. -[y/N] " 1>&3 + +Do you want to enable an Autohotpot? [y/N]" 1>&3 read -r response case "$response" in - [yY]) + [yY][eE][sS]|[yY]) ENABLE_AUTOHOTSPOT=true ;; *) @@ -51,7 +71,7 @@ WiFi. This way you can still access it. echo "Do you want to set a custom Password? (default: ${AUTOHOTSPOT_PASSWORD}) [y/N] " 1>&3 read -r response_pw_q case "$response_pw_q" in - [yY]) + [yY][eE][sS]|[yY]) while [ $(echo ${response_pw}|wc -m) -lt 8 ] do echo "Please type the new password (at least 8 character)." 1>&3 @@ -80,9 +100,13 @@ WiFi. This way you can still access it. _option_bluetooth() { # DISABLE_BLUETOOTH - echo "Do you want to disable Bluetooth? -Turn off Bluetooth if you do not plan to use it. It saves energy and start up time. -[Y/n] " 1>&3 + clear 1>&3 + echo "----------------------- BLUETOOTH ----------------------- + +Turning off Bluetooth will save energy and +start up time, if you do not plan to use it. + +Do you want to disable Bluetooth? [Y/n]" 1>&3 read -r response case "$response" in [nN][oO]|[nN]) @@ -96,10 +120,15 @@ Turn off Bluetooth if you do not plan to use it. It saves energy and start up ti _option_samba() { # ENABLE_SAMBA - echo "Samba will be installed. It is required to conveniently copy files to -your Phoniebox via a network share. If you don't need it, feel free to skip -the installation. If you are unsure, stick to YES! -[Y/n] " 1>&3 + clear 1>&3 + echo "------------------------- SAMBA ------------------------- + +Samba is required to conveniently copy files +to your Phoniebox via a network share. +If you don't need it, feel free to skip the installation. +If you are unsure, stick to YES! + +Do you want to install Samba? [Y/n]" 1>&3 read -r response case "$response" in [nN][oO]|[nN]) @@ -114,10 +143,13 @@ the installation. If you are unsure, stick to YES! _option_webapp() { # ENABLE_WEBAPP - echo "Would you like to install the web application? -This is only required if you want to use a graphical interface -to manage your Phoniebox! -[Y/n] " 1>&3 + clear 1>&3 + echo "------------------------ WEBAPP ------------------------- + +This is only required if you want to use +a graphical interface to manage your Phoniebox! + +Would you like to install the web application? [Y/n]" 1>&3 read -r response case "$response" in [nN][oO]|[nN]) @@ -132,14 +164,18 @@ to manage your Phoniebox! _option_kiosk_mode() { # ENABLE_KIOSK_MODE - echo "Would you like to enable the Kiosk Mode? -If you have a screen attached to your RPi, this will launch the -web application right after boot. It will only install the necessary -xserver dependencies and not the entire RPi desktop environment. -[y/N] " 1>&3 + clear 1>&3 + echo "----------------------- KIOSK MODE ---------------------- + +If you have a screen attached to your RPi, +this will launch the web application right after boot. +It will only install the necessary xserver dependencies +and not the entire RPi desktop environment. + +Would you like to enable the Kiosk Mode? [y/N]" 1>&3 read -r response case "$response" in - [yY]) + [yY][eE][sS]|[yY]) ENABLE_KIOSK_MODE=true ;; *) @@ -150,12 +186,16 @@ xserver dependencies and not the entire RPi desktop environment. _options_update_raspi_os() { # UPDATE_RASPI_OS - echo "Would you like to update the operating system? -This shall be done eventually, but increases the installation time a lot. -[Y/n] " 1>&3 + clear 1>&3 + echo "----------------------- UPDATE OS ----------------------- + +This shall be done eventually, +but increases the installation time a lot. + +Would you like to update the operating system? [Y/n]" 1>&3 read -r response case "$response" in - [nN]) + [nN][oO]|[nN]) UPDATE_RASPI_OS=false ;; *) @@ -167,18 +207,25 @@ This shall be done eventually, but increases the installation time a lot. _option_disable_onboard_audio() { # Disable BCM on-chip audio (typically Headphones) # not needed when external sound card is sued + clear 1>&3 + echo "--------------------- ON-CHIP AUDIO --------------------- + +If you are using an external sound card (e.g. USB, +HifiBerry, PirateAudio, etc), we recommend to disable +the on-chip audio. It will make the ALSA sound +configuration easier. +If you are planning to only use Bluetooth speakers, +leave the on-chip audio enabled! +(This will touch your boot configuration in +${RPI_BOOT_CONFIG_FILE}. +We will do our best not to mess anything up. However, +a backup copy will be written to +${DISABLE_ONBOARD_AUDIO_BACKUP} ) - echo -e "Disable Pi's on-chip audio (headphone / jack output)? -If you are using an external sound card (e.g. USB, HifiBerry, PirateAudio, etc), -we recommend to disable the on-chip audio. It will make the ALSA sound configuration easier. -If you are planning to only use Bluetooth speakers, leave the on-chip audio enabled! -(This will touch your boot configuration in ${RPI_BOOT_CONFIG_FILE}. -We will do our best not to mess anything up. However, a backup copy will be written to -${DISABLE_ONBOARD_AUDIO_BACKUP} if things go pear-shaped.) -[y/N] " 1>&3 +Disable Pi's on-chip audio (headphone / jack output)? [y/N]" 1>&3 read -r response case "$response" in - [yY]) + [yY][eE][sS]|[yY]) DISABLE_ONBOARD_AUDIO=true ;; *) @@ -196,27 +243,28 @@ _option_webapp_devel_build() { if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = "release-only" ]]; then ENABLE_WEBAPP_PROD_DOWNLOAD=false fi - if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = false ]]; then - echo -e "Your are installing from a non-release branch. + clear 1>&3 + echo "--------------------- WEBAPP NODE --------------------- + +You are installing from a non-release branch. This means, you will need to build the web app locally. For that you'll need Node. -Do you want to install Node? [Y/n] " 1>&3 + +Do you want to install Node? [Y/n]" 1>&3 read -r response case "$response" in - [nN]) + [nN][oO]|[nN]) ENABLE_INSTALL_NODE=false ;; *) ;; esac - # This message will be displayed at the end of the installation process - FIN_MESSAGE="$FIN_MESSAGE\n\nATTENTION: You need to build the web app locally with + FIN_MESSAGE="$FIN_MESSAGE\nATTENTION: You need to build the web app locally with $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u This must be done after reboot, due to memory restrictions. Read the documentation regarding local Web App builds!" - ENABLE_WEBAPP_PROD_DOWNLOAD=false fi fi } @@ -224,8 +272,8 @@ Do you want to install Node? [Y/n] " 1>&3 customize_options() { echo "Customize Options starts" - _option_static_ip _option_ipv6 + _option_static_ip _option_autohotspot _option_bluetooth _option_disable_onboard_audio diff --git a/installation/routines/install.sh b/installation/routines/install.sh index f7db0e1d5..ca25a17a3 100644 --- a/installation/routines/install.sh +++ b/installation/routines/install.sh @@ -1,5 +1,6 @@ install() { check_os_type + clear 1>&3 customize_options clear 1>&3 set_raspi_config diff --git a/installation/routines/optimize_boot_time.sh b/installation/routines/optimize_boot_time.sh index ef40ed1fb..2fea66a86 100644 --- a/installation/routines/optimize_boot_time.sh +++ b/installation/routines/optimize_boot_time.sh @@ -40,26 +40,17 @@ _optimize_handle_network_connection() { echo " Skipping. Already set up!" | tee /dev/fd/3 else # DHCP has not been configured - # Reference: https://unix.stackexchange.com/a/307790/478030 - INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$') - - # Reference: https://serverfault.com/a/31179/431930 - GATEWAY=$(route -n | grep 'UG[ \t]' | awk '{print $2}') - - # Using the dynamically assigned IP address as it is the best guess to be free - # Reference: https://unix.stackexchange.com/a/48254/478030 - CURRENT_IP_ADDRESS=$(hostname -I) - echo " * ${INTERFACE} is the default network interface" | tee /dev/fd/3 - echo " * ${GATEWAY} is the Router Gateway address" | tee /dev/fd/3 + echo " * ${CURRENT_INTERFACE} is the default network interface" | tee /dev/fd/3 + echo " * ${CURRENT_GATEWAY} is the Router Gateway address" | tee /dev/fd/3 echo " * Using ${CURRENT_IP_ADDRESS} as the static IP for now" | tee /dev/fd/3 sudo tee -a $DHCP_CONF <<-EOF ## Jukebox DHCP Config -interface ${INTERFACE} +interface ${CURRENT_INTERFACE} static ip_address=${CURRENT_IP_ADDRESS}/24 -static routers=${GATEWAY} -static domain_name_servers=${GATEWAY} +static routers=${CURRENT_GATEWAY} +static domain_name_servers=${CURRENT_GATEWAY} EOF diff --git a/installation/routines/setup_mpd.sh b/installation/routines/setup_mpd.sh index 6b72b5f9a..53b9ac01b 100644 --- a/installation/routines/setup_mpd.sh +++ b/installation/routines/setup_mpd.sh @@ -37,9 +37,9 @@ setup_mpd() { local MPD_EXECUTE_INSTALL=true if [[ -f ${MPD_CONF_PATH} || -f ${SYSTEMD_USR_PATH}/mpd.service ]]; then - echo " It seems there is a MPD already installed." - echo " Note: It is important that MPD runs as a user service!" - echo " Would you like to overwrite your configuration? [Y/n] " | tee /dev/fd/3 + echo "It seems there is a MPD already installed. +Note: It is important that MPD runs as a user service! +Would you like to overwrite your configuration? [Y/n]" 1>&3 read -r response case "$response" in [nN][oO]|[nN]) diff --git a/src/jukebox/components/hostif/linux/__init__.py b/src/jukebox/components/hostif/linux/__init__.py index 379af014d..582074413 100644 --- a/src/jukebox/components/hostif/linux/__init__.py +++ b/src/jukebox/components/hostif/linux/__init__.py @@ -193,6 +193,9 @@ def get_ip_address(): ip_address = p.stdout.strip().decode() else: ip_address = '127.0.0.1' + + # only get first if multiple adresses are present (ipv4/ipv6) + ip_address = ip_address.split(' ')[0] return ip_address diff --git a/src/webapp/public/locales/de/translation.json b/src/webapp/public/locales/de/translation.json index 25b11b703..85d39b879 100644 --- a/src/webapp/public/locales/de/translation.json +++ b/src/webapp/public/locales/de/translation.json @@ -227,7 +227,7 @@ }, "disk-usage": { "label": "Speicherverbrauch", - "loading-error": "Die Angaben zum Speicherverbrauch konntent nicht geladen werden.", + "loading-error": "Die Angaben zum Speicherverbrauch konnten nicht geladen werden.", "result": "{{used}} GB von {{total}} GB sind belegt ({{result}}%)" }, "ip-address": { From 2a0bb20bcb83000a9d7b0f0898aba0c8e811841b Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Fri, 24 Nov 2023 23:24:29 +0100 Subject: [PATCH 022/121] Update some links in the documentation (#2122) * Update some links in the documentation * Rename docs folder userguide to builders * Update some headlines --- README.md | 2 +- documentation/README.md | 14 ++++++++------ .../content/{userguide => builders}/README.md | 2 +- .../content/{userguide => builders}/audio.md | 0 .../{userguide => builders}/autohotspot.md | 0 .../bluetooth-audio-buttons.md | 0 .../{userguide => builders}/card-database.md | 2 +- .../content/{userguide => builders}/concepts.md | 0 .../{userguide => builders}/configuration.md | 0 .../{userguide => builders}/installation.md | 2 +- .../content/{userguide => builders}/mock_gpio.png | Bin .../content/{userguide => builders}/rfid.md | 0 .../content/{userguide => builders}/system.md | 0 .../{userguide => builders}/troubleshooting.md | 0 .../content/{userguide => builders}/update.md | 0 documentation/content/developers/coreapps.md | 8 ++++---- .../content/developers/development-environment.md | 2 +- documentation/content/developers/rfid/basics.md | 4 ++-- .../content/developers/rfid/mock_reader.md | 2 +- documentation/content/developers/status.md | 2 +- 20 files changed, 21 insertions(+), 19 deletions(-) rename documentation/content/{userguide => builders}/README.md (97%) rename documentation/content/{userguide => builders}/audio.md (100%) rename documentation/content/{userguide => builders}/autohotspot.md (100%) rename documentation/content/{userguide => builders}/bluetooth-audio-buttons.md (100%) rename documentation/content/{userguide => builders}/card-database.md (97%) rename documentation/content/{userguide => builders}/concepts.md (100%) rename documentation/content/{userguide => builders}/configuration.md (100%) rename documentation/content/{userguide => builders}/installation.md (97%) rename documentation/content/{userguide => builders}/mock_gpio.png (100%) rename documentation/content/{userguide => builders}/rfid.md (100%) rename documentation/content/{userguide => builders}/system.md (100%) rename documentation/content/{userguide => builders}/troubleshooting.md (100%) rename documentation/content/{userguide => builders}/update.md (100%) diff --git a/README.md b/README.md index b95b0e8c3..8b494698d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ project check out the [documentation of Version 2]( [!IMPORTANT] -> Currently, the installation does only work on Raspberry Pi's with ARMv7 and ARMv8 architecture, so 2, 3 and 4! Pi 1 and Zero's are currently unstable and will require a bit more work! +> Currently, the installation does only work on Raspberry Pi's with ARMv7 and ARMv8 architecture, so 2, 3 and 4! Pi 1 and Zero's are currently unstable and will require a bit more work! Pi 4 and 5 are an excess ;-) Before you can install the Phoniebox software, you need to prepare your Raspberry Pi. diff --git a/documentation/content/userguide/mock_gpio.png b/documentation/content/builders/mock_gpio.png similarity index 100% rename from documentation/content/userguide/mock_gpio.png rename to documentation/content/builders/mock_gpio.png diff --git a/documentation/content/userguide/rfid.md b/documentation/content/builders/rfid.md similarity index 100% rename from documentation/content/userguide/rfid.md rename to documentation/content/builders/rfid.md diff --git a/documentation/content/userguide/system.md b/documentation/content/builders/system.md similarity index 100% rename from documentation/content/userguide/system.md rename to documentation/content/builders/system.md diff --git a/documentation/content/userguide/troubleshooting.md b/documentation/content/builders/troubleshooting.md similarity index 100% rename from documentation/content/userguide/troubleshooting.md rename to documentation/content/builders/troubleshooting.md diff --git a/documentation/content/userguide/update.md b/documentation/content/builders/update.md similarity index 100% rename from documentation/content/userguide/update.md rename to documentation/content/builders/update.md diff --git a/documentation/content/developers/coreapps.md b/documentation/content/developers/coreapps.md index 757af2ae4..be15f133a 100644 --- a/documentation/content/developers/coreapps.md +++ b/documentation/content/developers/coreapps.md @@ -13,14 +13,14 @@ $ ./run_app_name.py -h This is the main app and starts the Jukebox Core. -Usually this runs as a service, which is started automatically after boot-up. At times, it may be necessary to restart the service. For example after a configuration change. Not all configuration changes can be applied on-the-fly. See [Jukebox Configuration](../userguide/configuration.md#jukebox-configuration). +Usually this runs as a service, which is started automatically after boot-up. At times, it may be necessary to restart the service. For example after a configuration change. Not all configuration changes can be applied on-the-fly. See [Jukebox Configuration](../builders/configuration.md#jukebox-configuration). -For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. See [Troubleshooting](../userguide/troubleshooting.md). +For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. See [Troubleshooting](../builders/troubleshooting.md). ## Configuration Tools Before running the configuration tools, stop the Jukebox Core service. -See [Best practice procedure](../userguide/configuration.md#best-practice-procedure). +See [Best practice procedure](../builders/configuration.md#best-practice-procedure). ### `run_configure_audio.py` @@ -28,7 +28,7 @@ Setup tool to register the PulseAudio sinks as primary and secondary audio outpu Will also setup equalizer and mono down mixer in the pulseaudio config file. -Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../userguide/audio.md). +Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). ### `run_register_rfid_reader.py` diff --git a/documentation/content/developers/development-environment.md b/documentation/content/developers/development-environment.md index 62eaeaec6..d79a6101e 100644 --- a/documentation/content/developers/development-environment.md +++ b/documentation/content/developers/development-environment.md @@ -16,7 +16,7 @@ developing. 1. Install the latest Pi OS on a SD card. 2. Boot up your Raspberry Pi. -3. [Install](../userguide/installation.md) the Jukebox software as if you were building a +3. [Install](../builders/installation.md) the Jukebox software as if you were building a Phoniebox. You can install from your own fork and feature branch if you wish which can be changed later as well. The original repository will be set as `upstream`. diff --git a/documentation/content/developers/rfid/basics.md b/documentation/content/developers/rfid/basics.md index 5cd1d1e9f..ec8844263 100644 --- a/documentation/content/developers/rfid/basics.md +++ b/documentation/content/developers/rfid/basics.md @@ -6,7 +6,7 @@ Cards placed on the reader trigger an action. An action may be any callable plugin function through the RPC with any arguments. Typically, this would be "play some folder", but can also be "activate shutdown timer", or "increase volume". This is configured in the -[Card Database](../../userguide/card-database.md). +[Card Database](../../builders/card-database.md). You may configure a single or even multiple parallel readers (of different or identical types). @@ -79,7 +79,7 @@ For place-capable RFID readers enable dual action mode: a start action (e.g. pla #### card_removal_action: Dictionary -Executes the given function on card removal. Only relevant if place_not_swipe is true. The action is identical for all cards read on that reader. The removal-action can be set to ignored on a card-by-card basis. More on card action configurations in [RPC Commands](../userguide/rpc-commands.md). +Executes the given function on card removal. Only relevant if place_not_swipe is true. The action is identical for all cards read on that reader. The removal-action can be set to ignored on a card-by-card basis. More on card action configurations in [RPC Commands](../builders/rpc-commands.md). > [!NOTE] > Developer's note: The reason for a unique removal action for all cards is that card triggering and card removal are happening in two separate threads. Removal needs to be in a time-out thread. Thus, we would need to transport information from one thread to another. This can be done of course but is not implemented (yet). Ignoring card removal is much easier and works for now. diff --git a/documentation/content/developers/rfid/mock_reader.md b/documentation/content/developers/rfid/mock_reader.md index da90d5405..ece44f653 100644 --- a/documentation/content/developers/rfid/mock_reader.md +++ b/documentation/content/developers/rfid/mock_reader.md @@ -6,7 +6,7 @@ machine - probably in a Python virtual environment. **place-capable**: yes -If you [mock the GPIO pins](../userguide/gpio.md), this GUI will show the GPIO devices. +If you [mock the GPIO pins](../builders/gpio.md), this GUI will show the GPIO devices. ![image](mock_reader.png) diff --git a/documentation/content/developers/status.md b/documentation/content/developers/status.md index 3cc8d7f47..50968293c 100644 --- a/documentation/content/developers/status.md +++ b/documentation/content/developers/status.md @@ -183,7 +183,7 @@ Topics marked _in progress_ are already in the process of implementation by comm ### GPIO -- [x] All done! Read the docs at [GPIO Recipes](#userguide/gpioz:GPIO Recipes)! +- [x] All done! Read the docs at [GPIO Recipes](#builders/gpioz:GPIO Recipes)! - [ ] USB Buttons: It's a different category as it works similar to the RFID cards (in progress) ### WLAN From 94aa9dc6231a5d42337ade19fccf9885984af0e1 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:14:29 +0100 Subject: [PATCH 023/121] Future3/update docs (#2123) * Update pulseaudio link for docker mac setup * Update Docker docs --- documentation/content/developers/docker.md | 112 ++++++++++----------- 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/documentation/content/developers/docker.md b/documentation/content/developers/docker.md index 0def11610..96b5a64b6 100644 --- a/documentation/content/developers/docker.md +++ b/documentation/content/developers/docker.md @@ -14,16 +14,8 @@ need to adapt some of those commands to your needs. ## Prerequisites -1. Install required software - * Linux - * [Docker](https://docs.docker.com/engine/install/debian/) - * [Compose](https://docs.docker.com/compose/install/) - * Mac - * [Docker & Compose (Mac)](https://docs.docker.com/docker-for-mac/install/) - * [pulseaudio (Docker)](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html) - * Windows - * [Docker & Compose (Windows)](https://docs.docker.com/docker-for-windows/install/) - * [pulseaudio (Windows)](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) +1. Install required software: Docker, Compose and pulseaudio + * Check installations guide for [Mac](#mac), [Windows](#windows) or [Linux](#linux) 2. Pull the Jukebox repository: @@ -52,47 +44,10 @@ practice to isolate different components in different Docker images. They can be run individually or in combination. To do that, we use `docker-compose`. -### Linux - -Make sure you don\'t use `sudo` to run your `docker-compose`. Check out -Docker\'s [post-installation guide](https://docs.docker.com/engine/install/linux-postinstall/) for more information. - -```bash -// Build Images -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build - -// Run Docker Environment -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up - -// Shuts down Docker containers and Docker network -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down -``` - -Note: if you have `mpd` running on your system, you need to stop it -using: - -``` bash -$ sudo systemctl stop mpd.socket -$ sudo mpd --kill -``` - -Otherwise you might get the error message: - -``` bash -$ docker-compose -f docker-compose.yml -f docker-compose.linux.yml up -Starting mpd ... -Starting mpd ... error -(...) -Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already in use -``` - -Read these threads for details: [thread 1](https://unix.stackexchange.com/questions/456909/socket-already-in-use-but-is-not-listed-mpd) and [thread 2](https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num/5106755#5106755) - ### Mac -Remember, pulseaudio is a prerequisite. [Follow these -instructions](https://stackoverflow.com/a/50939994/1062438) for Mac -hosts. +1. [Install Docker & Compose (Mac)](https://docs.docker.com/docker-for-mac/install/) +2. [Install pulseaudio](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712) (Other references: [[1]](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html), [[2]](https://stackoverflow.com/a/50939994/1062438)) ``` bash // Build Images @@ -107,31 +62,32 @@ $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml d ### Windows -1. Download - [pulseaudio](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) +1. Install [Docker & Compose (Windows)](https://docs.docker.com/docker-for-windows/install/) + +2. Download [pulseaudio](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) -2. Uncompress somewhere in your user folder +3. Uncompress somewhere in your user folder -3. Edit `$INSTALL_DIR/etc/pulse/default.pa` +4. Edit `$INSTALL_DIR/etc/pulse/default.pa` -4. Add the following line +5. Add the following line ``` bash load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 ``` -5. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the +6. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the following line and change it to: ``` bash exit-idle-time = -1 ``` -6. Execute `$INSTALL_DIR/bin/pulseaudio.exe` +7. Execute `$INSTALL_DIR/bin/pulseaudio.exe` -7. Make sure Docker is running (e.g. start Docker Desktop) +8. Make sure Docker is running (e.g. start Docker Desktop) -8. Run `docker-compose` +9. Run `docker-compose` ``` bash // Build Images @@ -144,6 +100,46 @@ $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml d $ docker-compose -f docker/docker-compose.yml down ``` +### Linux + +1. Install Docker & Compose + * [Docker](https://docs.docker.com/engine/install/debian/) + * [Compose](https://docs.docker.com/compose/install/) +2. Make sure you don\'t use `sudo` to run your `docker-compose`. Check out +Docker\'s [post-installation guide](https://docs.docker.com/engine/install/linux-postinstall/) for more information. + +```bash +// Build Images +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build + +// Run Docker Environment +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up + +// Shuts down Docker containers and Docker network +$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down +``` + +Note: if you have `mpd` running on your system, you need to stop it +using: + +``` bash +$ sudo systemctl stop mpd.socket +$ sudo mpd --kill +``` + +Otherwise you might get the error message: + +``` bash +$ docker-compose -f docker-compose.yml -f docker-compose.linux.yml up +Starting mpd ... +Starting mpd ... error +(...) +Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already in use +``` + +Read these threads for details: [thread 1](https://unix.stackexchange.com/questions/456909/socket-already-in-use-but-is-not-listed-mpd) and [thread 2](https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num/5106755#5106755) + + ## Test & Develop The Dockerfile is defined to start all Phoniebox related services. From 924c7f3a4db584d26d45e188b44209e0f053d320 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:46:42 +0100 Subject: [PATCH 024/121] Remove content folder from docs (#2124) --- documentation/README.md | 12 ++++++------ documentation/{content => }/builders/README.md | 0 documentation/{content => }/builders/audio.md | 0 documentation/{content => }/builders/autohotspot.md | 0 .../builders/bluetooth-audio-buttons.md | 0 .../{content => }/builders/card-database.md | 0 documentation/{content => }/builders/concepts.md | 0 .../{content => }/builders/configuration.md | 0 .../{content => }/builders/installation.md | 0 documentation/{content => }/builders/mock_gpio.png | Bin documentation/{content => }/builders/rfid.md | 0 documentation/{content => }/builders/system.md | 0 .../{content => }/builders/troubleshooting.md | 0 documentation/{content => }/builders/update.md | 0 .../calendars/2019-Phoniebox-Calendar.jpg | Bin .../calendars/2020-Phoniebox-Calendar.jpg | Bin .../calendars/2021-Phoniebox-Calendar.jpg | Bin documentation/{content => }/developers/README.md | 2 +- documentation/{content => }/developers/coreapps.md | 0 .../{content => }/developers/developer-issues.md | 0 .../developers/development-environment.md | 0 documentation/{content => }/developers/docker.md | 0 .../{content => }/developers/known-issues.md | 0 .../{content => }/developers/rfid/README.md | 0 .../{content => }/developers/rfid/basics.md | 0 .../{content => }/developers/rfid/genericusb.md | 0 .../{content => }/developers/rfid/mfrc522_spi.md | 0 .../{content => }/developers/rfid/mock_reader.md | 0 .../{content => }/developers/rfid/mock_reader.png | Bin .../{content => }/developers/rfid/pn532_i2c.md | 0 .../{content => }/developers/rfid/rdm6300.md | 0 .../developers/rfid/template_reader.md | 0 documentation/{content => }/developers/status.md | 0 33 files changed, 7 insertions(+), 7 deletions(-) rename documentation/{content => }/builders/README.md (100%) rename documentation/{content => }/builders/audio.md (100%) rename documentation/{content => }/builders/autohotspot.md (100%) rename documentation/{content => }/builders/bluetooth-audio-buttons.md (100%) rename documentation/{content => }/builders/card-database.md (100%) rename documentation/{content => }/builders/concepts.md (100%) rename documentation/{content => }/builders/configuration.md (100%) rename documentation/{content => }/builders/installation.md (100%) rename documentation/{content => }/builders/mock_gpio.png (100%) rename documentation/{content => }/builders/rfid.md (100%) rename documentation/{content => }/builders/system.md (100%) rename documentation/{content => }/builders/troubleshooting.md (100%) rename documentation/{content => }/builders/update.md (100%) rename documentation/{content => }/calendars/2019-Phoniebox-Calendar.jpg (100%) rename documentation/{content => }/calendars/2020-Phoniebox-Calendar.jpg (100%) rename documentation/{content => }/calendars/2021-Phoniebox-Calendar.jpg (100%) rename documentation/{content => }/developers/README.md (93%) rename documentation/{content => }/developers/coreapps.md (100%) rename documentation/{content => }/developers/developer-issues.md (100%) rename documentation/{content => }/developers/development-environment.md (100%) rename documentation/{content => }/developers/docker.md (100%) rename documentation/{content => }/developers/known-issues.md (100%) rename documentation/{content => }/developers/rfid/README.md (100%) rename documentation/{content => }/developers/rfid/basics.md (100%) rename documentation/{content => }/developers/rfid/genericusb.md (100%) rename documentation/{content => }/developers/rfid/mfrc522_spi.md (100%) rename documentation/{content => }/developers/rfid/mock_reader.md (100%) rename documentation/{content => }/developers/rfid/mock_reader.png (100%) rename documentation/{content => }/developers/rfid/pn532_i2c.md (100%) rename documentation/{content => }/developers/rfid/rdm6300.md (100%) rename documentation/{content => }/developers/rfid/template_reader.md (100%) rename documentation/{content => }/developers/status.md (100%) diff --git a/documentation/README.md b/documentation/README.md index 087b57ad8..d15d6c599 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -11,13 +11,13 @@ project check out the [documentation of Version 2](https://github.com/MiczFlor/R ## Quickstart * For Builders: Building a Phoniebox - * [Installing Phoniebox future3](./content/builders/installation.md) - * [Builder Guides](./content/builders/README.md) - * [Update](./content/builders/update.md) + * [Installing Phoniebox future3](./builders/installation.md) + * [Builder Guides](./builders/README.md) + * [Update](./builders/update.md) * For Developers: Add features or fix bugs - * [Developer Reference](./content/developers/README.md) - * [Feature Status](./content/developers/status.md) - * [Known Issues](./content/developers/known-issues.md) + * [Developer Guides](./developers/README.md) + * [Feature Status](./developers/status.md) + * [Known Issues](./developers/known-issues.md) ## future3 diff --git a/documentation/content/builders/README.md b/documentation/builders/README.md similarity index 100% rename from documentation/content/builders/README.md rename to documentation/builders/README.md diff --git a/documentation/content/builders/audio.md b/documentation/builders/audio.md similarity index 100% rename from documentation/content/builders/audio.md rename to documentation/builders/audio.md diff --git a/documentation/content/builders/autohotspot.md b/documentation/builders/autohotspot.md similarity index 100% rename from documentation/content/builders/autohotspot.md rename to documentation/builders/autohotspot.md diff --git a/documentation/content/builders/bluetooth-audio-buttons.md b/documentation/builders/bluetooth-audio-buttons.md similarity index 100% rename from documentation/content/builders/bluetooth-audio-buttons.md rename to documentation/builders/bluetooth-audio-buttons.md diff --git a/documentation/content/builders/card-database.md b/documentation/builders/card-database.md similarity index 100% rename from documentation/content/builders/card-database.md rename to documentation/builders/card-database.md diff --git a/documentation/content/builders/concepts.md b/documentation/builders/concepts.md similarity index 100% rename from documentation/content/builders/concepts.md rename to documentation/builders/concepts.md diff --git a/documentation/content/builders/configuration.md b/documentation/builders/configuration.md similarity index 100% rename from documentation/content/builders/configuration.md rename to documentation/builders/configuration.md diff --git a/documentation/content/builders/installation.md b/documentation/builders/installation.md similarity index 100% rename from documentation/content/builders/installation.md rename to documentation/builders/installation.md diff --git a/documentation/content/builders/mock_gpio.png b/documentation/builders/mock_gpio.png similarity index 100% rename from documentation/content/builders/mock_gpio.png rename to documentation/builders/mock_gpio.png diff --git a/documentation/content/builders/rfid.md b/documentation/builders/rfid.md similarity index 100% rename from documentation/content/builders/rfid.md rename to documentation/builders/rfid.md diff --git a/documentation/content/builders/system.md b/documentation/builders/system.md similarity index 100% rename from documentation/content/builders/system.md rename to documentation/builders/system.md diff --git a/documentation/content/builders/troubleshooting.md b/documentation/builders/troubleshooting.md similarity index 100% rename from documentation/content/builders/troubleshooting.md rename to documentation/builders/troubleshooting.md diff --git a/documentation/content/builders/update.md b/documentation/builders/update.md similarity index 100% rename from documentation/content/builders/update.md rename to documentation/builders/update.md diff --git a/documentation/content/calendars/2019-Phoniebox-Calendar.jpg b/documentation/calendars/2019-Phoniebox-Calendar.jpg similarity index 100% rename from documentation/content/calendars/2019-Phoniebox-Calendar.jpg rename to documentation/calendars/2019-Phoniebox-Calendar.jpg diff --git a/documentation/content/calendars/2020-Phoniebox-Calendar.jpg b/documentation/calendars/2020-Phoniebox-Calendar.jpg similarity index 100% rename from documentation/content/calendars/2020-Phoniebox-Calendar.jpg rename to documentation/calendars/2020-Phoniebox-Calendar.jpg diff --git a/documentation/content/calendars/2021-Phoniebox-Calendar.jpg b/documentation/calendars/2021-Phoniebox-Calendar.jpg similarity index 100% rename from documentation/content/calendars/2021-Phoniebox-Calendar.jpg rename to documentation/calendars/2021-Phoniebox-Calendar.jpg diff --git a/documentation/content/developers/README.md b/documentation/developers/README.md similarity index 93% rename from documentation/content/developers/README.md rename to documentation/developers/README.md index a2cb94c42..9136c5f31 100644 --- a/documentation/content/developers/README.md +++ b/documentation/developers/README.md @@ -1,4 +1,4 @@ -# Developers +# Developer Guides ## Getting started diff --git a/documentation/content/developers/coreapps.md b/documentation/developers/coreapps.md similarity index 100% rename from documentation/content/developers/coreapps.md rename to documentation/developers/coreapps.md diff --git a/documentation/content/developers/developer-issues.md b/documentation/developers/developer-issues.md similarity index 100% rename from documentation/content/developers/developer-issues.md rename to documentation/developers/developer-issues.md diff --git a/documentation/content/developers/development-environment.md b/documentation/developers/development-environment.md similarity index 100% rename from documentation/content/developers/development-environment.md rename to documentation/developers/development-environment.md diff --git a/documentation/content/developers/docker.md b/documentation/developers/docker.md similarity index 100% rename from documentation/content/developers/docker.md rename to documentation/developers/docker.md diff --git a/documentation/content/developers/known-issues.md b/documentation/developers/known-issues.md similarity index 100% rename from documentation/content/developers/known-issues.md rename to documentation/developers/known-issues.md diff --git a/documentation/content/developers/rfid/README.md b/documentation/developers/rfid/README.md similarity index 100% rename from documentation/content/developers/rfid/README.md rename to documentation/developers/rfid/README.md diff --git a/documentation/content/developers/rfid/basics.md b/documentation/developers/rfid/basics.md similarity index 100% rename from documentation/content/developers/rfid/basics.md rename to documentation/developers/rfid/basics.md diff --git a/documentation/content/developers/rfid/genericusb.md b/documentation/developers/rfid/genericusb.md similarity index 100% rename from documentation/content/developers/rfid/genericusb.md rename to documentation/developers/rfid/genericusb.md diff --git a/documentation/content/developers/rfid/mfrc522_spi.md b/documentation/developers/rfid/mfrc522_spi.md similarity index 100% rename from documentation/content/developers/rfid/mfrc522_spi.md rename to documentation/developers/rfid/mfrc522_spi.md diff --git a/documentation/content/developers/rfid/mock_reader.md b/documentation/developers/rfid/mock_reader.md similarity index 100% rename from documentation/content/developers/rfid/mock_reader.md rename to documentation/developers/rfid/mock_reader.md diff --git a/documentation/content/developers/rfid/mock_reader.png b/documentation/developers/rfid/mock_reader.png similarity index 100% rename from documentation/content/developers/rfid/mock_reader.png rename to documentation/developers/rfid/mock_reader.png diff --git a/documentation/content/developers/rfid/pn532_i2c.md b/documentation/developers/rfid/pn532_i2c.md similarity index 100% rename from documentation/content/developers/rfid/pn532_i2c.md rename to documentation/developers/rfid/pn532_i2c.md diff --git a/documentation/content/developers/rfid/rdm6300.md b/documentation/developers/rfid/rdm6300.md similarity index 100% rename from documentation/content/developers/rfid/rdm6300.md rename to documentation/developers/rfid/rdm6300.md diff --git a/documentation/content/developers/rfid/template_reader.md b/documentation/developers/rfid/template_reader.md similarity index 100% rename from documentation/content/developers/rfid/template_reader.md rename to documentation/developers/rfid/template_reader.md diff --git a/documentation/content/developers/status.md b/documentation/developers/status.md similarity index 100% rename from documentation/content/developers/status.md rename to documentation/developers/status.md From 12f4f92e15c26b719f702d8af49342d92b31867b Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:36:44 +0100 Subject: [PATCH 025/121] Future3/documentation (#2127) * fixed typo * restored "rpc-commands" * fixed broken links * switched placed for leading documentation * added docu for changing swap size * unified links to source --- documentation/README.md | 2 +- documentation/builders/README.md | 2 +- documentation/builders/audio.md | 2 +- documentation/builders/card-database.md | 2 +- documentation/builders/concepts.md | 6 +- documentation/builders/configuration.md | 2 +- documentation/builders/rpc-commands.md | 112 ++++++++++++++++++ documentation/developers/coreapps.md | 13 +- documentation/developers/developer-issues.md | 12 ++ documentation/developers/rfid/basics.md | 2 +- documentation/developers/rfid/genericusb.md | 2 +- documentation/developers/rfid/mock_reader.md | 2 +- .../developers/rfid/template_reader.md | 42 ++++++- .../rfid/hardware/fake_reader_gui/README.md | 2 +- .../rfid/hardware/generic_usb/README.md | 2 +- .../rfid/hardware/pn532_i2c_py532/README.md | 2 +- .../rfid/hardware/rc522_spi/README.md | 2 +- .../rfid/hardware/rdm6300_serial/README.md | 2 +- .../hardware/template_new_reader/README.md | 41 +------ 19 files changed, 193 insertions(+), 59 deletions(-) create mode 100644 documentation/builders/rpc-commands.md diff --git a/documentation/README.md b/documentation/README.md index d15d6c599..71ef9010f 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -6,7 +6,7 @@ The exciting, new Version 3 of the RPi Jukebox RFID. A complete rewrite of the J > This documentation applies to the Version 3 which is developed in the branches `future3/main` and `future3/develop`. Currently the default Version is 2.x To find out more about the RPi Jukebox RFID -project check out the [documentation of Version 2](https://github.com/MiczFlor/RPi-Jukebox-RFID) or [www.phoniebox.de](https://www.phoniebox.de/). +project check out the [documentation of Version 2](https://github.com/MiczFlor/RPi-Jukebox-RFID) or [www.phoniebox.de](https://phoniebox.de/). ## Quickstart diff --git a/documentation/builders/README.md b/documentation/builders/README.md index 22128308f..3d70bca15 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -21,4 +21,4 @@ * [System](./system.md) * [Feature Status](../developers/status.md) * [Known Issues](../developers/known-issues.md) -* [Developer Reference](../developers) +* [Developer Reference](../developers/README.md) diff --git a/documentation/builders/audio.md b/documentation/builders/audio.md index d4b06b481..07d24e40f 100644 --- a/documentation/builders/audio.md +++ b/documentation/builders/audio.md @@ -99,7 +99,7 @@ Rerun the config tool to register the Bluetooth device with the Jukebox core app For other audio configuration options, please look at the `jukebox.yaml` for now. -Directly edit `jukebox.yaml` following the steps: [Best practice procedure](configuraton.md#best-practice-procedure). +Directly edit `jukebox.yaml` following the steps: [Best practice procedure](configuration.md#best-practice-procedure). ## Developer Information diff --git a/documentation/builders/card-database.md b/documentation/builders/card-database.md index 99c213571..67dbf5dae 100644 --- a/documentation/builders/card-database.md +++ b/documentation/builders/card-database.md @@ -4,7 +4,7 @@ In the card database, an RPC command is assigned to every card. This RPC command is called every time when the card is swiped (or placed) on the reader. Every RPC callable function can be called. See -[RPC Commands](builders/rpc_commands.md) for an introduction. +[RPC Commands](rpc-commands.md) for an introduction. The card database is stored in `shared\settings\cards.yaml`. Here are some examples for RPC command assignments to cards \'0001\' to \'0003\' diff --git a/documentation/builders/concepts.md b/documentation/builders/concepts.md index 70f285973..c08a33caa 100644 --- a/documentation/builders/concepts.md +++ b/documentation/builders/concepts.md @@ -16,12 +16,10 @@ The Remote Procedure Call (RPC) server allows remotely triggering actions (e.g., Why should you care? Because we use the same protocol when triggering actions from other inputs like a card swipe, a GPIO button press, etc. How that works is described in [RPC Commands](rpc-commands.md). -You will find a full list of RPC callable functions in [RPC Command Reference](rpc-command-reference.md) and aliases for convenience in [RPC Command Alias Reference](rpc-command-alias-reference.md). - -We also have a tool to send RPC commands to the running Jukebox application: [run_rpc_tool.py](../../../src/jukebox/run_rpc_tool.py). +We also have a tool to send RPC commands to the running Jukebox application: [run_rpc_tool.py](../developers/coreapps.md#run_rpc_toolpy) ## Publishing Message Queue The Publishing Message Queue is the complementary part to the RPC where the core application publishes its status and status updates. As a user, you need not worry about it. -If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming status messages: [run_publicity_sniffer.py](../../../src/jukebox/run_rpc_tool.py). +If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming status messages: [run_publicity_sniffer.py](../developers/coreapps.md#run_publicity_snifferpy). diff --git a/documentation/builders/configuration.md b/documentation/builders/configuration.md index 2aaa3184b..e0240ddbe 100644 --- a/documentation/builders/configuration.md +++ b/documentation/builders/configuration.md @@ -10,7 +10,7 @@ Don't fear (overly), they contain commentaries. For several aspects we have :ref:`developer/coreapps:Configuration Tools` and detailed guides: * [Audio Configuration](./audio.md#audio-configuration) -* [RFID Reader Configuration](./rfid/basics.md#reader-configuration) +* [RFID Reader Configuration](../developers/rfid/basics.md#reader-configuration) Even after running the tools, certain aspects can only be changed by modifying the configuration files directly. diff --git a/documentation/builders/rpc-commands.md b/documentation/builders/rpc-commands.md new file mode 100644 index 000000000..f578ccd99 --- /dev/null +++ b/documentation/builders/rpc-commands.md @@ -0,0 +1,112 @@ +# RPC Commands + + +We use the RPC commands when triggering actions from different inputs like a card swipe, +a GPIO button press, etc. Triggering an action is equal to sending an RPC function call. +In many places the command to send when an input is triggered is configurable in a YAML-file. + +## Basics + +Consequently, you need to know how to specify the RPC command in the YAML file. +Here is the essence of what you need to know: + +An RPC command consists of up to three parts + + #. the function to execute (e.g. play_folder, change_volume) + #. the positional arguments (optional) + #. the keyword arguments (optional) + +The function specification consists of two (e.g., ``host.shutdown``) or three terms (e.g., ``volume.ctrl.change_volume``). +In configuration files, this will look like this: + +.. code-block:: yaml + + package: host + plugin: shutdown + +Or like this for a three part function with the argument set to ``5``: + +.. code-block:: yaml + + package: volume + plugin: ctrl + method: change_volume + args: [5] + +The keyword ``method`` is optional. If needs to be used depends on the function you want to call. + +## Aliases + + +Not so complicated, right? It will get even easier. For common commands we have defined aliases. An alias simply maps +to a pre-defined RPC command, e.g. ``play_card`` maps to ``player.ctrl.play_card``. + +Instead of + +.. code-block:: yaml + + package: player + plugin: ctrl + method: play_card + args: [path/to/folder] + +you can simply specify instead : + +.. code-block:: yaml + + alias: play_card + args: [path/to/folder] + +Using in alias is optional. But if the keyword is present in the configuration it takes precedence over an explicit +specified RPC command. + +## Arguments + +Arguments can be specified in similar fashion to Python function arguments: as positional arguments and / or +keyword arguments. Let's check out play_card, which is defined as: + +.. py:function:: play_card(...) -> player.ctrl.play_card(folder: str, recursive: bool = False) + :noindex: + + :param folder: Folder path relative to music library path + :param recursive: Add folder recursively + +This means it takes two arguments: + + * folder of type string + * recursive of type bool + +In the following examples, we will always use the alias for smaller configuration text. All three examples +do exactly the same, but use different ways of specifying the command. + +.. code-block:: yaml + + alias: play_card + args: [path/to/folder, True] + +.. code-block:: yaml + + alias: play_card + args: [path/to/folder] + kwargs: + recursive: True + +.. code-block:: yaml + + alias: play_card + kwargs: + folder: path/to/folder + recursive: True + + +.. important:: *args* must be a **list** of arguments to be passed! Even if only a single argument is passed. + So, use *args: [value]*. We try catch mis-uses but that might not always work. + + +You will find some more examples the configuration of the [Card Database](card-database.md) + +## For developers + +To send RPC commands for testing and debugging purpose you can use the CLI Tool [`run_rpc_tool.py`](../developers/coreapps.md#run_rpc_toolpy) +Also here is a ready-to-use decoding functions which decodes an RPC command (with or without alias) +from a YAML entry:func:`jukebox.utils.decode_rpc_command`. diff --git a/documentation/developers/coreapps.md b/documentation/developers/coreapps.md index be15f133a..2fa8b84e9 100644 --- a/documentation/developers/coreapps.md +++ b/documentation/developers/coreapps.md @@ -11,6 +11,8 @@ $ ./run_app_name.py -h ### `run_jukebox.py` +[run_jukebox.py](../../src/jukebox/run_jukebox.py) + This is the main app and starts the Jukebox Core. Usually this runs as a service, which is started automatically after boot-up. At times, it may be necessary to restart the service. For example after a configuration change. Not all configuration changes can be applied on-the-fly. See [Jukebox Configuration](../builders/configuration.md#jukebox-configuration). @@ -24,6 +26,8 @@ See [Best practice procedure](../builders/configuration.md#best-practice-procedu ### `run_configure_audio.py` +[run_configure_audio.py](../../src/jukebox/run_configure_audio.py) + Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. Will also setup equalizer and mono down mixer in the pulseaudio config file. @@ -32,6 +36,8 @@ Run this once after installation. Can be re-run at any time to change the settin ### `run_register_rfid_reader.py` +[run_register_rfid_reader.py](../../src/jukebox/run_register_rfid_reader.py) + Setup tool to configure the RFID Readers. Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change the settings. For more information see [RFID Readers](./rfid/README.md). @@ -43,7 +49,9 @@ Run this once to register and configure the RFID readers with the Jukebox. Can b ### `run_rpc_tool.py` -Command Line Interface to the Jukebox RPC Server +[run_rpc_tool.py](../../src/jukebox/run_rpc_tool.py) + +Command Line Interface to the Jukebox RPC Server. A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging. @@ -51,6 +59,9 @@ The tool features auto-completion and command history. The list of available commands is fetched from the running Jukebox service. + ### `run_publicity_sniffer.py` + [run_publicity_sniffer.py](../../src/jukebox/run_publicity_sniffer.py) + A command line tool that monitors all messages being sent out from the Jukebox via the publishing interface. Received messages are printed in the console. Mainly used for debugging. diff --git a/documentation/developers/developer-issues.md b/documentation/developers/developer-issues.md index 4b1bd4794..1ed531738 100644 --- a/documentation/developers/developer-issues.md +++ b/documentation/developers/developer-issues.md @@ -49,6 +49,18 @@ Alternatively, use the provided script, which sets the variable for you $ ./run_rebuild.sh ``` +**Changing Swap Size** + +This will set the swapsize to 1024 MB (and will deactivate swapfactor). Change accordingly if you have a SD Card with small capacity. + +``` +sudo dphys-swapfile swapoff +sudo sed -i "s|.*CONF_SWAPSIZE=.*|CONF_SWAPSIZE=1024|g" /etc/dphys-swapfile +sudo sed -i "s|^\s*CONF_SWAPFACTOR=|#CONF_SWAPFACTOR=|g" /etc/dphys-swapfile +sudo dphys-swapfile setup +sudo dphys-swapfile swapon +``` + ### Process exited too early // kill -9 ``` {.bash emphasize-lines="8,9"} diff --git a/documentation/developers/rfid/basics.md b/documentation/developers/rfid/basics.md index ec8844263..7a5aa37d9 100644 --- a/documentation/developers/rfid/basics.md +++ b/documentation/developers/rfid/basics.md @@ -79,7 +79,7 @@ For place-capable RFID readers enable dual action mode: a start action (e.g. pla #### card_removal_action: Dictionary -Executes the given function on card removal. Only relevant if place_not_swipe is true. The action is identical for all cards read on that reader. The removal-action can be set to ignored on a card-by-card basis. More on card action configurations in [RPC Commands](../builders/rpc-commands.md). +Executes the given function on card removal. Only relevant if place_not_swipe is true. The action is identical for all cards read on that reader. The removal-action can be set to ignored on a card-by-card basis. More on card action configurations in [RPC Commands](../../builders/rpc-commands.md). > [!NOTE] > Developer's note: The reason for a unique removal action for all cards is that card triggering and card removal are happening in two separate threads. Removal needs to be in a time-out thread. Thus, we would need to transport information from one thread to another. This can be done of course but is not implemented (yet). Ignoring card removal is much easier and works for now. diff --git a/documentation/developers/rfid/genericusb.md b/documentation/developers/rfid/genericusb.md index a9a6565b5..23975cf14 100644 --- a/documentation/developers/rfid/genericusb.md +++ b/documentation/developers/rfid/genericusb.md @@ -4,7 +4,7 @@ This module covers all types of USB-based RFID input readers. If you plan to connect multiple USB-based RFID readers to the Jukebox, make -sure to connect all of them before running the registration tool [run_register_rfid_reader.py](../coreapps.md). +sure to connect all of them before running the registration tool [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_readerpy). > [!NOTE] > The user needs to be part of the group \'input\' for evdev to work. This should usually be the case. However, a user can be added with: diff --git a/documentation/developers/rfid/mock_reader.md b/documentation/developers/rfid/mock_reader.md index ece44f653..5daee0ab5 100644 --- a/documentation/developers/rfid/mock_reader.md +++ b/documentation/developers/rfid/mock_reader.md @@ -6,7 +6,7 @@ machine - probably in a Python virtual environment. **place-capable**: yes -If you [mock the GPIO pins](../builders/gpio.md), this GUI will show the GPIO devices. +If you [mock the GPIO pins](../../../src/jukebox/components/gpio/gpioz/README.rst#use-mock-pins), this GUI will show the GPIO devices. ![image](mock_reader.png) diff --git a/documentation/developers/rfid/template_reader.md b/documentation/developers/rfid/template_reader.md index 48275d619..0f4c3e939 100644 --- a/documentation/developers/rfid/template_reader.md +++ b/documentation/developers/rfid/template_reader.md @@ -1,2 +1,42 @@ -For documentation see [src/jukebox/components/rfid/hardware/template_new_reader/README.md](../../../../src/jukebox/components/rfid/hardware/template_new_reader/README.md). +# Template Reader + +*Template for creating and integrating a new RFID Reader* + +> [!NOTE] +> For developers only + +This template provides the skeleton API for a new Reader. If you follow +the conventions outlined below, your new reader will be picked up +automatically There is no extra need to register the reader module with +the Phoniebox. Just re-run `the reader config tool `. + +Follow the instructions in [template_new_reader.py] + +Also have a look at the other reader subpackages to see how stuff works +with an example + +## File structure + +Your new reader is a python subpackage with these three mandatory files + +``` bash +components/rfid/hardware/awesome_reader/ + +- awesome_reader.py <-- The actual reader module + +- description.py <-- A description module w/o dependencies. Do not change the filename! + +- README.rst <-- The Readme +``` + +The module documentation must go into a separate file, called README.ME. + +## Conventions + +- Single reader per directory / subpackage +- reader module directory name and reader module file name must be + identical +- Obviously awesome_reader will be replaced with something more + descriptive. The naming scheme for the subpackage is + - \\_\\_\ + - e.g. generic_usb/generic_usb.py + - e.g. pn532_spi/pn532_spi.py + - ... diff --git a/src/jukebox/components/rfid/hardware/fake_reader_gui/README.md b/src/jukebox/components/rfid/hardware/fake_reader_gui/README.md index 9ba73355a..6144ac1f5 100644 --- a/src/jukebox/components/rfid/hardware/fake_reader_gui/README.md +++ b/src/jukebox/components/rfid/hardware/fake_reader_gui/README.md @@ -1,2 +1,2 @@ -For documentation see [documentation/content/developers/rfid/mock_reader.md](../../../../../../documentation/content/developers/rfid/mock_reader.md). +For documentation see [documentation/developers/rfid/mock_reader.md](../../../../../../documentation/developers/rfid/mock_reader.md). diff --git a/src/jukebox/components/rfid/hardware/generic_usb/README.md b/src/jukebox/components/rfid/hardware/generic_usb/README.md index e82e9005b..cf61d5822 100644 --- a/src/jukebox/components/rfid/hardware/generic_usb/README.md +++ b/src/jukebox/components/rfid/hardware/generic_usb/README.md @@ -1,2 +1,2 @@ -For documentation see [documentation/content/developers/rfid/genericusb.md](../../../../../../documentation/content/developers/rfid/genericusb.md). +For documentation see [documentation/developers/rfid/genericusb.md](../../../../../../documentation/developers/rfid/genericusb.md). diff --git a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md index d92a17c37..6a342e1d6 100644 --- a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md +++ b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/README.md @@ -1,2 +1,2 @@ -For documentation see [documentation/content/developers/rfid/pn532_i2c.md](../../../../../../documentation/content/developers/rfid/pn532_i2c.md). +For documentation see [documentation/developers/rfid/pn532_i2c.md](../../../../../../documentation/developers/rfid/pn532_i2c.md). diff --git a/src/jukebox/components/rfid/hardware/rc522_spi/README.md b/src/jukebox/components/rfid/hardware/rc522_spi/README.md index a78a7a890..8fcbb38a8 100644 --- a/src/jukebox/components/rfid/hardware/rc522_spi/README.md +++ b/src/jukebox/components/rfid/hardware/rc522_spi/README.md @@ -1,2 +1,2 @@ -For documentation see [documentation/content/developers/rfid/mfrc522_spi.md](../../../../../../documentation/content/developers/rfid/mfrc522_spi.md). +For documentation see [documentation/developers/rfid/mfrc522_spi.md](../../../../../../documentation/developers/rfid/mfrc522_spi.md). diff --git a/src/jukebox/components/rfid/hardware/rdm6300_serial/README.md b/src/jukebox/components/rfid/hardware/rdm6300_serial/README.md index 895c24bbf..9f0f39fad 100644 --- a/src/jukebox/components/rfid/hardware/rdm6300_serial/README.md +++ b/src/jukebox/components/rfid/hardware/rdm6300_serial/README.md @@ -1,2 +1,2 @@ -For documentation see [documentation/content/developers/rfid/rdm6300.md](../../../../../../documentation/content/developers/rfid/rdm6300.md). +For documentation see [documentation/developers/rfid/rdm6300.md](../../../../../../documentation/developers/rfid/rdm6300.md). diff --git a/src/jukebox/components/rfid/hardware/template_new_reader/README.md b/src/jukebox/components/rfid/hardware/template_new_reader/README.md index 77f3008c2..2ff93198c 100644 --- a/src/jukebox/components/rfid/hardware/template_new_reader/README.md +++ b/src/jukebox/components/rfid/hardware/template_new_reader/README.md @@ -1,41 +1,2 @@ -# Template Reader -*Template for creating and integrating a new RFID Reader* - -> [!NOTE] -> For developers only - -This template provides the skeleton API for a new Reader. If you follow -the conventions outlined below, your new reader will be picked up -automatically There is no extra need to register the reader module with -the Phoniebox. Just re-run `the reader config tool `. - -Follow the instructions in [template_new_reader.py] - -Also have a look at the other reader subpackages to see how stuff works -with an example - -## File structure - -Your new reader is a python subpackage with these three mandatory files - -``` bash -components/rfid/hardware/awesome_reader/ - +- awesome_reader.py <-- The actual reader module - +- description.py <-- A description module w/o dependencies. Do not change the filename! - +- README.rst <-- The Readme -``` - -The module documentation must go into a separate file, called README.ME. - -## Conventions - -- Single reader per directory / subpackage -- reader module directory name and reader module file name must be - identical -- Obviously awesome_reader will be replaced with something more - descriptive. The naming scheme for the subpackage is - - \\_\\_\ - - e.g. generic_usb/generic_usb.py - - e.g. pn532_spi/pn532_spi.py - - ... +For documentation see [documentation/developers/rfid/template_reader.md](../../../../../../documentation/developers/rfid/template_reader.md). From c0d5a20f5bb521d09a0b0d910fc2b83dcca79e61 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:48:28 +0100 Subject: [PATCH 026/121] future3 V3.3 - reference webapp build (#2126) --- installation/routines/setup_jukebox_webapp.sh | 2 +- src/jukebox/jukebox/version.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index a0e195622..54be3119c 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Constants -GD_ID_COMPILED_WEBAPP="1EE_1MdneGtKL5V7GyYZC0nb6ODQWTsPb" # https://drive.google.com/file/d/1EE_1MdneGtKL5V7GyYZC0nb6ODQWTsPb/view?usp=sharing +GD_ID_COMPILED_WEBAPP="1um-smyfsVPzVZn18hhwuFt97XR3PjAbB" # https://drive.google.com/file/d/1um-smyfsVPzVZn18hhwuFt97XR3PjAbB/view?usp=sharing # For ARMv7+ NODE_MAJOR=20 diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index ff86e01fc..de575e0b4 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,7 +1,7 @@ VERSION_MAJOR = 3 -VERSION_MINOR = 2 -VERSION_PATCH = 1 +VERSION_MINOR = 3 +VERSION_PATCH = 0 VERSION_EXTRA = "" __version__ = '%i.%i.%i' % (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) From 234cc3c883951b822343c517a392c60e499cbc90 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sat, 2 Dec 2023 17:32:53 +0100 Subject: [PATCH 027/121] Add installation checks + CI Workflow (#2119) * add ci workflow * add check for user home * changes for docker tests * added verify functions * exit on failed update * add check for samba setup * add check for mpd setup * add check for jukebox core setup * add check for git setup * add check for kiosk mode setup * add check for optimize boot setup * add check for webapp setup * add check for autohotspot setup * make rfid reader setup an optional step * Add Option for No Rfid Reader * moved setup checks to individual scripts * fix missing iw package * refactored get_reader_descriptions into function. be more consistent by using same var for display and index access * moved function to specific routine script * make sure verify parameters are all set. fix functionnames * added fix for not available interface in CI runs * check supported os type first * moved exit_on_error function top level * moved logging setup to function and added usage * moved load sources to function * change function name and var usage * updated log output for better logfile readability * harmonize log output * refactored disable onboard audio. only execute if flag is true. inlined function * add restart nginx.service again. use HOME_PATH * fix and check adding samba user * fix logging mpd setup routine * fix Disable boot logs error on existing file * added timer to log frame * add core package installation from file used also for ci optimization * Dont let remove packages fail install. ignore for ci * Update test scripts use robust path determination move noninteractive to dockerfile * fix clear in mpd option * fix bug in rfid configure * fix error on uninitialized rfid_reader settings * add checks for static ip config * Added test for webapp download in prod. Update test descriptions. * renamend workflow files to not interfere with develop branch * Update install links --- .dockerignore | 1 - .../test_docker_debian_codename_sub_v3.yml | 183 ++++++++++++ .github/workflows/test_docker_debian_v3.yml | 31 +++ README.md | 6 +- ci/ci-debian.Dockerfile | 100 +++++++ ci/installation/run_install_common.sh | 43 +++ ci/installation/run_install_faststartup.sh | 39 +++ ci/installation/run_install_user_not_pi.sh | 25 ++ .../run_install_webapp_download.sh | 41 +++ ci/installation/run_install_webapp_local.sh | 42 +++ installation/README.md | 8 +- installation/includes/00_constants.sh | 4 + installation/includes/01_default_config.sh | 2 + installation/includes/02_helpers.sh | 261 ++++++++++++++++-- installation/includes/04_cleanup.sh | 8 +- installation/includes/05_finish.sh | 25 +- installation/install-jukebox.sh | 141 +++++++--- installation/routines/customize_options.sh | 74 ++++- installation/routines/install.sh | 15 +- installation/routines/optimize_boot_time.sh | 135 ++++++--- installation/routines/set_raspi_config.sh | 26 +- installation/routines/set_ssh_qos.sh | 12 +- installation/routines/setup_autohotspot.sh | 76 +++-- installation/routines/setup_git.sh | 32 ++- installation/routines/setup_jukebox_core.sh | 80 ++++-- installation/routines/setup_jukebox_webapp.sh | 59 ++-- installation/routines/setup_kiosk_mode.sh | 59 +++- installation/routines/setup_mpd.sh | 77 +++--- installation/routines/setup_rfid_reader.sh | 12 +- installation/routines/setup_samba.sh | 48 +++- installation/routines/update_raspi_os.sh | 15 +- packages-core.txt | 17 ++ requirements.txt | 3 +- .../components/rfid/configure/__init__.py | 78 ++++-- .../components/rfid/reader/__init__.py | 24 +- 35 files changed, 1439 insertions(+), 363 deletions(-) create mode 100644 .github/workflows/test_docker_debian_codename_sub_v3.yml create mode 100644 .github/workflows/test_docker_debian_v3.yml create mode 100644 ci/ci-debian.Dockerfile create mode 100644 ci/installation/run_install_common.sh create mode 100644 ci/installation/run_install_faststartup.sh create mode 100644 ci/installation/run_install_user_not_pi.sh create mode 100644 ci/installation/run_install_webapp_download.sh create mode 100644 ci/installation/run_install_webapp_local.sh create mode 100644 packages-core.txt diff --git a/.dockerignore b/.dockerignore index 55fe62a70..a587de477 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,6 @@ # Application docker docs -installation shared # webapp diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml new file mode 100644 index 000000000..54abd142a --- /dev/null +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -0,0 +1,183 @@ +name: Subworkflow Test Install Scripts Debian V3 + +on: + workflow_call: + inputs: + debian_codename: + required: true + type: string + docker_image_name: + required: false + type: string + default: rpi-jukebox-rfid-v3 + platform: + required: false + type: string + default: linux/arm/v7 + cache_scope: + required: false + type: string + default: ${{ github.ref }}-test-debian-v3 + local_registry_port: + required: false + type: number + default: 5000 + runs_on: + required: false + type: string + default: ubuntu-latest + +# let only one instance run the test so cache is not corrupted. +# cancel already running instances as only the last run will be relevant +concurrency: + group: ${{ inputs.cache_scope }}-${{ inputs.debian_codename }} + cancel-in-progress: true + +jobs: + + # Build container for test execution + build: + runs-on: ${{ inputs.runs_on }} + + outputs: + cache_key: ${{ steps.vars.outputs.cache_key }} + image_file_name: ${{ steps.vars.outputs.image_file_name }} + image_tag_name: ${{ steps.vars.outputs.image_tag_name }} + + # create local docker registry to use locally build images + services: + registry: + image: registry:2 + ports: + - ${{ inputs.local_registry_port }}:5000 + + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + with: + # network=host driver-opt needed to push to local registry + driver-opts: network=host + + - name: Set Output pre-vars + id: pre-vars + env: + DEBIAN_CODENAME: ${{ inputs.debian_codename }} + DOCKER_IMAGE_NAME: ${{ inputs.docker_image_name }} + CACHE_SCOPE: ${{ inputs.cache_scope }} + run: | + echo "image_tag_name=${{ env.DOCKER_IMAGE_NAME }}:${{ env.DEBIAN_CODENAME }}-test" >> $GITHUB_OUTPUT + echo "image_file_name=${{ env.DOCKER_IMAGE_NAME }}-${{ env.DEBIAN_CODENAME }}.tar" >> $GITHUB_OUTPUT + echo "cache_scope=${{ env.CACHE_SCOPE }}-${{ env.DEBIAN_CODENAME }}" >> $GITHUB_OUTPUT + + - name: Set Output vars + id: vars + env: + LOCAL_REGISTRY_PORT: ${{ inputs.local_registry_port }} + run: | + echo "image_tag_name=${{ steps.pre-vars.outputs.image_tag_name }}" >> $GITHUB_OUTPUT + echo "image_tag_name_local_base=localhost:${{ env.LOCAL_REGISTRY_PORT }}/${{ steps.pre-vars.outputs.image_tag_name }}-base" >> $GITHUB_OUTPUT + echo "image_file_name=${{ steps.pre-vars.outputs.image_file_name }}" >> $GITHUB_OUTPUT + echo "image_file_path=./${{ steps.pre-vars.outputs.image_file_name }}" >> $GITHUB_OUTPUT + echo "cache_scope=${{ steps.pre-vars.outputs.cache_scope }}" >> $GITHUB_OUTPUT + echo "cache_key=${{ steps.pre-vars.outputs.cache_scope }}-${{ github.sha }}#${{ github.run_attempt }}" >> $GITHUB_OUTPUT + + # Build base image for debian version name. Layers will be cached and image pushes to local registry + - name: Build Image - Base + uses: docker/build-push-action@v5 + with: + context: . + load: false + push: true + file: ./ci/ci-debian.Dockerfile + target: test-code + platforms: ${{ inputs.platform }} + tags: ${{ steps.vars.outputs.image_tag_name_local_base }} + cache-from: type=gha,scope=${{ steps.vars.outputs.cache_scope }} + cache-to: type=gha,mode=max,scope=${{ steps.vars.outputs.cache_scope }} + build-args: | + DEBIAN_CODENAME=${{ inputs.debian_codename }} + GIT_BRANCH=${{ github.head_ref || github.ref_name }} + GIT_USER=${{ github.event.pull_request.head.user.login || github.repository_owner }} + + # Build new image with updates packages based on base image. Layers will NOT be chached. Result is written to file. + - name: Build Image - Update + uses: docker/build-push-action@v5 + with: + context: . + load: false + push: false + file: ./ci/ci-debian.Dockerfile + target: test-update + platforms: ${{ inputs.platform }} + tags: ${{ steps.vars.outputs.image_tag_name }} + cache-from: type=gha,scope=${{ steps.vars.outputs.cache_scope }} + # DON'T use 'cache-to' here as the layer is then cached and this build would be useless + outputs: type=docker,dest=${{ steps.vars.outputs.image_file_path }} + build-args: | + BASE_TEST_IMAGE=${{ steps.vars.outputs.image_tag_name_local_base }} + + - name: Artifact Upload Docker Image + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.vars.outputs.image_file_name }} + path: ${{ steps.vars.outputs.image_file_path }} + retention-days: 1 + + + # Run tests with build image + test: + needs: [build] + runs-on: ${{ inputs.runs_on }} + + strategy: + fail-fast: false + matrix: + username: ['pi'] + test_script: ['run_install_common.sh', 'run_install_faststartup.sh', 'run_install_webapp_local.sh', 'run_install_webapp_download.sh'] + include: + - username: hans + test_script: run_install_user_not_pi.sh + + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + + - name: Artifact Download Docker Image + uses: actions/download-artifact@v3 + with: + name: ${{ needs.build.outputs.image_file_name }} + + - name: Load Docker Image + run: | + docker load --input ${{ needs.build.outputs.image_file_name }} + + # Run test + - name: Run Test ${{ inputs.debian_codename }}-${{ matrix.username }}-${{ matrix.test_script }} + uses: tj-actions/docker-run@v2 + with: + image: ${{ needs.build.outputs.image_tag_name }} + options: --platform ${{ inputs.platform }} --user ${{ matrix.username }} --init + name: ${{ matrix.test_script }} + args: | + ./${{ matrix.test_script }} + + # cleanup after test execution + cleanup: + # run only if tests didn't fail: keep the artifact to make job reruns possible + if: ${{ !failure() }} + needs: [build, test] + runs-on: ${{ inputs.runs_on }} + + steps: + - name: Artifact Delete Docker Image + uses: geekyeggo/delete-artifact@v2 + with: + name: ${{ needs.build.outputs.image_file_name }} diff --git a/.github/workflows/test_docker_debian_v3.yml b/.github/workflows/test_docker_debian_v3.yml new file mode 100644 index 000000000..6cf1648aa --- /dev/null +++ b/.github/workflows/test_docker_debian_v3.yml @@ -0,0 +1,31 @@ +name: Test Install Scripts Debian v3 + +on: + schedule: + # run at 17:00 every sunday + - cron: '0 17 * * 0' + push: + pull_request: + # The branches below must be a subset of the branches above + branches: [ future3/develop ] + +# let only one instance run the test so cache is not corrupted. +# cancel already running instances as only the last run will be relevant +concurrency: + group: ${{ github.ref }}-test-debian-v3 + cancel-in-progress: true + +jobs: + + # Build container and run tests. Duplication of job intended for better visualization. + run_bookworm: + name: 'bookworm' + uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml + with: + debian_codename: 'bookworm' + + run_bullseye: + name: 'bullseye' + uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml + with: + debian_codename: 'bullseye' diff --git a/README.md b/README.md index 8b494698d..3731c7e06 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,4 @@ The documentation can be found [here](./documentation/README.md) ## Installation? -Run the following one-liner in a shell and follow the instructions - -~~~bash -cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) -~~~ +[Install Phoniebox software](documentation/builders/installation.md#install-phoniebox-software) diff --git a/ci/ci-debian.Dockerfile b/ci/ci-debian.Dockerfile new file mode 100644 index 000000000..1a227755a --- /dev/null +++ b/ci/ci-debian.Dockerfile @@ -0,0 +1,100 @@ +# Base Target to build and install all needed base configuration and packages. Specifie the needed platform with the docker '--platform XXX' option +ARG DEBIAN_CODENAME=bookworm +ARG BASE_TEST_IMAGE=test-code +FROM debian:${DEBIAN_CODENAME}-slim as base +ARG DEBIAN_CODENAME + +ENV TERM=xterm DEBIAN_FRONTEND=noninteractive +ENV CI_RUNNING=true + +# create pi configs to test installation +RUN touch /boot/config.txt +RUN echo "logo.nologo" > /boot/cmdline.txt + +RUN echo "--- install packages (1) ---" \ + && apt-get update \ + && apt-get -y install \ + apt-utils \ + curl \ + gnupg \ + && echo "--- add sources ---" \ + && curl -fsSL http://raspbian.raspberrypi.org/raspbian.public.key | gpg --dearmor > /usr/share/keyrings/raspberrypi-raspbian-keyring.gpg \ + && curl -fsSL http://archive.raspberrypi.org/debian/raspberrypi.gpg.key | gpg --dearmor > /usr/share/keyrings/raspberrypi-archive-debian-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/raspberrypi-raspbian-keyring.gpg] http://raspbian.raspberrypi.org/raspbian/ ${DEBIAN_CODENAME} main contrib non-free rpi" > /etc/apt/sources.list.d/raspi.list \ + && echo "deb [signed-by=/usr/share/keyrings/raspberrypi-archive-debian-keyring.gpg] http://archive.raspberrypi.org/debian/ ${DEBIAN_CODENAME} main" >> /etc/apt/sources.list.d/raspi.list \ + && echo "--- install packages (2) ---" \ + && apt-get update \ + && apt-get -y upgrade \ + && apt-get -y install \ + build-essential \ + iproute2 \ + openssh-client \ + sudo \ + systemd \ + wireless-tools \ + wget \ + wpasupplicant \ + && rm -rf /var/lib/apt/lists/* + +# Set NonInteractive for sudo usage in container. 'sudo' package needed +RUN echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections +# ------ + +# Base Target for setting up the default user. user can be selected with the docker '--user YYY' option +FROM base as user +ARG USER_NAME=pi +ARG USER_GROUP=$USER_NAME +ARG USER_ID=1000 + +ENV TEST_USER_GROUP=test +RUN groupadd --gid 1002 $TEST_USER_GROUP + +RUN groupadd --gid 1000 $USER_GROUP \ + && useradd -u $USER_ID -g $USER_GROUP -G sudo,$TEST_USER_GROUP -d /home/$USER_NAME -m -s /bin/bash -p '$1$iV7TOwOe$6ojkJQXyEA9bHd/SqNLNj0' $USER_NAME \ + && echo "$USER_NAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER_NAME + +ENV XDG_RUNTIME_DIR=/run/user/$USER_ID DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$USER_ID/bus +# ------ + + +# Target for setting up an alternativ user 'hans:wurst'. user can be selected with the docker '--user YYY' option +FROM user as test-user + +RUN export USER_ALT=hans \ + && export USER_ALT_GROUP=wurst \ + && groupadd --gid 1001 $USER_ALT_GROUP \ + && useradd -u 1001 -g $USER_ALT_GROUP -G sudo,$TEST_USER_GROUP -d /home/$USER_ALT -m -s /bin/bash -p '$1$iV7TOwOe$6ojkJQXyEA9bHd/SqNLNj0' $USER_ALT \ + && echo "$USER_ALT ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER_ALT +# ------ + + +# Target for adding envs and scripts from the repo to test installation +FROM test-user as test-code +ARG GIT_BRANCH +ARG GIT_USER + +ENV GIT_BRANCH=$GIT_BRANCH GIT_USER=$GIT_USER + +COPY --chown=root:$TEST_USER_GROUP --chmod=770 packages-core.txt ./ + +RUN echo "--- install internal packages ---" \ + && apt-get update \ + && sed 's/#.*//g' packages-core.txt | xargs apt-get -y install \ + && rm -rf /var/lib/apt/lists/* + +ENV INSTALL_SCRIPT_PATH=/code + +WORKDIR ${INSTALL_SCRIPT_PATH} +COPY --chown=root:$TEST_USER_GROUP --chmod=770 installation/install-jukebox.sh ./ + +WORKDIR ${INSTALL_SCRIPT_PATH}/tests +COPY --chown=root:$TEST_USER_GROUP --chmod=770 ci/installation/*.sh ./ +# ------ + + +# Target for applying latest updates (should not be cached!) +FROM $BASE_TEST_IMAGE as test-update +RUN apt-get update \ + && apt-get -y upgrade \ + && rm -rf /var/lib/apt/lists/* +# ------ diff --git a/ci/installation/run_install_common.sh b/ci/installation/run_install_common.sh new file mode 100644 index 000000000..102c71aa4 --- /dev/null +++ b/ci/installation/run_install_common.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for a common installation path. Including autohotspot + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export ENABLE_WEBAPP_PROD_DOWNLOAD=true +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# y - setup autohotspot +# n - use custom password +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# y - setup samba +# y - setup webapp +# n - setup kiosk mode +# - - install node (forced WebApp Download) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +y +n +n +n +n +y +y +n +n +' diff --git a/ci/installation/run_install_faststartup.sh b/ci/installation/run_install_faststartup.sh new file mode 100644 index 000000000..46cda25ec --- /dev/null +++ b/ci/installation/run_install_faststartup.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for disabling features (suggestions for faststartup). Skips installing all additionals. + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +# Run installation (in interactive mode) +# y - start setup +# y - use static ip +# y - deactivate ipv6 +# n - setup autohotspot +# y - deactivate bluetooth +# y - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# n - setup webapp +# - - setup kiosk mode (only with webapp = y) +# - - install node (only with webapp = y) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +y +y +n +y +y +n +n +n +n +' diff --git a/ci/installation/run_install_user_not_pi.sh b/ci/installation/run_install_user_not_pi.sh new file mode 100644 index 000000000..76a8cd576 --- /dev/null +++ b/ci/installation/run_install_user_not_pi.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: Test installation with script using a simple configuration + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +# Run installation (in interactive mode) +# - - Installation must abort early + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" +INSTALLATION_EXITCODE=$? + +# only count abortion due to "not user pi" as success +if [ "${INSTALLATION_EXITCODE}" -eq 2 ]; then + INSTALLATION_EXITCODE=0 +else + INSTALLATION_EXITCODE=1 +fi +exit "${INSTALLATION_EXITCODE}" diff --git a/ci/installation/run_install_webapp_download.sh b/ci/installation/run_install_webapp_download.sh new file mode 100644 index 000000000..69496e8e4 --- /dev/null +++ b/ci/installation/run_install_webapp_download.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for the WebApp (download) and dependent features path. + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export ENABLE_WEBAPP_PROD_DOWNLOAD=true +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# n - setup autohotspot +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# y - setup webapp +# y - setup kiosk mode +# - - install node (forced webapp download) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +n +n +n +n +n +y +y +n +' diff --git a/ci/installation/run_install_webapp_local.sh b/ci/installation/run_install_webapp_local.sh new file mode 100644 index 000000000..d4f122fd5 --- /dev/null +++ b/ci/installation/run_install_webapp_local.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for the WebApp (build locally) and dependent features path. + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export ENABLE_WEBAPP_PROD_DOWNLOAD=false +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# n - setup autohotspot +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# y - setup webapp +# y - setup kiosk mode +# y - install node +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +n +n +n +n +n +y +y +y +n +' diff --git a/installation/README.md b/installation/README.md index aff262bd8..b2a496348 100644 --- a/installation/README.md +++ b/installation/README.md @@ -11,10 +11,6 @@ No output to both console and logfile: "$ command > /dev/null" [Learn more about bash script outputs](https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console) -## Quick Installation +## Installation -Note: Replace the branch in this command to be the one you like to install depending on your needs. Release branch is preset. - -```bash -cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh) -``` +[Install Phoniebox software](../documentation/builders/installation.md#install-phoniebox-software) diff --git a/installation/includes/00_constants.sh b/installation/includes/00_constants.sh index e19dd1327..380e1de2e 100644 --- a/installation/includes/00_constants.sh +++ b/installation/includes/00_constants.sh @@ -1,7 +1,11 @@ RPI_BOOT_CONFIG_FILE="/boot/config.txt" +RPI_BOOT_CMDLINE_FILE="/boot/cmdline.txt" SHARED_PATH="${INSTALLATION_PATH}/shared" SETTINGS_PATH="${SHARED_PATH}/settings" SYSTEMD_USR_PATH="/usr/lib/systemd/user" +VIRTUAL_ENV="${INSTALLATION_PATH}/.venv" +# Do not change this directory! It must match MPDs expectation where to find the user configuration +MPD_CONF_PATH="${HOME}/.config/mpd/mpd.conf" # The default upstream user, release branch, and develop branch # These are used to prepare the repo for developers diff --git a/installation/includes/01_default_config.sh b/installation/includes/01_default_config.sh index eb6e45a91..b2f37a1de 100644 --- a/installation/includes/01_default_config.sh +++ b/installation/includes/01_default_config.sh @@ -11,7 +11,9 @@ DISABLE_SSH_QOS=true DISABLE_BOOT_SCREEN=true DISABLE_BOOT_LOGS_PRINT=true SETUP_MPD=true +ENABLE_MPD_OVERWRITE_INSTALL=true UPDATE_RASPI_OS=${UPDATE_RASPI_OS:-"true"} +ENABLE_RFID_READER=true ENABLE_SAMBA=true ENABLE_WEBAPP=true ENABLE_KIOSK_MODE=false diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 9ad8e71b8..4a39ae07e 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Helpers +### Helpers # $1->start, $2->end calc_runtime_and_print() { @@ -9,7 +9,7 @@ calc_runtime_and_print() { ((m=(${runtime}%3600)/60)) ((s=${runtime}%60)) - echo "Done in ${h}h ${m}m ${s}s." + echo "Done in ${h}h ${m}m ${s}s" } run_with_timer() { @@ -18,7 +18,20 @@ run_with_timer() { $1; # Executes the function passed as an argument calc_runtime_and_print time_start $(date +%s) | tee /dev/fd/3 - echo "--------------------------------------" +} + +run_with_log_frame() { + local time_start=$(date +%s); + local description="$2" + echo -e "\n\n" + echo "#########################################################" + echo "${description}" | tee /dev/fd/3 + + $1; # Executes the function passed as an argument + + local done_in=$(calc_runtime_and_print time_start $(date +%s)) + echo -e "\n${done_in} - ${description}" + echo "#########################################################" } _download_file_from_google_drive() { @@ -28,31 +41,231 @@ _download_file_from_google_drive() { echo "Downloaded from Google Drive ID ${GD_SHARING_ID} into ${TAR_FILENAME}" } -get_onboard_audio() { - if grep -q -E "^dtparam=([^,]*,)*audio=(on|true|yes|1).*" ${RPI_BOOT_CONFIG_FILE} - then - echo 1 - else - echo 0 - fi + +### Verify helpers + +print_verify_installation() { + echo "" + echo " -------------------------------------------------------" + echo " Check installation" + echo "" +} + +# Check if the file(s) exists +verify_files_exists() { + local files="$@" + echo " Verify '${files}' exists" + + if [[ -z "${files}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for file in $files + do + test ! -f ${file} && exit_on_error "ERROR: '${file}' does not exists or is not a file!" + done + echo " CHECK" +} + +# Check if the dir(s) exists +verify_dirs_exists() { + local dirs="$@" + echo " Verify '${dirs}' exists" + + if [[ -z "${dirs}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for dir in $dirs + do + test ! -d ${dir} && exit_on_error "ERROR: '${dir}' does not exists or is not a dir!" + done + echo " CHECK" +} + +# Check if the file(s) has/have the expected owner and modifications +verify_files_chmod_chown() { + local mod_expected=$1 + local user_expected=$2 + local group_expected=$3 + local files="${@:4}" + echo " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${files}'" + + if [[ -z "${mod_expected}" || -z "${user_expected}" || -z "${group_expected}" || -z "${files}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for file in $files + do + test ! -f ${file} && exit_on_error "ERROR: '${file}' does not exists or is not a file!" + + mod_actual=$(stat --format '%a' "${file}") + user_actual=$(stat -c '%U' "${file}") + group_actual=$(stat -c '%G' "${file}") + test ! "${mod_expected}" -eq "${mod_actual}" && exit_on_error "ERROR: '${file}' actual mod '${mod_actual}' differs from expected '${mod_expected}'!" + test ! "${user_expected}" == "${user_actual}" && exit_on_error "ERROR: '${file}' actual owner '${user_actual}' differs from expected '${user_expected}'!" + test ! "${group_expected}" == "${group_actual}" && exit_on_error "ERROR: '${file}' actual group '${group_actual}' differs from expected '${group_expected}'!" + done + echo " CHECK" +} + +# Check if the dir(s) has/have the expected owner and modifications +verify_dirs_chmod_chown() { + local mod_expected=$1 + local user_expected=$2 + local group_expected=$3 + local dirs="${@:4}" + echo " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${dirs}'" + + if [[ -z "${mod_expected}" || -z "${user_expected}" || -z "${group_expected}" || -z "${dirs}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for dir in $dirs + do + test ! -d ${dir} && exit_on_error "ERROR: '${dir}' does not exists or is not a dir!" + + mod_actual=$(stat --format '%a' "${dir}") + user_actual=$(stat -c '%U' "${dir}") + group_actual=$(stat -c '%G' "${dir}") + test ! "${mod_expected}" -eq "${mod_actual}" && exit_on_error "ERROR: '${dir}' actual mod '${mod_actual}' differs from expected '${mod_expected}'!" + test ! "${user_expected}" == "${user_actual}" && exit_on_error "ERROR: '${dir}' actual owner '${user_actual}' differs from expected '${user_expected}'!" + test ! "${group_expected}" == "${group_actual}" && exit_on_error "ERROR: '${dir}' actual group '${group_actual}' differs from expected '${group_expected}'!" + done + echo " CHECK" +} + +verify_file_contains_string() { + local string="$1" + local file="$2" + echo " Verify '${string}' found in '${file}'" + + if [[ -z "${string}" || -z "${file}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + if [[ ! $(grep -iw "${string}" "${file}") ]]; then + exit_on_error "ERROR: '${string}' not found in '${file}'" + fi + echo " CHECK" +} + +verify_file_contains_string_once() { + local string="$1" + local file="$2" + echo " Verify '${string}' found in '${file}'" + + if [[ -z "${string}" || -z "${file}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local file_contains_string_count=$(grep -oiw "${string}" "${file}" | wc -l) + if [ "$file_contains_string_count" -lt 1 ]; then + exit_on_error "ERROR: '${string}' not found in '${file}'" + elif [ "$file_contains_string_count" -gt 1 ]; then + exit_on_error "ERROR: '${string}' found more than once in '${file}'" + fi + echo " CHECK" +} + +verify_service_state() { + local service="$1" + local desired_state="$2" + local option="${3:+$3 }" # optional, dont't quote in next call! + echo " Verify service '${option}${service}' is '${desired_state}'" + + if [[ -z "${service}" || -z "${desired_state}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local actual_state=$(systemctl is-active ${option}${service}) + if [[ ! "${actual_state}" == "${desired_state}" ]]; then + exit_on_error "ERROR: service '${option}${service}' is not '${desired_state}' (state: '${actual_state}')." + fi + echo " CHECK" +} + +verify_service_enablement() { + local service="$1" + local desired_enablement="$2" + local option="${3:+$3 }" # optional, dont't quote in next call! + echo " Verify service ${option}${service} is ${desired_enablement}" + + if [[ -z "${service}" || -z "${desired_enablement}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local actual_enablement=$(systemctl is-enabled ${option}${service}) + if [[ ! "${actual_enablement}" == "${desired_enablement}" ]]; then + exit_on_error "ERROR: service ${option}${service} is not ${desired_enablement} (state: ${actual_enablement})." + fi + echo " CHECK" +} + +verify_optional_service_enablement() { + local service="$1" + local desired_enablement="$2" + local option="${3:+$3 }" # optional, dont't quote in next call! + echo " Verify service ${option}${service} is ${desired_enablement}" + + if [[ -z "${service}" || -z "${desired_enablement}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local actual_enablement=$(systemctl is-enabled ${option}${service}) 2>/dev/null + if [[ -z "${actual_enablement}" ]]; then + echo " INFO: optional service ${option}${service} is not installed." + elif [[ "${actual_enablement}" == "static" ]]; then + echo " INFO: optional service ${option}${service} is set static." + elif [[ ! "${actual_enablement}" == "${desired_enablement}" ]]; then + exit_on_error "ERROR: service ${option}${service} is not ${desired_enablement} (state: ${actual_enablement})." + fi + echo " CHECK" +} + +# Reads a textfile and returns all lines as args. +# Does filter out comments, egg-prefixes and version suffixes +# Arguments: +# 1 : textfile to read +get_args_from_file() { + local package_file="$1" + sed 's/.*#egg=//g' ${package_file} | sed -E 's/(#|=|>|<).*//g' | xargs echo } -check_os_type() { - # Check if current distro is a 32 bit version - # Support for 64 bit Distros has not been checked (or precisely: is known not to work) - # All RaspianOS versions report as machine "armv6l" or "armv7l", if 32 bit (even the ARMv8 cores!) +# Check if all passed packages are installed. Fail on first missing. +verify_apt_packages() { + local packages="$@" + echo " Verify packages are installed: '${packages}'" + + if [[ -z "${packages}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi - local os_type - os_type=$(uname -m) + local apt_list_installed=$(apt -qq list --installed 2>/dev/null) + for package in ${packages} + do + if [[ ! $(echo "${apt_list_installed}" | grep -i "^${package}/.*installed") ]]; then + exit_on_error "ERROR: ${package} is not installed" + fi + done + echo " CHECK" +} - echo -e "\nChecking OS type '$os_type'" | tee /dev/fd/3 +# Check if all passed modules are installed. Fail on first missing. +verify_pip_modules() { + local modules="$@" + echo " Verify modules are installed: '${modules}'" - if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then - echo -e " ... OK!\n" | tee /dev/fd/3 - else - echo "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" | tee /dev/fd/3 - echo "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" | tee /dev/fd/3 - exit 1 - fi + if [[ -z "${modules}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + local pip_list_installed=$(pip list 2>/dev/null) + for module in ${modules} + do + if [[ ! $(echo "${pip_list_installed}" | grep -i "^${module} ") ]]; then + exit_on_error "ERROR: ${module} is not installed" + fi + done + echo " CHECK" } diff --git a/installation/includes/04_cleanup.sh b/installation/includes/04_cleanup.sh index fd714132f..d6e39266c 100644 --- a/installation/includes/04_cleanup.sh +++ b/installation/includes/04_cleanup.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash -cleanup() { - sudo rm -rf /var/lib/apt/lists/* +_run_cleanup() { + sudo rm -rf /var/lib/apt/lists/* +} - echo "DONE: cleanup" +cleanup() { + run_with_log_frame _run_cleanup "Cleanup" } diff --git a/installation/includes/05_finish.sh b/installation/includes/05_finish.sh index 50c2f6d9d..55489ff46 100644 --- a/installation/includes/05_finish.sh +++ b/installation/includes/05_finish.sh @@ -14,8 +14,8 @@ Your SSH connection will disconnect. After the reboot, you can access the WebApp in your browser at http://${local_hostname}.local or http://${CURRENT_IP_ADDRESS} Don't forget to upload files. - -Do you want to reboot now? [Y/n]" 1>&3 +" | tee /dev/fd/3 +echo "Do you want to reboot now? [Y/n]" 1>&3 read -r response case "$response" in @@ -31,24 +31,3 @@ Do you want to reboot now? [Y/n]" 1>&3 ;; esac } - -# Generic emergency error handler that exits the script immediately -# Print additional custom message if passed as first argument -# Examples: -# cd some-dir || exit_on_error -# cd some-dir || exit_on_error "During installation of some" -exit_on_error () { - - echo -e "\n****************************************" | tee /dev/fd/3 - echo "ERROR OCCURRED! -A non-recoverable error occurred. -Check install log for details:" | tee /dev/fd/3 - echo "$INSTALLATION_LOGFILE" | tee /dev/fd/3 - echo "****************************************" | tee /dev/fd/3 - if [[ -n $1 ]]; then - echo "$1" | tee /dev/fd/3 - echo "****************************************" | tee /dev/fd/3 - fi - echo "Abort!" - exit 1 -} diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 44b03fca0..b72dd32ff 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -20,17 +20,36 @@ GIT_URL="https://github.com/${GIT_USER}/${GIT_REPO_NAME}" echo GIT_BRANCH $GIT_BRANCH echo GIT_URL $GIT_URL -CURRENT_USER="${SUDO_USER:-$USER}" +CURRENT_USER="${SUDO_USER:-$(whoami)}" +CURRENT_USER_GROUP=$(id -gn "$CURRENT_USER") HOME_PATH=$(getent passwd "$CURRENT_USER" | cut -d: -f6) echo "Current User: $CURRENT_USER" echo "User home dir: $HOME_PATH" INSTALLATION_PATH="${HOME_PATH}/${GIT_REPO_NAME}" INSTALL_ID=$(date +%s) +INSTALLATION_LOGFILE="${HOME_PATH}/INSTALL-${INSTALL_ID}.log" + +# Check if current distro is a 32 bit version +# Support for 64 bit Distros has not been checked (or precisely: is known not to work) +# All RaspianOS versions report as machine "armv6l" or "armv7l", if 32 bit (even the ARMv8 cores!) +_check_os_type() { + local os_type=$(uname -m) + + echo -e "\nChecking OS type '$os_type'" + + if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then + echo -e " ... OK!\n" + else + echo "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" + echo "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" + exit 1 + fi +} -checkPrerequisite() { - #currently the user 'pi' is mandatory - #https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1785 +# currently the user 'pi' is mandatory +# https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1785 +_check_user() { if [ "${CURRENT_USER}" != "pi" ]; then echo echo "ERROR: User must be 'pi'!" @@ -46,52 +65,104 @@ checkPrerequisite() { echo " Please check the wiki for further information" exit 2 fi + + if [ ! -d "${HOME_PATH}" ]; then + echo + echo "ERROR: HomeDir ${HOME_PATH} does not exist." + echo " Please create it and start again." + exit 2 + fi +} + +# Manipulate file descriptor for logging +# Behavior: +# Write To logfile: +# default stdout will only write to logfile +# default stderr will only write to logfile +# e.g echo "write only to logfile" +# Write To console (user window): +# redirect to fd 3 will only write to the console +# e.g. echo "write only to console" 1>&3 +# Write To both: +# use tee to write output to logfile and console +# e.g. echo "write to both" | tee /dev/fd/3 +_setup_logging(){ + if [ "$CI_RUNNING" == "true" ]; then + exec 3>&1 2>&1 + else + exec 3>&1 1>>"${INSTALLATION_LOGFILE}" 2>&1 || { echo "ERROR: Cannot create log file."; exit 1; } + fi + echo "Log start: ${INSTALL_ID}" +} + +# Generic emergency error handler that exits the script immediately +# Print additional custom message if passed as first argument +# Examples: +# a command || exit_on_error +# a command || exit_on_error "Execution of command failed" +exit_on_error () { + echo -e "\n****************************************" | tee /dev/fd/3 + echo "ERROR OCCURRED! +A non-recoverable error occurred. +Check install log for details:" | tee /dev/fd/3 + echo "$INSTALLATION_LOGFILE" | tee /dev/fd/3 + echo "****************************************" | tee /dev/fd/3 + if [[ -n $1 ]]; then + echo "$1" | tee /dev/fd/3 + echo "****************************************" | tee /dev/fd/3 + fi + echo "Abort!" + exit 1 } -download_jukebox_source() { +_download_jukebox_source() { + echo -e "\n\n" + echo "#########################################################" + echo "Downloading Phoniebox software from Github ..." 1>&3 + echo "Download Source: ${GIT_URL}/${GIT_BRANCH}" | tee /dev/fd/3 + + cd "${HOME_PATH}" || exit_on_error "ERROR: Changing to home dir failed." wget -qO- "${GIT_URL}/tarball/${GIT_BRANCH}" | tar xz # Use case insensitive search/sed because user names in Git Hub are case insensitive - GIT_REPO_DOWNLOAD=$(find . -maxdepth 1 -type d -iname "${GIT_USER}-${GIT_REPO_NAME}-*") - echo "GIT REPO DOWNLOAD = $GIT_REPO_DOWNLOAD" - GIT_HASH=$(echo "$GIT_REPO_DOWNLOAD" | sed -rn "s/.*${GIT_USER}-${GIT_REPO_NAME}-([0-9a-fA-F]+)/\1/ip") + local git_repo_download=$(find . -maxdepth 1 -type d -iname "${GIT_USER}-${GIT_REPO_NAME}-*") + echo "GIT REPO DOWNLOAD = $git_repo_download" + GIT_HASH=$(echo "$git_repo_download" | sed -rn "s/.*${GIT_USER}-${GIT_REPO_NAME}-([0-9a-fA-F]+)/\1/ip") # Save the git hash for this particular download for later git repo initialization echo "GIT HASH = $GIT_HASH" - if [[ -z ${GIT_REPO_DOWNLOAD} ]]; then - echo "ERROR in finding git download. Panic." - exit 1 + if [[ -z "${git_repo_download}" ]]; then + exit_on_error "ERROR: Couldn't find git download." fi - if [[ -z ${GIT_HASH} ]]; then - echo "ERROR in determining git hash from download. Panic." - exit 1 + if [[ -z "${GIT_HASH}" ]]; then + exit_on_error "ERROR: Couldn't determine git hash from download." fi - mv "$GIT_REPO_DOWNLOAD" "$GIT_REPO_NAME" - unset GIT_REPO_DOWNLOAD + mv "$git_repo_download" "$GIT_REPO_NAME" + echo -e "\nDONE: Downloading Phoniebox software from Github" + echo "#########################################################" } +_load_sources() { + # Load / Source dependencies + for i in "${INSTALLATION_PATH}"/installation/includes/*; do + source "$i" + done -### CHECK PREREQUISITE -checkPrerequisite - -### RUN INSTALLATION -INSTALLATION_LOGFILE="${HOME_PATH}/INSTALL-${INSTALL_ID}.log" -exec 3>&1 1>>"${INSTALLATION_LOGFILE}" 2>&1 || { echo "Cannot create log file. Panic."; exit 1; } -echo "Log start: ${INSTALL_ID}" + for j in "${INSTALLATION_PATH}"/installation/routines/*; do + source "$j" + done +} -clear 1>&3 -echo "Downloading Phoniebox software from Github ..." 1>&3 -echo "Download Source: ${GIT_URL}/${GIT_BRANCH}" | tee /dev/fd/3 -download_jukebox_source -cd "${INSTALLATION_PATH}" || { echo "ERROR in changing to install dir. Panic."; exit 1; } +### CHECK PREREQUISITE +_check_os_type +_check_user -# Load / Source dependencies -for i in "${INSTALLATION_PATH}"/installation/includes/*; do - source "$i" -done +### SETUP LOGGING +_setup_logging -for j in "${INSTALLATION_PATH}"/installation/routines/*; do - source "$j" -done +### RUN INSTALLATION +_download_jukebox_source +cd "${INSTALLATION_PATH}" || exit_on_error "ERROR: Changing to install dir failed." +_load_sources welcome run_with_timer install diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index d5ac7c547..50df075fe 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -118,6 +118,54 @@ Do you want to disable Bluetooth? [Y/n]" 1>&3 echo "DISABLE_BLUETOOTH=${DISABLE_BLUETOOTH}" } +_option_mpd() { + clear 1>&3 + if [[ "$SETUP_MPD" == true ]]; then + if [[ -f "${MPD_CONF_PATH}" || -f "${SYSTEMD_USR_PATH}/mpd.service" ]]; then + echo "-------------------------- MPD -------------------------- + +It seems there is a MPD already installed. +Note: It is important that MPD runs as a user service! +Would you like to overwrite your configuration? [Y/n]" 1>&3 + read -r response + case "$response" in + [nN][oO]|[nN]) + ENABLE_MPD_OVERWRITE_INSTALL=false + ;; + *) + ;; + esac + fi + fi + + echo "SETUP_MPD=${SETUP_MPD}" + if [ "$SETUP_MPD" == true ]; then + echo "ENABLE_MPD_OVERWRITE_INSTALL=${ENABLE_MPD_OVERWRITE_INSTALL}" + fi +} + +_option_rfid_reader() { + # ENABLE_RFID_READER + clear 1>&3 + echo "---------------------- RFID READER ---------------------- + +Phoniebox can be controlled with rfid cards/tags, if you +have a rfid reader connected. +Choose yes to setup a reader. You get prompted for +the type selection and configuration later on. + +Do you want to setup a rfid reader? [Y/n]" 1>&3 + read -r response + case "$response" in + [nN][oO]|[nN]) + ENABLE_RFID_READER=false + ;; + *) + ;; + esac + echo "ENABLE_RFID_READER=${ENABLE_RFID_READER}" +} + _option_samba() { # ENABLE_SAMBA clear 1>&3 @@ -133,7 +181,6 @@ Do you want to install Samba? [Y/n]" 1>&3 case "$response" in [nN][oO]|[nN]) ENABLE_SAMBA=false - ENABLE_KIOSK_MODE=false ;; *) ;; @@ -237,13 +284,13 @@ Disable Pi's on-chip audio (headphone / jack output)? [y/N]" 1>&3 _option_webapp_devel_build() { # Let's detect if we are on the official release branch - if [[ "$GIT_BRANCH" != "${GIT_BRANCH_RELEASE}" || "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then + if [[ "$GIT_BRANCH" != "${GIT_BRANCH_RELEASE}" || "$GIT_USER" != "$GIT_UPSTREAM_USER" || "$CI_RUNNING" == "true" ]]; then ENABLE_INSTALL_NODE=true # Unless ENABLE_WEBAPP_PROD_DOWNLOAD is forced to true by user override, do not download a potentially stale build - if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = "release-only" ]]; then + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == "release-only" ]]; then ENABLE_WEBAPP_PROD_DOWNLOAD=false fi - if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = false ]]; then + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == false ]]; then clear 1>&3 echo "--------------------- WEBAPP NODE --------------------- @@ -261,22 +308,23 @@ Do you want to install Node? [Y/n]" 1>&3 ;; esac # This message will be displayed at the end of the installation process - FIN_MESSAGE="$FIN_MESSAGE\nATTENTION: You need to build the web app locally with - $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u - This must be done after reboot, due to memory restrictions. - Read the documentation regarding local Web App builds!" + local tmp_fin_message="ATTENTION: You need to build the web app locally with + $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u + This must be done after reboot, due to memory restrictions. + Read the documentation regarding local Web App builds!" + FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" fi fi } -customize_options() { - echo "Customize Options starts" - +_run_customize_options() { _option_ipv6 _option_static_ip _option_autohotspot _option_bluetooth _option_disable_onboard_audio + _option_mpd + _option_rfid_reader _option_samba _option_webapp if [[ $ENABLE_WEBAPP == true ]] ; then @@ -286,6 +334,8 @@ customize_options() { # Bullseye is currently under active development and should be updated in any case. # Hence, removing the step below as it becomse mandatory # _options_update_raspi_os +} - echo "Customize Options ends" +customize_options() { + run_with_log_frame _run_customize_options "Customize Options" } diff --git a/installation/routines/install.sh b/installation/routines/install.sh index ca25a17a3..d241658b6 100644 --- a/installation/routines/install.sh +++ b/installation/routines/install.sh @@ -1,19 +1,18 @@ install() { - check_os_type clear 1>&3 customize_options clear 1>&3 set_raspi_config - if [ "$DISABLE_SSH_QOS" = true ] ; then set_ssh_qos; fi; - if [ "$UPDATE_RASPI_OS" = true ] ; then update_raspi_os; fi; + set_ssh_qos + update_raspi_os init_git_repo_from_tardir setup_jukebox_core - if [ "$SETUP_MPD" = true ] ; then setup_mpd; fi; - if [ "$ENABLE_SAMBA" = true ] ; then setup_samba; fi; - if [ "$ENABLE_WEBAPP" = true ] ; then setup_jukebox_webapp; fi; - if [ "$ENABLE_KIOSK_MODE" = true ] ; then setup_kiosk_mode; fi; + setup_mpd + setup_samba + setup_jukebox_webapp + setup_kiosk_mode setup_rfid_reader optimize_boot_time - if [ "$ENABLE_AUTOHOTSPOT" = true ] ; then setup_autohotspot; fi; + setup_autohotspot cleanup } diff --git a/installation/routines/optimize_boot_time.sh b/installation/routines/optimize_boot_time.sh index 2fea66a86..383f790c0 100644 --- a/installation/routines/optimize_boot_time.sh +++ b/installation/routines/optimize_boot_time.sh @@ -2,18 +2,24 @@ # Reference: https://panther.software/configuration-code/raspberry-pi-3-4-faster-boot-time-in-few-easy-steps/ +OPTIMIZE_DHCP_CONF="/etc/dhcpcd.conf" +OPTIMIZE_BOOT_CMDLINE_OPTIONS="consoleblank=1 logo.nologo quiet loglevel=0 plymouth.enable=0 vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fastboot noatime nodiratime noram" +OPTIMIZE_DHCP_CONF_HEADER="## Jukebox DHCP Config" +OPTIMIZE_IPV6_CONF_HEADER="## Jukebox IPV6 Config" +OPTIMIZE_BOOT_CONF_HEADER="## Jukebox Boot Config" + _optimize_disable_irrelevant_services() { - echo " * Disable keyboard-setup.service" + echo " Disable keyboard-setup.service" sudo systemctl disable keyboard-setup.service - echo " * Disable triggerhappy.service" + echo " Disable triggerhappy.service" sudo systemctl disable triggerhappy.service sudo systemctl disable triggerhappy.socket - echo " * Disable raspi-config.service" + echo " Disable raspi-config.service" sudo systemctl disable raspi-config.service - echo " * Disable apt-daily.service & apt-daily-upgrade.service" + echo " Disable apt-daily.service & apt-daily-upgrade.service" sudo systemctl disable apt-daily.service sudo systemctl disable apt-daily-upgrade.service sudo systemctl disable apt-daily.timer @@ -23,30 +29,28 @@ _optimize_disable_irrelevant_services() { # TODO: If false, actually make sure bluetooth is enabled _optimize_handle_bluetooth() { if [ "$DISABLE_BLUETOOTH" = true ] ; then - echo " * Disable hciuart.service and bluetooth" + echo " Disable bluetooth" | tee /dev/fd/3 sudo systemctl disable hciuart.service sudo systemctl disable bluetooth.service fi } # TODO: Allow options to enable/disable wifi, Dynamic/Static IP etc. -_optimize_handle_network_connection() { +_optimize_static_ip() { # Static IP Address and DHCP optimizations - local DHCP_CONF="/etc/dhcpcd.conf" - if [ "$ENABLE_STATIC_IP" = true ] ; then - echo " * Set static IP address" | tee /dev/fd/3 - if grep -q "## Jukebox DHCP Config" "$DHCP_CONF"; then - echo " Skipping. Already set up!" | tee /dev/fd/3 + echo " Set static IP address" | tee /dev/fd/3 + if grep -q "${OPTIMIZE_DHCP_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then + echo " Skipping. Already set up!" else # DHCP has not been configured - echo " * ${CURRENT_INTERFACE} is the default network interface" | tee /dev/fd/3 - echo " * ${CURRENT_GATEWAY} is the Router Gateway address" | tee /dev/fd/3 - echo " * Using ${CURRENT_IP_ADDRESS} as the static IP for now" | tee /dev/fd/3 + echo " ${CURRENT_INTERFACE} is the default network interface" + echo " ${CURRENT_GATEWAY} is the Router Gateway address" + echo " Using ${CURRENT_IP_ADDRESS} as the static IP for now" - sudo tee -a $DHCP_CONF <<-EOF + sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF -## Jukebox DHCP Config +${OPTIMIZE_DHCP_CONF_HEADER} interface ${CURRENT_INTERFACE} static ip_address=${CURRENT_IP_ADDRESS}/24 static routers=${CURRENT_GATEWAY} @@ -55,59 +59,114 @@ static domain_name_servers=${CURRENT_GATEWAY} EOF fi - else - echo " * Skipped static IP address" fi } # TODO: Allow both Enable and Disable _optimize_ipv6_arp() { if [ "$DISABLE_IPv6" = true ] ; then - echo " * Disabling IPV6 and ARP" - sudo tee -a $DHCP_CONF <<-EOF + echo " Disabling IPV6" | tee /dev/fd/3 + if grep -q "${OPTIMIZE_IPV6_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then + echo " Skipping. Already set up!" + else + sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF -## Jukebox boot speed-up settings +${OPTIMIZE_IPV6_CONF_HEADER} noarp ipv4only noipv6 EOF - + fi fi } # TODO: Allow both Enable and Disable _optimize_handle_boot_screen() { if [ "$DISABLE_BOOT_SCREEN" = true ] ; then - echo " * Disable RPi rainbow screen" - BOOT_CONFIG='/boot/config.txt' - sudo tee -a $BOOT_CONFIG <<-EOF + echo " Disable RPi rainbow screen" + if grep -q "${OPTIMIZE_BOOT_CONF_HEADER}" "$RPI_BOOT_CONFIG_FILE"; then + echo " Skipping. Already set up!" + else + sudo tee -a $RPI_BOOT_CONFIG_FILE <<-EOF -## Jukebox Settings +${OPTIMIZE_BOOT_CONF_HEADER} disable_splash=1 EOF + fi fi } # TODO: Allow both Enable and Disable _optimize_handle_boot_logs() { if [ "$DISABLE_BOOT_LOGS_PRINT" = true ] ; then - echo " * Disable boot logs" - BOOT_CMDLINE='/boot/cmdline.txt' - sudo sed -i "$ s/$/ consoleblank=1 logo.nologo quiet loglevel=0 plymouth.enable=0 vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fastboot noatime nodiratime noram/" $BOOT_CMDLINE + echo " Disable boot logs" + + if [ ! -s "${RPI_BOOT_CMDLINE_FILE}" ];then + sudo tee "${RPI_BOOT_CMDLINE_FILE}" <<-EOF +${OPTIMIZE_BOOT_CMDLINE_OPTIONS} +EOF + else + for option in $OPTIMIZE_BOOT_CMDLINE_OPTIONS + do + if ! grep -qiw "$option" "${RPI_BOOT_CMDLINE_FILE}" ; then + sudo sed -i "s/$/ $option/" "${RPI_BOOT_CMDLINE_FILE}" + fi + done + fi fi } -optimize_boot_time() { - echo "Optimize boot time" | tee /dev/fd/3 - _optimize_disable_irrelevant_services - _optimize_handle_bluetooth - _optimize_handle_network_connection - _optimize_ipv6_arp - _optimize_handle_boot_screen - _optimize_handle_boot_logs +_optimize_check() { + print_verify_installation + + verify_optional_service_enablement keyboard-setup.service disabled + verify_optional_service_enablement triggerhappy.service disabled + verify_optional_service_enablement triggerhappy.socket disabled + verify_optional_service_enablement raspi-config.service disabled + verify_optional_service_enablement apt-daily.service disabled + verify_optional_service_enablement apt-daily-upgrade.service disabled + verify_optional_service_enablement apt-daily.timer disabled + verify_optional_service_enablement apt-daily-upgrade.timer disabled - echo "DONE: optimize_boot_time" + if [ "$DISABLE_BLUETOOTH" = true ] ; then + verify_optional_service_enablement hciuart.service disabled + verify_optional_service_enablement bluetooth.service disabled + fi + + if [ "$ENABLE_STATIC_IP" = true ] ; then + verify_file_contains_string_once "${OPTIMIZE_DHCP_CONF_HEADER}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_INTERFACE}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_IP_ADDRESS}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_GATEWAY}" "${OPTIMIZE_DHCP_CONF}" + fi + if [ "$DISABLE_IPv6" = true ] ; then + verify_file_contains_string_once "${OPTIMIZE_IPV6_CONF_HEADER}" "${OPTIMIZE_DHCP_CONF}" + fi + if [ "$DISABLE_BOOT_SCREEN" = true ] ; then + verify_file_contains_string_once "${OPTIMIZE_BOOT_CONF_HEADER}" "${RPI_BOOT_CONFIG_FILE}" + fi + + if [ "$DISABLE_BOOT_LOGS_PRINT" = true ] ; then + for option in $OPTIMIZE_BOOT_CMDLINE_OPTIONS + do + verify_file_contains_string_once $option "${RPI_BOOT_CMDLINE_FILE}" + done + fi +} + +_run_optimize_boot_time() { + _optimize_disable_irrelevant_services + _optimize_handle_bluetooth + _optimize_static_ip + _optimize_ipv6_arp + _optimize_handle_boot_screen + _optimize_handle_boot_logs + _optimize_check +} + +optimize_boot_time() { + run_with_log_frame _run_optimize_boot_time "Optimize boot time" } diff --git a/installation/routines/set_raspi_config.sh b/installation/routines/set_raspi_config.sh index 1b1c3ee55..a9cb7b6f1 100644 --- a/installation/routines/set_raspi_config.sh +++ b/installation/routines/set_raspi_config.sh @@ -1,33 +1,33 @@ #!/usr/bin/env bash - -set_raspi_config() { - echo "Set default raspi-config" | tee /dev/fd/3 +_run_set_raspi_config() { # Source: https://raspberrypi.stackexchange.com/a/66939 # Autologin - echo " * Enable Autologin for user" + echo " Enable Autologin for user" sudo raspi-config nonint do_boot_behaviour B2 # Wait for network at boot - # echo " * Enable 'Wait for network at boot'" + # echo " Enable 'Wait for network at boot'" # sudo raspi-config nonint do_boot_wait 1 # power management of wifi: switch off to avoid disconnecting - echo " * Disable Wifi power management to avoid disconnecting" + echo " Disable Wifi power management to avoid disconnecting" sudo iwconfig wlan0 power off # On-board audio - if [[ $(get_onboard_audio) -eq 1 ]]; then - DISABLE_ONBOARD_AUDIO=${DISABLE_ONBOARD_AUDIO:-false} - if [[ $DISABLE_ONBOARD_AUDIO = true ]]; then - echo " * Disable on-chip BCM audio" - echo "Backup ${RPI_BOOT_CONFIG_FILE} --> ${DISABLE_ONBOARD_AUDIO_BACKUP}" + if [ "$DISABLE_ONBOARD_AUDIO" == true ]; then + echo " Disable on-chip BCM audio" + if grep -q -E "^dtparam=([^,]*,)*audio=(on|true|yes|1).*" "${RPI_BOOT_CONFIG_FILE}" ; then + echo " Backup ${RPI_BOOT_CONFIG_FILE} --> ${DISABLE_ONBOARD_AUDIO_BACKUP}" sudo cp "${RPI_BOOT_CONFIG_FILE}" "${DISABLE_ONBOARD_AUDIO_BACKUP}" sudo sed -i "s/^\(dtparam=\([^,]*,\)*\)audio=\(on\|true\|yes\|1\)\(.*\)/\1audio=off\4/g" "${RPI_BOOT_CONFIG_FILE}" + else + echo " On board audio seems to be off already. Not touching ${RPI_BOOT_CONFIG_FILE}" fi - else - echo "On board audio seems to be off already. Not touching ${RPI_BOOT_CONFIG_FILE}" fi +} +set_raspi_config() { + run_with_log_frame _run_set_raspi_config "Set default raspi-config" } diff --git a/installation/routines/set_ssh_qos.sh b/installation/routines/set_ssh_qos.sh index ad67ddc41..eaca62fed 100644 --- a/installation/routines/set_ssh_qos.sh +++ b/installation/routines/set_ssh_qos.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash set_ssh_qos() { - # The latest version of SSH installed on the Raspberry Pi 3 uses QoS headers, which disagrees with some - # routers and other hardware. This causes immense delays when remotely accessing the RPi over ssh. - echo " * Set SSH QoS to best effort" - echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/sshd_config - echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/ssh_config + if [ "$DISABLE_SSH_QOS" == true ] ; then + # The latest version of SSH installed on the Raspberry Pi 3 uses QoS headers, which disagrees with some + # routers and other hardware. This causes immense delays when remotely accessing the RPi over ssh. + echo " Set SSH QoS to best effort" + echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/sshd_config + echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/ssh_config + fi } diff --git a/installation/routines/setup_autohotspot.sh b/installation/routines/setup_autohotspot.sh index 214a90e0f..a385352dd 100644 --- a/installation/routines/setup_autohotspot.sh +++ b/installation/routines/setup_autohotspot.sh @@ -1,14 +1,34 @@ #!/usr/bin/env bash +# inspired by +# https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection + + +AUTOHOTSPOT_HOSTAPD_CONF_FILE="/etc/hostapd/hostapd.conf" +AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE="/etc/default/hostapd" +AUTOHOTSPOT_DNSMASQ_CONF_FILE="/etc/dnsmasq.conf" +AUTOHOTSPOT_DHCPD_CONF_FILE="/etc/dhcpcd.conf" + +AUTOHOTSPOT_TARGET_PATH="/usr/bin/autohotspot" + _get_interface() { # interfaces may vary WIFI_INTERFACE=$(iw dev | grep "Interface"| awk '{ print $2 }') WIFI_REGION=$(iw reg get | grep country | awk '{ print $2}' | cut -d: -f1) -} + # fix for CI runs on docker + if [ "${CI_RUNNING}" == "true" ]; then + if [ -z "${WIFI_INTERFACE}" ]; then + WIFI_INTERFACE="CI TEST INTERFACE" + fi + if [ -z "${WIFI_REGION}" ]; then + WIFI_REGION="CI TEST REGION" + fi + fi +} _install_packages() { - sudo apt-get -y install hostapd dnsmasq + sudo apt-get -y install hostapd dnsmasq iw # disable services. We want to start them manually sudo systemctl unmask hostapd @@ -17,18 +37,18 @@ _install_packages() { } _configure_hostapd() { - HOSTAPD_CUSTOM_FILE="${INSTALLATION_PATH}"/resources/autohotspot/hostapd.conf - HOSTAPD_CONF_FILE="/etc/hostapd/hostapd.conf" + local HOSTAPD_CUSTOM_FILE="${INSTALLATION_PATH}"/resources/autohotspot/hostapd.conf + sed -i "s/WIFI_INTERFACE/${WIFI_INTERFACE}/g" "${HOSTAPD_CUSTOM_FILE}" sed -i "s/AUTOHOTSPOT_PASSWORD/${AUTOHOTSPOT_PASSWORD}/g" "${HOSTAPD_CUSTOM_FILE}" sed -i "s/WIFI_REGION/${WIFI_REGION}/g" "${HOSTAPD_CUSTOM_FILE}" - sudo cp "${HOSTAPD_CUSTOM_FILE}" "${HOSTAPD_CONF_FILE}" + sudo cp "${HOSTAPD_CUSTOM_FILE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" - sudo sed -i "s@^#DAEMON_CONF=.*@DAEMON_CONF=\"${HOSTAPD_CONF_FILE}\"@g" /etc/default/hostapd + sudo sed -i "s@^#DAEMON_CONF=.*@DAEMON_CONF=\"${AUTOHOTSPOT_HOSTAPD_CONF_FILE}\"@g" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" } _configure_dnsmasq() { - sudo tee -a /etc/dnsmasq.conf <<-EOF + sudo tee -a "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" <<-EOF #AutoHotspot Config #stop DNSmasq from using resolv.conf no-resolv @@ -42,7 +62,7 @@ EOF _other_configuration() { sudo mv /etc/network/interfaces /etc/network/interfaces.bak sudo touch /etc/network/interfaces - echo nohook wpa_supplicant | sudo tee -a /etc/dhcpcd.conf + echo nohook wpa_supplicant | sudo tee -a "${AUTOHOTSPOT_DHCPD_CONF_FILE}" } _install_service_and_timer() { @@ -52,23 +72,45 @@ _install_service_and_timer() { } _install_autohotspot_script() { - TARGET_PATH="/usr/bin/autohotspot" - sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot "${TARGET_PATH}" - sudo chmod +x "${TARGET_PATH}" + sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot "${AUTOHOTSPOT_TARGET_PATH}" + sudo chmod +x "${AUTOHOTSPOT_TARGET_PATH}" } -setup_autohotspot() { - echo "Install AutoHotspot functionality" | tee /dev/fd/3 - # inspired by - # https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection - _get_interface +_autohotspot_check() { + print_verify_installation + + verify_apt_packages hostapd dnsmasq iw + + verify_service_enablement hostapd.service disabled + verify_service_enablement dnsmasq.service disabled + verify_service_enablement autohotspot.service enabled + + verify_files_exists "/etc/cron.d/autohotspot" + verify_files_exists "${AUTOHOTSPOT_TARGET_PATH}" + + verify_file_contains_string "${WIFI_INTERFACE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "${AUTOHOTSPOT_PASSWORD}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "${WIFI_REGION}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + + verify_file_contains_string "${WIFI_INTERFACE}" "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + verify_file_contains_string "nohook wpa_supplicant" "${AUTOHOTSPOT_DHCPD_CONF_FILE}" +} + +_run_setup_autohotspot() { _install_packages + _get_interface _configure_hostapd _configure_dnsmasq _other_configuration _install_autohotspot_script _install_service_and_timer + _autohotspot_check +} - echo "DONE: setup_autohotspot" +setup_autohotspot() { + if [ "$ENABLE_AUTOHOTSPOT" == true ] ; then + run_with_log_frame _run_setup_autohotspot "Install AutoHotspot" + fi } diff --git a/installation/routines/setup_git.sh b/installation/routines/setup_git.sh index 740d43ae3..613062f43 100644 --- a/installation/routines/setup_git.sh +++ b/installation/routines/setup_git.sh @@ -2,7 +2,7 @@ GIT_ABORT_MSG="Aborting dir to git repo conversion. Your directory content is untouched, you simply cannot use git for updating / developing" _git_install_os_dependencies() { - echo "Install Git dependencies" + echo " Install Git dependencies" sudo apt-get -y update; sudo apt-get -y install \ git \ --no-install-recommends \ @@ -30,6 +30,7 @@ _git_convert_tardir_git_repo() { # We simply get everything from the beginning of future 3 development but excluding Version 2.X if [[ $GIT_USE_SSH == true ]]; then git remote add origin "git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git" + echo "" echo "*** Git fetch (SSH) *******************************" # Prevent: The authenticity of host 'github.com (140.82.121.4)' can't be established. # Do only for this one command, so we do not disable the checks forever @@ -43,6 +44,7 @@ _git_convert_tardir_git_repo() { echo "* Defaulting to HTTPS protocol. You can change back to SSH later with" echo "* git remote set-url origin git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git" echo "* git remote set-url upstream git@github.com:${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git" + echo "" git remote remove origin GIT_USE_SSH=false else @@ -58,7 +60,8 @@ _git_convert_tardir_git_repo() { if [[ "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then git remote add upstream "https://github.com/${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git" fi - echo "*** Git fetch (HTTPS) *****************************" + echo "" + echo "*** Git fetch (HTTPS) *****************************" if ! git fetch origin --set-upstream --shallow-since=2021-04-21 --tags "${GIT_BRANCH}"; then echo "Error: Could not fetch repository!" echo -e "$GIT_ABORT_MSG" @@ -66,6 +69,7 @@ _git_convert_tardir_git_repo() { fi fi HASH_BRANCH=$(git rev-parse FETCH_HEAD) || { echo -e "$GIT_ABORT_MSG"; return; } + echo "" echo "*** FETCH_HEAD ($GIT_BRANCH) = $HASH_BRANCH" git add . @@ -113,12 +117,14 @@ _git_convert_tardir_git_repo() { # Provide some status outputs to the user if [[ "${HASH_BRANCH}" != "${HASH_HEAD}" ]]; then + echo "" echo "*** IMPORTANT NOTICE *******************************" echo "* Your requested branch has moved on while you were installing." - echo "* Don't worry! We will stay within the the exact download version!" + echo "* Don't worry! We will stay within the exact download version!" echo "* But we set up the git repo to be ready for updating." echo "* To start updating (observe updating guidelines!), do:" echo "* $ git pull origin $GIT_BRANCH" + echo "" fi echo "*** Git remotes ************************************" @@ -137,12 +143,20 @@ _git_convert_tardir_git_repo() { unset HASH_BRANCH } -init_git_repo_from_tardir() { - echo "Install Git & init repository" | tee /dev/fd/3 +_git_repo_check() { + print_verify_installation + + verify_apt_packages git + verify_dirs_chmod_chown 755 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${INSTALLATION_PATH}/.git" +} - cd "${INSTALLATION_PATH}" || exit_on_error - _git_install_os_dependencies - _git_convert_tardir_git_repo +_run_init_git_repo_from_tardir() { + cd "${INSTALLATION_PATH}" || exit_on_error + _git_install_os_dependencies + _git_convert_tardir_git_repo + _git_repo_check +} - echo "DONE: init_git_repo_from_tardir" +init_git_repo_from_tardir() { + run_with_log_frame _run_init_git_repo_from_tardir "Install Git & init repository" } diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index e6dfa6d8f..3daa53081 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -9,6 +9,9 @@ GD_ID_COMPILED_PYZMQ_ARMV6="1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n" # https://drive.g ZMQ_TMP_DIR="libzmq" ZMQ_PREFIX="/usr/local" +JUKEBOX_PULSE_CONFIG="${HOME_PATH}"/.config/pulse/default.pa +JUKEBOX_SERVICE_NAME="${SYSTEMD_USR_PATH}/jukebox-daemon.service" + _show_slow_hardware_message() { echo " -------------------------------------------------------------------- | Your hardware is a little slower so this step will take a while. | @@ -19,14 +22,11 @@ echo " -------------------------------------------------------------------- # Functions _jukebox_core_install_os_dependencies() { - echo " Install Jukebox OS dependencies" + echo " Install Jukebox OS dependencies" | tee /dev/fd/3 + + local apt_packages=$(get_args_from_file "${INSTALLATION_PATH}/packages-core.txt") sudo apt-get -y update && sudo apt-get -y install \ - at \ - alsa-utils \ - python3 python3-venv python3-dev \ - espeak ffmpeg mpg123 \ - pulseaudio pulseaudio-module-bluetooth pulseaudio-utils caps \ - libasound2-dev \ + $apt_packages \ --no-install-recommends \ --allow-downgrades \ --allow-remove-essential \ @@ -34,11 +34,10 @@ _jukebox_core_install_os_dependencies() { } _jukebox_core_install_python_requirements() { - echo " Install Python requirements" + echo " Install Python requirements" | tee /dev/fd/3 cd "${INSTALLATION_PATH}" || exit_on_error - VIRTUAL_ENV="${INSTALLATION_PATH}/.venv" python3 -m venv $VIRTUAL_ENV source "$VIRTUAL_ENV/bin/activate" @@ -47,9 +46,9 @@ _jukebox_core_install_python_requirements() { } _jukebox_core_configure_pulseaudio() { - echo "Copy PulseAudio configuration" - mkdir -p ~/.config/pulse - cp -f "${INSTALLATION_PATH}/resources/default-settings/pulseaudio.default.pa" ~/.config/pulse/default.pa + echo " Copy PulseAudio configuration" | tee /dev/fd/3 + mkdir -p $(dirname "$JUKEBOX_PULSE_CONFIG") + cp -f "${INSTALLATION_PATH}/resources/default-settings/pulseaudio.default.pa" "${JUKEBOX_PULSE_CONFIG}" } _jukebox_core_build_libzmq_with_drafts() { @@ -87,7 +86,7 @@ _jukebox_core_build_and_install_pyzmq() { # Sources: # https://pyzmq.readthedocs.io/en/latest/howto/draft.html # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md - echo " Build and install pyzmq with WebSockets Support" + echo " Build and install pyzmq with WebSockets Support" | tee /dev/fd/3 if ! pip list | grep -F pyzmq >> /dev/null; then # Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it @@ -112,37 +111,60 @@ _jukebox_core_build_and_install_pyzmq() { ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ pip install --no-cache-dir --no-binary "pyzmq" --pre pyzmq else - echo " Skipping. pyzmq already installed" + echo " Skipping. pyzmq already installed" | tee /dev/fd/3 fi } _jukebox_core_install_settings() { - echo " Register Jukebox settings" + echo " Register Jukebox settings" | tee /dev/fd/3 cp -f "${INSTALLATION_PATH}/resources/default-settings/jukebox.default.yaml" "${SETTINGS_PATH}/jukebox.yaml" cp -f "${INSTALLATION_PATH}/resources/default-settings/logger.default.yaml" "${SETTINGS_PATH}/logger.yaml" } _jukebox_core_register_as_service() { - echo " Register Jukebox Core user service" + echo " Register Jukebox Core user service" | tee /dev/fd/3 - local jukebox_service="${SYSTEMD_USR_PATH}/jukebox-daemon.service" - sudo cp -f "${INSTALLATION_PATH}/resources/default-services/jukebox-daemon.service" "${jukebox_service}" - sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${jukebox_service}" - sudo chmod 644 "${jukebox_service}" + sudo cp -f "${INSTALLATION_PATH}/resources/default-services/jukebox-daemon.service" "${JUKEBOX_SERVICE_NAME}" + sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${JUKEBOX_SERVICE_NAME}" + sudo chmod 644 "${JUKEBOX_SERVICE_NAME}" systemctl --user daemon-reload systemctl --user enable jukebox-daemon.service } -setup_jukebox_core() { - echo "Install Jukebox Core" | tee /dev/fd/3 +_jukebox_core_check() { + print_verify_installation + + local apt_packages=$(get_args_from_file "${INSTALLATION_PATH}/packages-core.txt") + verify_apt_packages $apt_packages + + verify_dirs_exists "${VIRTUAL_ENV}" + + local pip_modules=$(get_args_from_file "${INSTALLATION_PATH}/requirements.txt") + verify_pip_modules pyzmq $pip_modules + + verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${JUKEBOX_PULSE_CONFIG}" - _jukebox_core_install_os_dependencies - _jukebox_core_install_python_requirements - _jukebox_core_configure_pulseaudio - _jukebox_core_build_and_install_pyzmq - _jukebox_core_install_settings - _jukebox_core_register_as_service + verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${SETTINGS_PATH}/jukebox.yaml" + verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${SETTINGS_PATH}/logger.yaml" - echo "DONE: setup_jukebox_core" + verify_files_chmod_chown 644 root root "${SYSTEMD_USR_PATH}/jukebox-daemon.service" + + verify_file_contains_string "${INSTALLATION_PATH}" "${JUKEBOX_SERVICE_NAME}" + + verify_service_enablement jukebox-daemon.service enabled --user +} + +_run_setup_jukebox_core() { + _jukebox_core_install_os_dependencies + _jukebox_core_install_python_requirements + _jukebox_core_configure_pulseaudio + _jukebox_core_build_and_install_pyzmq + _jukebox_core_install_settings + _jukebox_core_register_as_service + _jukebox_core_check +} + +setup_jukebox_core() { + run_with_log_frame _run_setup_jukebox_core "Install Jukebox Core" } diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 54be3119c..df58188fb 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -2,6 +2,7 @@ # Constants GD_ID_COMPILED_WEBAPP="1um-smyfsVPzVZn18hhwuFt97XR3PjAbB" # https://drive.google.com/file/d/1um-smyfsVPzVZn18hhwuFt97XR3PjAbB/view?usp=sharing +WEBAPP_NGINX_SITE_DEFAULT_CONF="/etc/nginx/sites-available/default" # For ARMv7+ NODE_MAJOR=20 @@ -39,14 +40,13 @@ _jukebox_webapp_install_node() { sudo apt-get update sudo apt-get install -y nodejs fi - fi } # TODO: Avoid building the app locally # Instead implement a Github Action that prebuilds on commititung a git tag _jukebox_webapp_build() { - echo " Building web application" + echo " Building web application" | tee /dev/fd/3 cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error npm ci --prefer-offline --no-audit --production rm -rf build @@ -70,30 +70,47 @@ _jukebox_webapp_register_as_system_service_with_nginx() { sudo apt-get -y purge apache2 sudo apt-get -y install nginx - sudo service nginx start - - sudo mv -f /etc/nginx/sites-available/default /etc/nginx/sites-available/default.orig - sudo cp -f "${INSTALLATION_PATH}/resources/default-settings/nginx.default" /etc/nginx/sites-available/default + sudo mv -f "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}.orig" + sudo cp -f "${INSTALLATION_PATH}/resources/default-settings/nginx.default" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" # make sure nginx can access the home directory of the user - sudo chmod o+x /home/pi + sudo chmod o+x "${HOME_PATH}" - sudo service nginx restart + sudo systemctl restart nginx.service } -setup_jukebox_webapp() { - echo "Install web application" | tee /dev/fd/3 +_jukebox_webapp_check() { + print_verify_installation - if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then - _jukebox_webapp_download - fi - if [[ $ENABLE_INSTALL_NODE == true ]] ; then - _jukebox_webapp_install_node - # Local Web App build during installation does not work at the moment - # Needs to be done after reboot! There will be a message at the end of the installation process - # _jukebox_webapp_build - fi - _jukebox_webapp_register_as_system_service_with_nginx + if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then + verify_dirs_exists "${INSTALLATION_PATH}/src/webapp/build" + fi + if [[ $ENABLE_INSTALL_NODE == true ]] ; then + verify_apt_packages nodejs + fi + + verify_apt_packages nginx + verify_files_exists "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" - echo "DONE: setup_jukebox_webapp" + verify_service_enablement nginx.service enabled +} + +_run_setup_jukebox_webapp() { + if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then + _jukebox_webapp_download + fi + if [[ $ENABLE_INSTALL_NODE == true ]] ; then + _jukebox_webapp_install_node + # Local Web App build during installation does not work at the moment + # Needs to be done after reboot! There will be a message at the end of the installation process + # _jukebox_webapp_build + fi + _jukebox_webapp_register_as_system_service_with_nginx + _jukebox_webapp_check +} + +setup_jukebox_webapp() { + if [ "$ENABLE_WEBAPP" == true ] ; then + run_with_log_frame _run_setup_jukebox_webapp "Install web application" + fi } diff --git a/installation/routines/setup_kiosk_mode.sh b/installation/routines/setup_kiosk_mode.sh index b2857b624..f8d07971c 100644 --- a/installation/routines/setup_kiosk_mode.sh +++ b/installation/routines/setup_kiosk_mode.sh @@ -1,6 +1,13 @@ #!/usr/bin/env bash +KIOSK_MODE_CONF_HEADER="## Jukebox Kiosk Mode" +KIOSK_MODE_XINITRC='/etc/xdg/openbox/autostart' +KIOSK_MODE_BASHRC="${HOME_PATH}/.bashrc" +KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK='/etc/chromium-browser/customizations/01-disable-update-check' +KIOSK_MODE_CHROMIUM_FLAG_UPDATE_INTERVAL='--check-for-update-interval=31536000' + _kiosk_mode_install_os_dependencies() { + echo " Install Kiosk Mode dependencies" | tee /dev/fd/3 # Resource: # https://blog.r0b.io/post/minimal-rpi-kiosk/ sudo apt-get -qq -y install --no-install-recommends \ @@ -12,19 +19,20 @@ _kiosk_mode_install_os_dependencies() { } _kiosk_mode_set_autostart() { + echo " Configure Kiosk Mode" | tee /dev/fd/3 local _DISPLAY='$DISPLAY' local _XDG_VTNR='$XDG_VTNR' - cat << EOF >> /home/pi/.bashrc -## Jukebox kiosk autostart + tee -a "${KIOSK_MODE_BASHRC}" <<-EOF + +${KIOSK_MODE_CONF_HEADER} [[ -z $_DISPLAY && $_XDG_VTNR -eq 1 ]] && startx -- -nocursor EOF - local XINITRC='/etc/xdg/openbox/autostart' - cat << EOF | sudo tee -a $XINITRC + sudo tee -a "${KIOSK_MODE_XINITRC}" <<-EOF -## Jukebox Kiosk Mode +${KIOSK_MODE_CONF_HEADER} # Disable any form of screen saver / screen blanking / power management xset s off xset s noblank @@ -46,16 +54,43 @@ EOF _kiosk_mode_update_settings() { # Resource: https://github.com/Thyraz/Sonos-Kids-Controller/blob/d1f061f4662c54ae9b8dc8b545f9c3ba39f670eb/README.md#kiosk-mode-installation - sudo touch /etc/chromium-browser/customizations/01-disable-update-check;echo CHROMIUM_FLAGS=\"\$\{CHROMIUM_FLAGS\} --check-for-update-interval=31536000\" | sudo tee /etc/chromium-browser/customizations/01-disable-update-check + sudo mkdir -p $(dirname "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}") + sudo rm -f "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" + sudo tee -a "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" <<-EOF +${KIOSK_MODE_CONF_HEADER} +CHROMIUM_FLAGS=\"\$\{CHROMIUM_FLAGS\} --check-for-update-interval=31536000\" +EOF +} + +_kiosk_mode_check() { + print_verify_installation + + verify_apt_packages xserver-xorg \ + x11-xserver-utils \ + xinit \ + openbox \ + chromium-browser + verify_files_exists "${KIOSK_MODE_BASHRC}" + verify_file_contains_string "${KIOSK_MODE_CONF_HEADER}" "${KIOSK_MODE_BASHRC}" + + verify_files_exists "${KIOSK_MODE_XINITRC}" + verify_file_contains_string "${KIOSK_MODE_CONF_HEADER}" "${KIOSK_MODE_XINITRC}" + + verify_files_exists "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" + verify_file_contains_string "${KIOSK_MODE_CONF_HEADER}" "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" } -setup_kiosk_mode() { - echo "Setup Kiosk Mode" | tee /dev/fd/3 +_run_setup_kiosk_mode() { + _kiosk_mode_install_os_dependencies + _kiosk_mode_set_autostart + _kiosk_mode_update_settings + _kiosk_mode_check +} - _kiosk_mode_install_os_dependencies - _kiosk_mode_set_autostart - _kiosk_mode_update_settings - echo "DONE: setup_kiosk_mode" +setup_kiosk_mode() { + if [ "$ENABLE_KIOSK_MODE" == true ] ; then + run_with_log_frame _run_setup_kiosk_mode "Setup Kiosk Mode" + fi } diff --git a/installation/routines/setup_mpd.sh b/installation/routines/setup_mpd.sh index 53b9ac01b..40ffe8aac 100644 --- a/installation/routines/setup_mpd.sh +++ b/installation/routines/setup_mpd.sh @@ -3,13 +3,11 @@ AUDIOFOLDERS_PATH="${SHARED_PATH}/audiofolders" PLAYLISTS_PATH="${SHARED_PATH}/playlists" -# Do not change this directory! It must match MPDs expectation where to find the user configuration -MPD_CONF_PATH="$HOME/.config/mpd/mpd.conf" - _mpd_install_os_dependencies() { + echo " Install MPD OS dependencies" sudo apt-get -y update - echo "Install MPD OS dependencies" - echo "Note: Installing MPD will cause a message: 'Job failed. See journalctl -xe for details'" + + echo "Note: Installing MPD might cause a message: 'Job failed. See journalctl -xe for details'" echo "It can be ignored! It's an artefact of the MPD installation - nothing we can do about it." sudo apt-get -y install \ mpd mpc \ @@ -20,8 +18,15 @@ _mpd_install_os_dependencies() { } _mpd_configure() { + echo " Configure MPD as user local service" | tee /dev/fd/3 + + # Make sure system-wide mpd is disabled + sudo systemctl stop mpd.socket + sudo systemctl stop mpd.service + sudo systemctl disable mpd.socket + sudo systemctl disable mpd.service # MPD will be setup as user process (rather than a system-wide process) - mkdir -p ~/.config/mpd + mkdir -p $(dirname "$MPD_CONF_PATH") cp -f "${INSTALLATION_PATH}/resources/default-settings/mpd.default.conf" "${MPD_CONF_PATH}" @@ -29,48 +34,38 @@ _mpd_configure() { sed -i 's|%%JUKEBOX_AUDIOFOLDERS_PATH%%|'"$AUDIOFOLDERS_PATH"'|' "${MPD_CONF_PATH}" sed -i 's|%%JUKEBOX_PLAYLISTS_PATH%%|'"$PLAYLISTS_PATH"'|' "${MPD_CONF_PATH}" + # Prepare user-service MPD to be started at next boot + systemctl --user daemon-reload + systemctl --user enable mpd.socket + systemctl --user enable mpd.service } -setup_mpd() { - echo "Install MPD" | tee /dev/fd/3 +_mpd_check() { + print_verify_installation - local MPD_EXECUTE_INSTALL=true + verify_apt_packages mpd mpc - if [[ -f ${MPD_CONF_PATH} || -f ${SYSTEMD_USR_PATH}/mpd.service ]]; then - echo "It seems there is a MPD already installed. -Note: It is important that MPD runs as a user service! -Would you like to overwrite your configuration? [Y/n]" 1>&3 - read -r response - case "$response" in - [nN][oO]|[nN]) - MPD_EXECUTE_INSTALL=false - ;; - *) - ;; - esac - fi + verify_files_chmod_chown 755 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${MPD_CONF_PATH}" - echo "MPD_EXECUTE_INSTALL=${MPD_EXECUTE_INSTALL}" + verify_file_contains_string "${AUDIOFOLDERS_PATH}" "${MPD_CONF_PATH}" + verify_file_contains_string "${PLAYLISTS_PATH}" "${MPD_CONF_PATH}" - if [[ $MPD_EXECUTE_INSTALL == true ]] ; then + verify_service_enablement mpd.socket disabled + verify_service_enablement mpd.service disabled - # Install/update only if enabled: do not stuff up any existing configuration - _mpd_install_os_dependencies + verify_service_enablement mpd.socket enabled --user + verify_service_enablement mpd.service enabled --user +} - # Make sure system-wide mpd is disabled - echo "Configure MPD as user local service" | tee /dev/fd/3 - sudo systemctl stop mpd.socket - sudo systemctl stop mpd - sudo systemctl disable mpd.socket - sudo systemctl disable mpd +_run_setup_mpd() { + _mpd_install_os_dependencies _mpd_configure - # Prepare user-service MPD to be started at next boot - systemctl --user daemon-reload - systemctl --user enable mpd.socket - systemctl --user enable mpd - # Start MPD now, but not the socket: MPD is already started and we expect a reboot anyway - systemctl --user start mpd - fi - - echo "DONE: setup_mpd" + _mpd_check +} + +setup_mpd() { + # Install/update only if enabled: do not stuff up any existing configuration + if [[ "$SETUP_MPD" == true && $ENABLE_MPD_OVERWRITE_INSTALL == true ]] ; then + run_with_log_frame _run_setup_mpd "Install MPD" + fi } diff --git a/installation/routines/setup_rfid_reader.sh b/installation/routines/setup_rfid_reader.sh index 4ae693076..b5e8b4bbc 100644 --- a/installation/routines/setup_rfid_reader.sh +++ b/installation/routines/setup_rfid_reader.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash -setup_rfid_reader() { - echo "Install RFID Reader" | tee /dev/fd/3 - - python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" | tee /dev/fd/3 +_run_setup_rfid_reader() { + python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" | tee /dev/fd/3 +} - echo "DONE: setup_rfid_reader" +setup_rfid_reader() { + if [ "$ENABLE_RFID_READER" == true ] ; then + run_with_log_frame _run_setup_rfid_reader "Install RFID Reader" + fi } diff --git a/installation/routines/setup_samba.sh b/installation/routines/setup_samba.sh index 0914439b7..4b45b54e1 100644 --- a/installation/routines/setup_samba.sh +++ b/installation/routines/setup_samba.sh @@ -1,7 +1,10 @@ #!/usr/bin/env bash +SMB_CONF="/etc/samba/smb.conf" +SMB_CONF_HEADER="## Jukebox Samba Config" + _samba_install_os_dependencies() { - echo "Install Samba Core dependencies" + echo " Install Samba Core dependencies" sudo apt-get -qq -y update; sudo apt-get -qq -y install \ samba samba-common-bin \ --no-install-recommends \ @@ -11,23 +14,22 @@ _samba_install_os_dependencies() { } _samba_set_user() { - local SMB_CONF="/etc/samba/smb.conf" - local SMB_USER="pi" + echo " Configure Samba" | tee /dev/fd/3 local SMB_PASSWD="raspberry" # Samba has not been configured - if grep -q "## Jukebox Samba Config" "$SMB_CONF"; then - echo " Skipping. Already set up!" | tee /dev/fd/3 + if grep -q "$SMB_CONF_HEADER" "$SMB_CONF"; then + echo " Skipping. Already set up!" | tee /dev/fd/3 else # Create Samba user - (echo "${SMB_PASSWD}"; echo "${SMB_PASSWD}") | sudo smbpasswd -s -a $SMB_USER + (echo "${SMB_PASSWD}"; echo "${SMB_PASSWD}") | sudo smbpasswd -s -a "${CURRENT_USER}" sudo chown root:root $SMB_CONF sudo chmod 777 $SMB_CONF # Create Samba Mount Points sudo cat << EOF >> $SMB_CONF -## Jukebox Samba Config +${SMB_CONF_HEADER} [phoniebox] comment= Pi Jukebox path=${SHARED_PATH} @@ -43,13 +45,31 @@ EOF fi } -setup_samba() { - echo "Install Samba and configure user" | tee /dev/fd/3 +_samba_check() { + print_verify_installation + + verify_apt_packages samba samba-common-bin - # Skip interactive Samba WINS config dialog - echo "samba-common samba-common/dhcp boolean false" | sudo debconf-set-selections - _samba_install_os_dependencies - _samba_set_user + verify_files_chmod_chown 644 root root "${SMB_CONF}" - echo "DONE: setup_samba" + verify_file_contains_string "${SMB_CONF_HEADER}" "${SMB_CONF}" + verify_file_contains_string "${SHARED_PATH}" "${SMB_CONF}" + + if ! (sudo pdbedit -L | grep -qw "^${CURRENT_USER}") ; then + exit_on_error "ERROR: samba user not found" + fi +} + +_run_setup_samba() { + # Skip interactive Samba WINS config dialog + echo "samba-common samba-common/dhcp boolean false" | sudo debconf-set-selections + _samba_install_os_dependencies + _samba_set_user + _samba_check +} + +setup_samba() { + if [ "$ENABLE_SAMBA" == true ] ; then + run_with_log_frame _run_setup_samba "Install Samba" + fi } diff --git a/installation/routines/update_raspi_os.sh b/installation/routines/update_raspi_os.sh index b7c356454..f38e975ed 100644 --- a/installation/routines/update_raspi_os.sh +++ b/installation/routines/update_raspi_os.sh @@ -1,9 +1,14 @@ #!/usr/bin/env bash -update_raspi_os() { - echo "Updating Raspberry Pi OS" | tee /dev/fd/3 - - sudo apt-get -qq -y update; sudo apt-get -qq -y full-upgrade; sudo apt-get -qq -y autoremove +_run_update_raspi_os() { + sudo apt-get -qq -y update && sudo apt-get -qq -y full-upgrade || exit_on_error "Failed to Update Raspberry Pi OS" + if [ "$CI_RUNNING" != "true" ]; then + sudo apt-get -qq -y autoremove + fi +} - echo "DONE: update_raspi_os" +update_raspi_os() { + if [ "$UPDATE_RASPI_OS" == true ] ; then + run_with_log_frame _run_update_raspi_os "Updating Raspberry Pi OS" + fi } diff --git a/packages-core.txt b/packages-core.txt new file mode 100644 index 000000000..b2f6779a2 --- /dev/null +++ b/packages-core.txt @@ -0,0 +1,17 @@ +# Define packages for apt-get. These can be installed with +# 'sed 's/#.*//g' packages.txt | xargs sudo apt-get install' + +at +alsa-utils +caps +espeak +ffmpeg +libasound2-dev +mpg123 +pulseaudio +pulseaudio-module-bluetooth +pulseaudio-utils +python3 +python3-venv +python3-dev +rsync diff --git a/requirements.txt b/requirements.txt index f9f452799..0f8c8c86d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ wheel evdev pyalsaaudio pulsectl -python_mpd2 +python-mpd2 ruamel.yaml # For playlistgenerator requests @@ -28,7 +28,6 @@ gpiozero # On regular Linux PCs, Websocket is enabled in the Python package # pyzmq - # Code quality flake8>=4.0.0 pytest diff --git a/src/jukebox/components/rfid/configure/__init__.py b/src/jukebox/components/rfid/configure/__init__.py index 7f9e232f2..0a8ff7aca 100755 --- a/src/jukebox/components/rfid/configure/__init__.py +++ b/src/jukebox/components/rfid/configure/__init__.py @@ -10,6 +10,8 @@ logger = logging.getLogger() +NO_RFID_READER = 'No RFID Reader' + def reader_install_dependencies(reader_path: str, dependency_install: str) -> None: """ @@ -80,6 +82,40 @@ def reader_load_module(reader_name): return reader_module +def _get_reader_descriptions(reader_dirs: list[str]) -> dict[str, tuple[str, str]]: + # Try to load the description modules from all valid directories (as this has no dependencies) + # If unavailable, use placeholder description + reader_descriptions = {} + for reader_type in reader_dirs: + reader_description_module_name = '' + reader_description = '' + if reader_type == NO_RFID_READER: + # Add Option to not add a RFid Reader + reader_description_module_name = reader_type + reader_description = reader_type + else: + reader_description_module_name = f"{reader_type + '/' + reader_type + '.py'}" + try: + reader_description_module = (importlib.import_module('components.rfid.hardware.' + reader_type + + '.description', 'pkg.subpkg')) + reader_description = reader_description_module.DESCRIPTION + except ModuleNotFoundError: + # The developer for this reader simply omitted to provide a description module + # Or there is no valid module in this directory, despite correct naming scheme. + # But this we will only find out later, because we want to be as lenient as possible + # and don't already load and check reader modules the user is + # not selecting (and thus no interested in) + logger.warning(f"No module 'description.py' available for reader subpackage '{reader_type}'") + reader_description = '(No description provided!)' + except AttributeError: + # The module loaded ok, but has no identifier 'DESCRIPTION' + logger.warning(f"Module 'description.py' of reader subpackage '{reader_type}' is missing 'DESCRIPTION'. " + f"Spelling error?") + reader_description = '(No description provided!)' + reader_descriptions[reader_type] = (reader_description, reader_description_module_name) + return reader_descriptions + + def query_user_for_reader(dependency_install='query') -> dict: """ Ask the user to select a RFID reader and prompt for the reader's configuration @@ -115,39 +151,20 @@ def query_user_for_reader(dependency_install='query') -> dict: package_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/../hardware') logger.debug(f"Package location: {package_dir}") # For known included readers, specify manual order - included_readers = ['generic_usb', 'rdm6300_serial', 'rc522_spi', 'pn532_i2c_py532', 'fake_reader_gui'] + included_readers = [NO_RFID_READER, 'generic_usb', 'rdm6300_serial', 'rc522_spi', 'pn532_i2c_py532', 'fake_reader_gui'] # Get all local directories (i.e subpackages) that conform to naming/structuring convention (except known readers) # Naming convention: modname/modname.py - reader_dirs = [x for x in os.listdir(package_dir) + additional_readers = [x for x in os.listdir(package_dir) if (os.path.isdir(package_dir + '/' + x) and os.path.exists(package_dir + '/' + x + '/' + x + '.py') and os.path.isfile(package_dir + '/' + x + '/' + x + '.py') and not x.endswith('template_new_reader') and x not in included_readers)] - reader_dirs = [*included_readers, *sorted(reader_dirs, key=lambda x: x.casefold())] + reader_dirs = [*included_readers, *sorted(additional_readers, key=lambda x: x.casefold())] + logger.debug(f"reader_dirs = {reader_dirs}") - # Try to load the description modules from all valid directories (as this has no dependencies) - # If unavailable, use placeholder description - reader_description_modules = [] - reader_descriptions = [] - for reader_type in reader_dirs: - try: - reader_description_modules.append(importlib.import_module('components.rfid.hardware.' + reader_type - + '.description', 'pkg.subpkg')) - reader_descriptions.append(reader_description_modules[-1].DESCRIPTION) - except ModuleNotFoundError: - # The developer for this reader simply omitted to provide a description module - # Or there is no valid module in this directory, despite correct naming scheme. But this we will only find out - # later, because we want to be as lenient as possible and don't already load and check reader modules the user is - # not selecting (and thus no interested in) - logger.warning(f"No module 'description.py' available for reader subpackage '{reader_type}'") - reader_descriptions.append('(No description provided!)') - except AttributeError: - # The module loaded ok, but has no identifier 'DESCRIPTION' - logger.warning(f"Module 'description.py' of reader subpackage '{reader_type}' is missing 'DESCRIPTION'. " - f"Spelling error?") - reader_descriptions.append('(No description provided!)') + reader_descriptions = _get_reader_descriptions(reader_dirs) # Prepare the configuration collector with the base values config_dict = {'rfid': {'readers': {}}} @@ -157,14 +174,21 @@ def query_user_for_reader(dependency_install='query') -> dict: while True: # List all modules and query user print("Choose Reader Module from list:\n") - for idx, (des, mod) in enumerate(zip(reader_descriptions, reader_dirs)): + for idx, (des, mod) in enumerate(reader_descriptions.values()): print(f" {Colors.lightgreen}{idx:2d}{Colors.reset}: {Colors.lightcyan}{Colors.bold}{des:40s}{Colors.reset} " - f"(Module: {mod + '/' + mod + '.py'})") + f"(Module: {mod})") print("") reader_id = pyil.input_int("Reader module number?", min=0, max=len(reader_descriptions) - 1, prompt_color=Colors.lightgreen, prompt_hint=True) + # The (short) name of the selected reader module, which is identical to the directory name - reader_select_name.append(reader_dirs[reader_id]) + reader_selected = list(reader_descriptions.keys())[reader_id] + print(f"Reader selected: '{reader_selected}'") + if reader_selected == NO_RFID_READER: + logger.debug(f"Entry '{NO_RFID_READER}' selected. skip") + break + + reader_select_name.append(reader_selected) # If this reader has not been selected before, auto install dependencies if reader_select_name[-1] not in reader_select_name[:-1]: diff --git a/src/jukebox/components/rfid/reader/__init__.py b/src/jukebox/components/rfid/reader/__init__.py index 9245a127a..db0ccb1da 100644 --- a/src/jukebox/components/rfid/reader/__init__.py +++ b/src/jukebox/components/rfid/reader/__init__.py @@ -239,14 +239,22 @@ def run(self): # noqa: C901 @plugs.finalize def finalize(): - jukebox.cfghandler.load_yaml(cfg_rfid, cfg_main.getn('rfid', 'reader_config')) - - # Load all the required modules - # Start a ReaderRunner-Thread for each Reader - for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): - _READERS[reader_cfg_key] = ReaderRunner(reader_cfg_key) - for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): - _READERS[reader_cfg_key].start() + try: + reader_config_file = cfg_main.getn('rfid', 'reader_config') + jukebox.cfghandler.load_yaml(cfg_rfid, reader_config_file) + except FileNotFoundError: + cfg_rfid.config_dict({'rfid': {'readers': {}}}) + log.warning(f"rfid reader database file not found. Creating empty database: '{reader_config_file}'") + # Save the empty rfid reader database, to make sure we can create the file and have access to it + cfg_rfid.save(only_if_changed=False) + + if 'rfid' in cfg_rfid and 'readers' in cfg_rfid['rfid']: + # Load all the required modules + # Start a ReaderRunner-Thread for each Reader + for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): + _READERS[reader_cfg_key] = ReaderRunner(reader_cfg_key) + for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): + _READERS[reader_cfg_key].start() @plugs.atexit From dbfd58ab7beff1907317534ecc6ed77ae6b6fc54 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sun, 3 Dec 2023 13:18:03 +0100 Subject: [PATCH 028/121] Bump libzmq version to 4.3.5 for armv7 --- installation/routines/setup_jukebox_core.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index e6dfa6d8f..272901de1 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Constants -GD_ID_COMPILED_LIBZMQ_ARMV7="1KP6BqLF-i2dCUsHhOUpOwwuOmKsB5GKY" # ARMv7: https://drive.google.com/file/d/1KP6BqLF-i2dCUsHhOUpOwwuOmKsB5GKY/view?usp=sharing +GD_ID_COMPILED_LIBZMQ_ARMV7="1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F" # ARMv7: https://drive.google.com/file/d/1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F/view?usp=sharing GD_ID_COMPILED_LIBZMQ_ARMV6="1iygOm-G1cg_3YERuVRT6FhGBE34ZkwgV" # ARMv6: https://drive.google.com/file/d/1iygOm-G1cg_3YERuVRT6FhGBE34ZkwgV/view?usp=sharing GD_ID_COMPILED_PYZMQ_ARMV7="" GD_ID_COMPILED_PYZMQ_ARMV6="1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n" # https://drive.google.com/file/d/1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n/view?usp=sharing From f1558a2f2cc9d4c9d5adc6d9bf6956c0e72a51bf Mon Sep 17 00:00:00 2001 From: s-martin Date: Mon, 4 Dec 2023 08:20:55 +0100 Subject: [PATCH 029/121] Add badges to future3 (#2133) * Add badges to future3 * Add python action badge --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 3731c7e06..1ada3e8b3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # RFID Jukebox Version 3 (aka future3) +![GitHub last commit (branch)](https://img.shields.io/github/last-commit/MiczFlor/RPi-Jukebox-RFID/future3/develop) + +[![Test Install Scripts Debian v3](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml) [![Python + Docs Checks and Tests](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml) + +[![Matrix chat](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#phoniebox_community:gitter.im) + ## What is this? The exiting, new **Version 3** of the RPi Jukebox RFID. A complete re-write of the Jukebox. From cf5aa6c92de88396d00a576f6497a6944138c1ab Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Mon, 4 Dec 2023 23:21:24 +0100 Subject: [PATCH 030/121] allow custom username (#2132) * removed hardcoded references to user "pi" * removed check userNotPi * change testuser in workflow and container * updated docs * updated comments and docs * updated comments and docs * remove extra home_path check * changes testuser name and group --- .github/ISSUE_TEMPLATE/bug_template.md | 2 +- .../test_docker_debian_codename_sub_v3.yml | 14 ++++++---- CONTRIBUTING.md | 6 ++-- ci/ci-debian.Dockerfile | 18 ++---------- ci/installation/run_install_user_not_pi.sh | 25 ----------------- documentation/builders/autohotspot.md | 2 +- documentation/builders/installation.md | 4 +-- documentation/builders/system.md | 7 ++--- documentation/developers/known-issues.md | 4 +-- installation/install-jukebox.sh | 28 ------------------- installation/routines/setup_autohotspot.sh | 5 +++- installation/routines/setup_jukebox_webapp.sh | 1 + resources/autohotspot/autohotspot.timer | 4 +-- .../default-settings/jukebox.default.yaml | 2 +- resources/default-settings/nginx.default | 8 +++--- src/webapp/src/config.js | 4 +-- 16 files changed, 36 insertions(+), 98 deletions(-) delete mode 100644 ci/installation/run_install_user_not_pi.sh diff --git a/.github/ISSUE_TEMPLATE/bug_template.md b/.github/ISSUE_TEMPLATE/bug_template.md index 1be942778..508cf50b9 100644 --- a/.github/ISSUE_TEMPLATE/bug_template.md +++ b/.github/ISSUE_TEMPLATE/bug_template.md @@ -50,7 +50,7 @@ Otherwise the output of `cat /etc/os-release` i.e. `master` the following command will help with that -`cd /home/pi/RPi-Jukebox-RFID/ && git status | head -2` +`cd ~/RPi-Jukebox-RFID/ && git status | head -2` --> ### Installscript diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml index 54abd142a..97924c4bf 100644 --- a/.github/workflows/test_docker_debian_codename_sub_v3.yml +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -27,6 +27,10 @@ on: type: string default: ubuntu-latest +env: + TEST_USER_NAME: testuser + TEST_USER_GROUP: testusergroup + # let only one instance run the test so cache is not corrupted. # cancel already running instances as only the last run will be relevant concurrency: @@ -101,6 +105,8 @@ jobs: cache-to: type=gha,mode=max,scope=${{ steps.vars.outputs.cache_scope }} build-args: | DEBIAN_CODENAME=${{ inputs.debian_codename }} + USER_NAME=${{ env.TEST_USER_NAME }} + USER_GROUP=${{ env.TEST_USER_GROUP }} GIT_BRANCH=${{ github.head_ref || github.ref_name }} GIT_USER=${{ github.event.pull_request.head.user.login || github.repository_owner }} @@ -137,11 +143,7 @@ jobs: strategy: fail-fast: false matrix: - username: ['pi'] test_script: ['run_install_common.sh', 'run_install_faststartup.sh', 'run_install_webapp_local.sh', 'run_install_webapp_download.sh'] - include: - - username: hans - test_script: run_install_user_not_pi.sh steps: - name: Set up QEMU @@ -160,11 +162,11 @@ jobs: docker load --input ${{ needs.build.outputs.image_file_name }} # Run test - - name: Run Test ${{ inputs.debian_codename }}-${{ matrix.username }}-${{ matrix.test_script }} + - name: Run Test ${{ inputs.debian_codename }}-${{ env.TEST_USER_NAME }}-${{ matrix.test_script }} uses: tj-actions/docker-run@v2 with: image: ${{ needs.build.outputs.image_tag_name }} - options: --platform ${{ inputs.platform }} --user ${{ matrix.username }} --init + options: --platform ${{ inputs.platform }} --user ${{ env.TEST_USER_NAME }} --init name: ${{ matrix.test_script }} args: | ./${{ matrix.test_script }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3bc03705..dbf12f84d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ For bug fixes and improvements just open an issue or PR as described below. If y * By default this will get you to the `future3/main` branch. You will move to the `future3/develop` branch, do this: ~~~bash -cd /home/pi/RPi-Jukebox-RFID +cd ~/RPi-Jukebox-RFID git checkout future3/develop git fetch origin git reset --hard origin/future3/develop @@ -122,7 +122,7 @@ If you touched *any* Python file (even if only for fixing spelling errors), run It contains out setup file. ~~~bash -cd /home/pi/RPi-Jukebox-RFID +cd ~/RPi-Jukebox-RFID ./run_flake8.sh ~~~ @@ -135,7 +135,7 @@ Tests are very few at the moment, but it cannot hurt to run them. If you have te them. ~~~bash -cd /home/pi/RPi-Jukebox-RFID/ +cd ~/RPi-Jukebox-RFID/ ./run_pytest.sh ~~~ diff --git a/ci/ci-debian.Dockerfile b/ci/ci-debian.Dockerfile index 1a227755a..5228d83d5 100644 --- a/ci/ci-debian.Dockerfile +++ b/ci/ci-debian.Dockerfile @@ -7,7 +7,7 @@ ARG DEBIAN_CODENAME ENV TERM=xterm DEBIAN_FRONTEND=noninteractive ENV CI_RUNNING=true -# create pi configs to test installation +# create RPi configs to test installation RUN touch /boot/config.txt RUN echo "logo.nologo" > /boot/cmdline.txt @@ -40,8 +40,8 @@ RUN echo "--- install packages (1) ---" \ RUN echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections # ------ -# Base Target for setting up the default user. user can be selected with the docker '--user YYY' option -FROM base as user +# Base Target for setting up a test user. user can be selected with the docker '--user YYY' option +FROM base as test-user ARG USER_NAME=pi ARG USER_GROUP=$USER_NAME ARG USER_ID=1000 @@ -56,18 +56,6 @@ RUN groupadd --gid 1000 $USER_GROUP \ ENV XDG_RUNTIME_DIR=/run/user/$USER_ID DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$USER_ID/bus # ------ - -# Target for setting up an alternativ user 'hans:wurst'. user can be selected with the docker '--user YYY' option -FROM user as test-user - -RUN export USER_ALT=hans \ - && export USER_ALT_GROUP=wurst \ - && groupadd --gid 1001 $USER_ALT_GROUP \ - && useradd -u 1001 -g $USER_ALT_GROUP -G sudo,$TEST_USER_GROUP -d /home/$USER_ALT -m -s /bin/bash -p '$1$iV7TOwOe$6ojkJQXyEA9bHd/SqNLNj0' $USER_ALT \ - && echo "$USER_ALT ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER_ALT -# ------ - - # Target for adding envs and scripts from the repo to test installation FROM test-user as test-code ARG GIT_BRANCH diff --git a/ci/installation/run_install_user_not_pi.sh b/ci/installation/run_install_user_not_pi.sh deleted file mode 100644 index 76a8cd576..000000000 --- a/ci/installation/run_install_user_not_pi.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -# Install Phoniebox and test it -# Used e.g. for tests on Docker - -# Objective: Test installation with script using a simple configuration - -SOURCE="${BASH_SOURCE[0]}" -SCRIPT_DIR="$(dirname "$SOURCE")" -LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" -LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" - -# Run installation (in interactive mode) -# - - Installation must abort early - -"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" -INSTALLATION_EXITCODE=$? - -# only count abortion due to "not user pi" as success -if [ "${INSTALLATION_EXITCODE}" -eq 2 ]; then - INSTALLATION_EXITCODE=0 -else - INSTALLATION_EXITCODE=1 -fi -exit "${INSTALLATION_EXITCODE}" diff --git a/documentation/builders/autohotspot.md b/documentation/builders/autohotspot.md index 7f064cb53..69e6f4d6a 100644 --- a/documentation/builders/autohotspot.md +++ b/documentation/builders/autohotspot.md @@ -28,7 +28,7 @@ After connecting to the `Phoniebox_Hotspot` you are able to connect via ssh to your Jukebox ``` bash -ssh pi@10.0.0.5 +ssh @10.0.0.5 ``` ## Changing basic configuration of the hotspot diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index d0befca29..fadc77aac 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -16,7 +16,7 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr * Click `Edit Settings` * Switch to the `General` tab * Provide a hostname. (When on Mac, you will be able to use it to connect via SSH) - * Username currently MUST be `pi`. Other usernames are currently not supported. + * Username * Password * Wifi * Set locale settings @@ -72,7 +72,7 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to 7. Eject your SD card and insert it into your Raspberry Pi. 8. Start your Raspberry Pi by attaching a power supply. -9. Login into your Raspberry Pi, username is `pi` and password is `raspberry`. +9. Login into your Raspberry Pi If `raspberrypi.local` does not work, find out your Raspberry Pi's IP address from your router.
diff --git a/documentation/builders/system.md b/documentation/builders/system.md index 5b1ce5c0c..873d489f6 100644 --- a/documentation/builders/system.md +++ b/documentation/builders/system.md @@ -10,9 +10,8 @@ The system consists of 4. [Web UI](system.md#web-ui) which is served through an Nginx web server 5. A set of [Configuration Tools](../developers/coreapps.md#configuration-tools) and a set of [Developer Tools](../developers/coreapps.md#developer-tools) -.. note:: The default install puts everything into the folder `/home/pi/RPi-Jukebox-RFID`. - Another folder might work, but is certainly not tested. Things are installed for the default user `pi`. Again, - another user might work, but is not tested. +.. note:: The default install puts everything into the users home folder `~/RPi-Jukebox-RFID`. + Another folder might work, but is certainly not tested. ## Music Player Daemon (MPD) @@ -102,7 +101,7 @@ Starting and stopping the service can be useful for debugging or configuration c The Web UI is served using nginx. Nginx runs as a system service. The home directory is localed at ``` -/home/pi/RPi-Jukebox-RFID/src/webapp/build +~/RPi-Jukebox-RFID/src/webapp/build ``` The Nginx configuration is located at diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index 598ecd791..2ec52c3c2 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -7,7 +7,7 @@ browser for now. ## Configuration -In `jukebox.yaml` (and all other config files): do not use relative paths with `~/some/dir`. -Always use entire explicit path, e.g. `/home/pi/some/dir`. +In `jukebox.yaml` (and all other config files): +Always use relative path from settingsfile `../../`, but do not use relative paths with `~/`. **Sole** exception is in `playermpd.mpd_conf`. diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index b72dd32ff..90f78800f 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -47,33 +47,6 @@ _check_os_type() { fi } -# currently the user 'pi' is mandatory -# https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1785 -_check_user() { - if [ "${CURRENT_USER}" != "pi" ]; then - echo - echo "ERROR: User must be 'pi'!" - echo " Other usernames are currently not supported." - echo " Please check the wiki for further information" - exit 2 - fi - - if [ "${HOME_PATH}" != "/home/pi" ]; then - echo - echo "ERROR: HomeDir must be '/home/pi'!" - echo " Other usernames are currently not supported." - echo " Please check the wiki for further information" - exit 2 - fi - - if [ ! -d "${HOME_PATH}" ]; then - echo - echo "ERROR: HomeDir ${HOME_PATH} does not exist." - echo " Please create it and start again." - exit 2 - fi -} - # Manipulate file descriptor for logging # Behavior: # Write To logfile: @@ -154,7 +127,6 @@ _load_sources() { ### CHECK PREREQUISITE _check_os_type -_check_user ### SETUP LOGGING _setup_logging diff --git a/installation/routines/setup_autohotspot.sh b/installation/routines/setup_autohotspot.sh index a385352dd..cc40b7794 100644 --- a/installation/routines/setup_autohotspot.sh +++ b/installation/routines/setup_autohotspot.sh @@ -68,7 +68,10 @@ _other_configuration() { _install_service_and_timer() { sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.service /etc/systemd/system/autohotspot.service sudo systemctl enable autohotspot.service - sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.timer /etc/cron.d/autohotspot + + local cron_autohotspot_file="/etc/cron.d/autohotspot" + sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.timer "${cron_autohotspot_file}" + sudo sed -i "s|%%USER%%|${CURRENT_USER}|g" "${cron_autohotspot_file}" } _install_autohotspot_script() { diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index df58188fb..54c573654 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -72,6 +72,7 @@ _jukebox_webapp_register_as_system_service_with_nginx() { sudo mv -f "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}.orig" sudo cp -f "${INSTALLATION_PATH}/resources/default-settings/nginx.default" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" + sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" # make sure nginx can access the home directory of the user sudo chmod o+x "${HOME_PATH}" diff --git a/resources/autohotspot/autohotspot.timer b/resources/autohotspot/autohotspot.timer index a35f15757..eb2acaebe 100644 --- a/resources/autohotspot/autohotspot.timer +++ b/resources/autohotspot/autohotspot.timer @@ -1,3 +1,3 @@ # cron timer for autohotspot -*/5 * * * * pi sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot -@reboot pi sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot +*/5 * * * * %%USER%% sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot +@reboot %%USER%% sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot diff --git a/resources/default-settings/jukebox.default.yaml b/resources/default-settings/jukebox.default.yaml index 6e565c684..91ada74c6 100644 --- a/resources/default-settings/jukebox.default.yaml +++ b/resources/default-settings/jukebox.default.yaml @@ -1,5 +1,5 @@ # IMPORTANT: -# Do not use paths with '~/some/dir' - always use '/home/pi/some/dir' +# Always use relative path from settingsfile `../../`, but do not use relative paths with `~/`. # Sole (!) exception is in playermpd.mpd_conf system: box_name: Jukebox diff --git a/resources/default-settings/nginx.default b/resources/default-settings/nginx.default index b949beb26..d664f4cdd 100644 --- a/resources/default-settings/nginx.default +++ b/resources/default-settings/nginx.default @@ -2,7 +2,7 @@ server { listen 80 default_server; listen [::]:80 default_server; - root /home/pi/RPi-Jukebox-RFID/src/webapp/build; + root %%INSTALLATION_PATH%%/src/webapp/build; index index.html index.htm; @@ -21,7 +21,7 @@ server { } location /logs { - root /home/pi/RPi-Jukebox-RFID/shared; + root %%INSTALLATION_PATH%%/shared; autoindex on; autoindex_exact_size off; @@ -31,14 +31,14 @@ server { } location @buildwebui { - root /home/pi/RPi-Jukebox-RFID/resources/html; + root %%INSTALLATION_PATH%%/resources/html; try_files /runbuildui.html =404; internal; } error_page 404 = /404.html; location /404.html { - root /home/pi/RPi-Jukebox-RFID/resources/html; + root %%INSTALLATION_PATH%%/resources/html; internal; } } diff --git a/src/webapp/src/config.js b/src/webapp/src/config.js index 4383a7897..35b5a980e 100644 --- a/src/webapp/src/config.js +++ b/src/webapp/src/config.js @@ -17,9 +17,7 @@ const SUBSCRIPTIONS = [ 'volume.level', ]; -// TODO: This is not optimal, we should not know about this path here! -// Let's try to work with relatives paths in the RPC only! -const DEFAULT_AUDIO_DIR = '/home/pi/RPi-Jukebox-RFID/shared/audiofolders'; +const DEFAULT_AUDIO_DIR = '../../shared/audiofolders'; const ROOT_DIRS = ['./', DEFAULT_AUDIO_DIR]; From 01ec0bca88c974f672175562b5fcbb6d60d219be Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 5 Dec 2023 08:20:39 +0100 Subject: [PATCH 031/121] Allow download of prebuild libzmq-v4.3.5 for Docker --- docker/jukebox.Dockerfile | 17 ++++-- documentation/developers/known-issues.md | 15 ++++- documentation/developers/libzmq.md | 68 +++++++++++++++++++++ installation/routines/setup_jukebox_core.sh | 6 +- 4 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 documentation/developers/libzmq.md diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index 5e7a08915..44c66cc24 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -1,7 +1,5 @@ FROM debian:bullseye-slim -# Prepare Raspberry Pi like environment - # These are only dependencies that are required to get as close to the # Raspberry Pi environment as possible. RUN apt-get update && apt-get install -y \ @@ -25,7 +23,7 @@ RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ gcc at wget \ espeak mpc mpg123 git ffmpeg spi-tools netcat \ - python3 python3-venv python3-dev python3-mutagen + python3 python3-venv python3-dev python3-mutagen build-essential ENV VIRTUAL_ENV=${INSTALLATION_PATH}/.venv RUN python3 -m venv $VIRTUAL_ENV @@ -36,7 +34,18 @@ WORKDIR ${HOME} COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt -RUN pip install pyzmq + +ENV ZMQ_TMP_DIR libzmq +ENV ZMQ_VERSION 4.3.5 +ENV ZMQ_PREFIX /usr/local + +RUN [ "$(uname -m)" = "aarch64" ] && ARCH="arm64" || ARCH="$(uname -m)"; \ + wget https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ + tar -xzf libzmq.tar.gz -C ${ZMQ_PREFIX}; \ + rm -f libzmq.tar.gz; + +RUN export ZMQ_PREFIX=${PREFIX} && export ZMQ_DRAFT_API=1 +RUN pip install -v --no-binary pyzmq --pre pyzmq EXPOSE 5555 5556 diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index 598ecd791..aec5c027b 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -1,9 +1,18 @@ # Known Issues -## Browsers +## Installing limzmq in Docker fails -The Web UI will **not** work with Firefox, due to an issue with websockets and pyzmq. Please use a different -browser for now. +To speed up the Docker build process, we are distributing pre-build versions of libzmq with drafts flag at the latest version. In case the download fails because the respective architecture build does not exist, you can build the version yourself. Just replace the command to download the pre-built library with the following command + +``` +# Compile ZMQ +RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ + wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ + tar -xzf libzmq.tar.gz; \ + rm -f libzmq.tar.gz; \ + zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts; \ + make && make install +``` ## Configuration diff --git a/documentation/developers/libzmq.md b/documentation/developers/libzmq.md new file mode 100644 index 000000000..f4a58f550 --- /dev/null +++ b/documentation/developers/libzmq.md @@ -0,0 +1,68 @@ +# `libzmq` for Raspberry Pi + +## `libzmp` Releases + +The Jukebox requires `libzmq` to work properly. We provide downloadable builds to speed up the installation process of the Phoniebox. + +* https://github.com/pabera/libzmq/releases + +> [!NOTE] +> We can't use stable builds that are distributed by zeromq directly because the Jukebox requires draft builds to support WebSockets. These draft builds are not officially provided through zeromq for Raspberry Pi architecture (e.g. `armv6` or `armv7`). + +## Building `libzmp` + +If you need to update the `libzmq` version in the future, follow these steps. + +### Install Cross-Compilation Environment + +First, you need to install Dockcross. Dockcross provides Docker images for cross-compilation. + +1. **Pull the Dockcross Image**: For Raspberry Pi B, 4 or Zero 2 we need `linux-armv7`, for older models `linux-armv6`. The following example shows how to compile for `armv7` (32 bit, `arm32v7`). If you want to compile for another target, change the commands accordingly. For Docker Development environments, other targets like `arm64` or `x86_64` become relevant. + +```bash +docker pull dockcross/linux-armv7 +``` + +3. **Create a Dockcross Script**: After pulling the image, you create a Dockcross script which will be used to run the cross-compilation tools in the Docker container. + +```bash +docker run --rm dockcross/linux-armv7 > ./dockcross-linux-armv7 +chmod +x ./dockcross-linux-armv7 +``` + +This command creates a script named `dockcross-linux-armv7` in your current directory. + +### Cross-Compiling libzmq + +With Dockcross installed, you can now modify your `libzmq` compilation process to use the Dockcross environment. + +1. **Download `libzmq` Source**: Similar to your original process, download the `libzmq` source code: + +```bash +ZMQ_VERSION=4.3.5 +wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz +tar -xzf libzmq.tar.gz +``` + +2. **Cross-Compilation using Dockcross**: + +Modify your build process to run inside the Dockcross environment: + +```bash +./dockcross-linux-armv7 bash -c '\ +cd zeromq-${ZMQ_VERSION} && \ +./configure --prefix=/usr/local --enable-drafts && \ +make -j$(nproc) && \ +make install DESTDIR=$(pwd)/../installed' +``` + +> [!NOTE] +> In the script above, you need to update ${ZMQ_VERSION} to the actual value as the script does not jave access to your host machine ENV variables. + +This command configures and builds `libzmq` inside the Docker container. The `DESTDIR` variable is used to specify where to install the files inside the container. + +3. **Compress the Compiled Binaries**: After compilation, the binaries are located in the `installed` directory inside your `zeromq-${ZMQ_VERSION}` directory. + +``` +tar -czvf libzmq5-armv7-${ZMQ_VERSION}.tar.gz -C installed/usr/local --exclude='.' include lib +``` diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 272901de1..2ef0a0a04 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # Constants -GD_ID_COMPILED_LIBZMQ_ARMV7="1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F" # ARMv7: https://drive.google.com/file/d/1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F/view?usp=sharing -GD_ID_COMPILED_LIBZMQ_ARMV6="1iygOm-G1cg_3YERuVRT6FhGBE34ZkwgV" # ARMv6: https://drive.google.com/file/d/1iygOm-G1cg_3YERuVRT6FhGBE34ZkwgV/view?usp=sharing -GD_ID_COMPILED_PYZMQ_ARMV7="" +GD_ID_COMPILED_LIBZMQ_ARMV6="17VTgriCsYmAm72YKkga97jO0nExtq6G-" # armv6: https://drive.google.com/file/d/17VTgriCsYmAm72YKkga97jO0nExtq6G-/view?usp=sharing +GD_ID_COMPILED_LIBZMQ_ARMV7="1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F" # armv7: https://drive.google.com/file/d/1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F/view?usp=sharing GD_ID_COMPILED_PYZMQ_ARMV6="1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n" # https://drive.google.com/file/d/1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n/view?usp=sharing +GD_ID_COMPILED_PYZMQ_ARMV7="" ZMQ_TMP_DIR="libzmq" ZMQ_PREFIX="/usr/local" From 8ea49cf49508f389d6ba83d50787539950532e79 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 5 Dec 2023 08:31:40 +0100 Subject: [PATCH 032/121] Remove unnecessary build-essentials from apt-get in Dockerfile --- docker/jukebox.Dockerfile | 2 +- documentation/developers/known-issues.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index 44c66cc24..f842a7394 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -23,7 +23,7 @@ RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ gcc at wget \ espeak mpc mpg123 git ffmpeg spi-tools netcat \ - python3 python3-venv python3-dev python3-mutagen build-essential + python3 python3-venv python3-dev python3-mutagen ENV VIRTUAL_ENV=${INSTALLATION_PATH}/.venv RUN python3 -m venv $VIRTUAL_ENV diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index 32c3e2cd2..25e8b73d2 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -2,7 +2,9 @@ ## Installing limzmq in Docker fails -To speed up the Docker build process, we are distributing pre-build versions of libzmq with drafts flag at the latest version. In case the download fails because the respective architecture build does not exist, you can build the version yourself. Just replace the command to download the pre-built library with the following command +To speed up the Docker build process, we are distributing pre-build versions of libzmq with drafts flag at the latest version. In case the download fails because the respective architecture build does not exist, you can build the version yourself. + +Add `build-essential` to tbe installed additionally with `apt-get`. Additionally, replace the command to download the pre-built library with the following command. ``` # Compile ZMQ @@ -16,7 +18,7 @@ RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ ## Configuration -In `jukebox.yaml` (and all other config files): +In `jukebox.yaml` (and all other config files): Always use relative path from settingsfile `../../`, but do not use relative paths with `~/`. **Sole** exception is in `playermpd.mpd_conf`. From a32b771014d6a9ee666326ec865b86a1f8bc527e Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 5 Dec 2023 08:42:03 +0100 Subject: [PATCH 033/121] Fix a few typos --- documentation/developers/known-issues.md | 2 +- documentation/developers/libzmq.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index 25e8b73d2..cdaf35d4f 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -4,7 +4,7 @@ To speed up the Docker build process, we are distributing pre-build versions of libzmq with drafts flag at the latest version. In case the download fails because the respective architecture build does not exist, you can build the version yourself. -Add `build-essential` to tbe installed additionally with `apt-get`. Additionally, replace the command to download the pre-built library with the following command. +Add `build-essential` to be installed additionally with `apt-get`. Additionally, replace the command to download the pre-built library with the following command. ``` # Compile ZMQ diff --git a/documentation/developers/libzmq.md b/documentation/developers/libzmq.md index f4a58f550..3407d971e 100644 --- a/documentation/developers/libzmq.md +++ b/documentation/developers/libzmq.md @@ -7,7 +7,7 @@ The Jukebox requires `libzmq` to work properly. We provide downloadable builds t * https://github.com/pabera/libzmq/releases > [!NOTE] -> We can't use stable builds that are distributed by zeromq directly because the Jukebox requires draft builds to support WebSockets. These draft builds are not officially provided through zeromq for Raspberry Pi architecture (e.g. `armv6` or `armv7`). +> We can't use stable builds that are distributed by [zeromq](https://github.com/zeromq/libzmq/releases) directly because the Jukebox requires draft builds to support WebSockets. These [draft builds](https://pyzmq.readthedocs.io/en/latest/howto/draft.html) are not officially provided through zeromq for Raspberry Pi architecture (e.g. `armv6` or `armv7`). ## Building `libzmp` From 78eb0c45e5aac46f707ecc862f24e663dcc0b208 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:04:39 +0100 Subject: [PATCH 034/121] Use wget to download pre-built libzmq from github --- documentation/developers/known-issues.md | 2 +- installation/includes/02_helpers.sh | 15 +++++++++++++++ installation/routines/setup_jukebox_core.sh | 18 +++++++++--------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index cdaf35d4f..1460c1cb4 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -1,6 +1,6 @@ # Known Issues -## Installing limzmq in Docker fails +## Installing `libzmq` in Docker fails To speed up the Docker build process, we are distributing pre-build versions of libzmq with drafts flag at the latest version. In case the download fails because the respective architecture build does not exist, you can build the version yourself. diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 4a39ae07e..2da629468 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -34,6 +34,21 @@ run_with_log_frame() { echo "#########################################################" } +get_architecture() { + ARCH="" + if [ "$(uname -m)" = "armv7l" ]; then + ARCH="armv7" + elif [ "$(uname -m)" = "armv6l" ]; then + ARCH="armv6" + elif [ "$(uname -m)" = "aarch64" ]; then + ARCH="arm64" + else + ARCH="$(uname -m)" + fi + + echo $ARCH +} + _download_file_from_google_drive() { GD_SHARING_ID=${1} TAR_FILENAME=${2} diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index dbe5d9115..a62b398ca 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -8,6 +8,7 @@ GD_ID_COMPILED_PYZMQ_ARMV7="" ZMQ_TMP_DIR="libzmq" ZMQ_PREFIX="/usr/local" +ZMQ_VERSION="4.3.5" JUKEBOX_PULSE_CONFIG="${HOME_PATH}"/.config/pulse/default.pa JUKEBOX_SERVICE_NAME="${SYSTEMD_USR_PATH}/jukebox-daemon.service" @@ -52,9 +53,10 @@ _jukebox_core_configure_pulseaudio() { } _jukebox_core_build_libzmq_with_drafts() { - LIBSODIUM_VERSION="1.0.18" - ZMQ_VERSION="4.3.4" + echo " Building libzmq with drafts support" | tee /dev/fd/3 + LIBSODIUM_VERSION="1.0.18" + echo " Building libsodium v${LIBSODIUM_VERSION}" | tee /dev/fd/3 { cd "${HOME_PATH}" && mkdir "${ZMQ_TMP_DIR}" && cd "${ZMQ_TMP_DIR}"; } || exit_on_error wget --quiet https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VERSION}-RELEASE/libsodium-${LIBSODIUM_VERSION}.tar.gz tar -zxvf libsodium-${LIBSODIUM_VERSION}.tar.gz @@ -62,6 +64,7 @@ _jukebox_core_build_libzmq_with_drafts() { ./configure make && make install + echo " Building libzmq v${ZMQ_VERSION}" | tee /dev/fd/3 cd "${HOME}/${ZMQ_TMP_DIR}" || exit_on_error wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz tar -xzf libzmq.tar.gz @@ -71,8 +74,9 @@ _jukebox_core_build_libzmq_with_drafts() { _jukebox_core_download_prebuilt_libzmq_with_drafts() { local ZMQ_TAR_FILENAME="libzmq.tar.gz" + ARCH=$(get_architecture) - _download_file_from_google_drive "${LIBZMQ_GD_DOWNLOAD_ID}" "${ZMQ_TAR_FILENAME}" + wget https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O ${ZMQ_TAR_FILENAME} tar -xzf ${ZMQ_TAR_FILENAME} rm -f ${ZMQ_TAR_FILENAME} sudo rsync -a ./* ${ZMQ_PREFIX}/ @@ -86,7 +90,7 @@ _jukebox_core_build_and_install_pyzmq() { # Sources: # https://pyzmq.readthedocs.io/en/latest/howto/draft.html # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md - echo " Build and install pyzmq with WebSockets Support" | tee /dev/fd/3 + echo " Install pyzmq with WebSockets Support" | tee /dev/fd/3 if ! pip list | grep -F pyzmq >> /dev/null; then # Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it @@ -94,11 +98,7 @@ _jukebox_core_build_and_install_pyzmq() { { cd "${HOME_PATH}" && mkdir "${ZMQ_TMP_DIR}" && cd "${ZMQ_TMP_DIR}"; } || exit_on_error - # ARMv7 as default - LIBZMQ_GD_DOWNLOAD_ID=${GD_ID_COMPILED_LIBZMQ_ARMV7} if [[ $(uname -m) == "armv6l" ]]; then - # ARMv6 as fallback - LIBZMQ_GD_DOWNLOAD_ID=${GD_ID_COMPILED_LIBZMQ_ARMV6} _show_slow_hardware_message fi @@ -109,7 +109,7 @@ _jukebox_core_build_and_install_pyzmq() { fi ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ - pip install --no-cache-dir --no-binary "pyzmq" --pre pyzmq + pip install -v --no-binary pyzmq --pre pyzmq else echo " Skipping. pyzmq already installed" | tee /dev/fd/3 fi From 5a875b87116a3e3151173d1acc271073c30b0a55 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:05:39 +0100 Subject: [PATCH 035/121] Remove unused constants --- installation/routines/setup_jukebox_core.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index a62b398ca..0568feb64 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -1,11 +1,6 @@ #!/usr/bin/env bash # Constants -GD_ID_COMPILED_LIBZMQ_ARMV6="17VTgriCsYmAm72YKkga97jO0nExtq6G-" # armv6: https://drive.google.com/file/d/17VTgriCsYmAm72YKkga97jO0nExtq6G-/view?usp=sharing -GD_ID_COMPILED_LIBZMQ_ARMV7="1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F" # armv7: https://drive.google.com/file/d/1950psO7Mbs4GdWBMqIzdTAZPLnKhDg7F/view?usp=sharing -GD_ID_COMPILED_PYZMQ_ARMV6="1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n" # https://drive.google.com/file/d/1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n/view?usp=sharing -GD_ID_COMPILED_PYZMQ_ARMV7="" - ZMQ_TMP_DIR="libzmq" ZMQ_PREFIX="/usr/local" ZMQ_VERSION="4.3.5" From 54681dabf22de96e2c17dbc37003b123c8091785 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:24:50 +0100 Subject: [PATCH 036/121] Make variable local to its function --- installation/includes/02_helpers.sh | 12 ++++++------ installation/routines/setup_jukebox_core.sh | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 2da629468..1294d5bc6 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -35,18 +35,18 @@ run_with_log_frame() { } get_architecture() { - ARCH="" + local arch="" if [ "$(uname -m)" = "armv7l" ]; then - ARCH="armv7" + arch="armv7" elif [ "$(uname -m)" = "armv6l" ]; then - ARCH="armv6" + arch="armv6" elif [ "$(uname -m)" = "aarch64" ]; then - ARCH="arm64" + arch="arm64" else - ARCH="$(uname -m)" + arch="$(uname -m)" fi - echo $ARCH + echo $arch } _download_file_from_google_drive() { diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 0568feb64..353607c12 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -68,6 +68,7 @@ _jukebox_core_build_libzmq_with_drafts() { } _jukebox_core_download_prebuilt_libzmq_with_drafts() { + echo " Download pre-compiled libzmq" local ZMQ_TAR_FILENAME="libzmq.tar.gz" ARCH=$(get_architecture) @@ -85,12 +86,9 @@ _jukebox_core_build_and_install_pyzmq() { # Sources: # https://pyzmq.readthedocs.io/en/latest/howto/draft.html # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md - echo " Install pyzmq with WebSockets Support" | tee /dev/fd/3 + echo " Install pyzmq with libzmq-drafts to support WebSockets" | tee /dev/fd/3 if ! pip list | grep -F pyzmq >> /dev/null; then - # Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it - echo " Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it" - { cd "${HOME_PATH}" && mkdir "${ZMQ_TMP_DIR}" && cd "${ZMQ_TMP_DIR}"; } || exit_on_error if [[ $(uname -m) == "armv6l" ]]; then From ade28940c9da12e187ec5f58bf9762ebf5e05bd9 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:23:49 +0100 Subject: [PATCH 037/121] Install webapp from release build if Node.JS download is aborted --- installation/routines/customize_options.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index 50df075fe..1590383d1 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -303,6 +303,7 @@ Do you want to install Node? [Y/n]" 1>&3 case "$response" in [nN][oO]|[nN]) ENABLE_INSTALL_NODE=false + ENABLE_WEBAPP_PROD_DOWNLOAD=true ;; *) ;; From c3d08977b93c99733d86fda9c9e839e8231105d4 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:32:30 +0100 Subject: [PATCH 038/121] Use GH release downloads for webapp --- installation/includes/02_helpers.sh | 22 ++++++++++++++----- installation/routines/setup_jukebox_core.sh | 4 ++-- installation/routines/setup_jukebox_webapp.sh | 5 +++-- installation/routines/setup_samba.sh | 2 +- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 1294d5bc6..92d6754e0 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -49,14 +49,24 @@ get_architecture() { echo $arch } -_download_file_from_google_drive() { - GD_SHARING_ID=${1} - TAR_FILENAME=${2} - wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=${GD_SHARING_ID}' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=${GD_SHARING_ID}" -O ${TAR_FILENAME} && rm -rf /tmp/cookies.txt - echo "Downloaded from Google Drive ID ${GD_SHARING_ID} into ${TAR_FILENAME}" +get_version_string() { + local python_file="$1" + local version_major + local version_minor + local version_patch + + version_major=$(grep 'VERSION_MAJOR\s*=\s*[0-9]*' "$python_file" | awk -F= '{print $2}' | tr -d ' ') + version_minor=$(grep 'VERSION_MINOR\s*=\s*[0-9]*' "$python_file" | awk -F= '{print $2}' | tr -d ' ') + version_patch=$(grep 'VERSION_PATCH\s*=\s*[0-9]*' "$python_file" | awk -F= '{print $2}' | tr -d ' ') + + if [ -n "$version_major" ] && [ -n "$version_minor" ] && [ -n "$version_patch" ]; then + local version_string="${version_major}.${version_minor}.${version_patch}" + echo "$version_string" + else + echo "Unable to extract version information from $python_file" + fi } - ### Verify helpers print_verify_installation() { diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 353607c12..efdb6c168 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -61,7 +61,7 @@ _jukebox_core_build_libzmq_with_drafts() { echo " Building libzmq v${ZMQ_VERSION}" | tee /dev/fd/3 cd "${HOME}/${ZMQ_TMP_DIR}" || exit_on_error - wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz + wget --quiet https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz tar -xzf libzmq.tar.gz zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts make && make install @@ -72,7 +72,7 @@ _jukebox_core_download_prebuilt_libzmq_with_drafts() { local ZMQ_TAR_FILENAME="libzmq.tar.gz" ARCH=$(get_architecture) - wget https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O ${ZMQ_TAR_FILENAME} + wget --quiet https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O ${ZMQ_TAR_FILENAME} tar -xzf ${ZMQ_TAR_FILENAME} rm -f ${ZMQ_TAR_FILENAME} sudo rsync -a ./* ${ZMQ_PREFIX}/ diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 54c573654..b7837c29a 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash # Constants -GD_ID_COMPILED_WEBAPP="1um-smyfsVPzVZn18hhwuFt97XR3PjAbB" # https://drive.google.com/file/d/1um-smyfsVPzVZn18hhwuFt97XR3PjAbB/view?usp=sharing WEBAPP_NGINX_SITE_DEFAULT_CONF="/etc/nginx/sites-available/default" # For ARMv7+ @@ -56,9 +55,11 @@ _jukebox_webapp_build() { _jukebox_webapp_download() { echo " Downloading web application" | tee /dev/fd/3 + local JUKEBOX_VERSION==$(get_version_string "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") local TAR_FILENAME="webapp-build.tar.gz" + cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error - _download_file_from_google_drive ${GD_ID_COMPILED_WEBAPP} ${TAR_FILENAME} + wget --quiet ${GIT_URL}/releases/download/v${JUKEBOX_VERSION}/webapp-v${JUKEBOX_VERSION}.tar.gz -O ${TAR_FILENAME} tar -xzf ${TAR_FILENAME} rm -f ${TAR_FILENAME} cd "${INSTALLATION_PATH}" || exit_on_error diff --git a/installation/routines/setup_samba.sh b/installation/routines/setup_samba.sh index 4b45b54e1..100d7370d 100644 --- a/installation/routines/setup_samba.sh +++ b/installation/routines/setup_samba.sh @@ -31,7 +31,7 @@ _samba_set_user() { sudo cat << EOF >> $SMB_CONF ${SMB_CONF_HEADER} [phoniebox] - comment= Pi Jukebox + comment=Pi Jukebox path=${SHARED_PATH} browseable=Yes writeable=Yes From 3ffe8a3598b3f328152aff2bb492d4ca0b12d274 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:55:57 +0100 Subject: [PATCH 039/121] Bugfix: wrong variable assignment --- installation/includes/02_helpers.sh | 11 +++++------ installation/routines/setup_jukebox_webapp.sh | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 92d6754e0..c0a9aa0d1 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -55,20 +55,19 @@ get_version_string() { local version_minor local version_patch - version_major=$(grep 'VERSION_MAJOR\s*=\s*[0-9]*' "$python_file" | awk -F= '{print $2}' | tr -d ' ') - version_minor=$(grep 'VERSION_MINOR\s*=\s*[0-9]*' "$python_file" | awk -F= '{print $2}' | tr -d ' ') - version_patch=$(grep 'VERSION_PATCH\s*=\s*[0-9]*' "$python_file" | awk -F= '{print $2}' | tr -d ' ') + version_major=$(grep 'VERSION_MAJOR\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') + version_minor=$(grep 'VERSION_MINOR\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') + version_patch=$(grep 'VERSION_PATCH\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') if [ -n "$version_major" ] && [ -n "$version_minor" ] && [ -n "$version_patch" ]; then local version_string="${version_major}.${version_minor}.${version_patch}" - echo "$version_string" + echo "Jukebox Version: ${version_string}" else - echo "Unable to extract version information from $python_file" + echo "Unable to extract version information from ${python_file}" fi } ### Verify helpers - print_verify_installation() { echo "" echo " -------------------------------------------------------" diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index b7837c29a..881b27468 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -55,7 +55,7 @@ _jukebox_webapp_build() { _jukebox_webapp_download() { echo " Downloading web application" | tee /dev/fd/3 - local JUKEBOX_VERSION==$(get_version_string "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") + local JUKEBOX_VERSION=$(get_version_string "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") local TAR_FILENAME="webapp-build.tar.gz" cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error From bdb1dd1b7c30260b681508c112b38755c7644002 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Fri, 8 Dec 2023 09:07:00 +0100 Subject: [PATCH 040/121] Static release download url --- installation/routines/setup_jukebox_webapp.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 881b27468..9dc74bf12 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -57,9 +57,12 @@ _jukebox_webapp_download() { echo " Downloading web application" | tee /dev/fd/3 local JUKEBOX_VERSION=$(get_version_string "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") local TAR_FILENAME="webapp-build.tar.gz" + local DOWNLOAD_URL="https://github.com/MiczFlor/RPi-Jukebox-RFID/releases/download/v${JUKEBOX_VERSION}/webapp-v${JUKEBOX_VERSION}.tar.gz" + echo " DOWNLOAD_URL: ${DOWNLOAD_URL}" cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error - wget --quiet ${GIT_URL}/releases/download/v${JUKEBOX_VERSION}/webapp-v${JUKEBOX_VERSION}.tar.gz -O ${TAR_FILENAME} + # URL must be set to default repo as installation can be run from different repos as well where releases may not exist + wget --quiet ${DOWNLOAD_URL} -O ${TAR_FILENAME} tar -xzf ${TAR_FILENAME} rm -f ${TAR_FILENAME} cd "${INSTALLATION_PATH}" || exit_on_error From caf4a5525fa3f4d50a0ecff1870abb9d5b9f21d3 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Fri, 8 Dec 2023 09:33:09 +0100 Subject: [PATCH 041/121] Fix version funtion return value --- installation/includes/02_helpers.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index c0a9aa0d1..a8deca9c3 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -61,9 +61,9 @@ get_version_string() { if [ -n "$version_major" ] && [ -n "$version_minor" ] && [ -n "$version_patch" ]; then local version_string="${version_major}.${version_minor}.${version_patch}" - echo "Jukebox Version: ${version_string}" + echo ${version_string} else - echo "Unable to extract version information from ${python_file}" + exit_on_error "ERROR: Unable to extract version information from ${python_file}" fi } From 554c0ed0b87717dc90e76794dd63f871ae83e1f6 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:26:19 +0100 Subject: [PATCH 042/121] fix local libzmq build fix folder handling ZMQ_TMP_DIR. remove libsodium (use internal encryption lib tweetnacl). add cpu_count. add --disable-Werror. add sudo to make install. update log messaages. --- installation/routines/setup_jukebox_core.sh | 38 +++++++++------------ 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index efdb6c168..b02b8418b 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Constants -ZMQ_TMP_DIR="libzmq" +ZMQ_TMP_DIR="${HOME_PATH}/libzmq" ZMQ_PREFIX="/usr/local" ZMQ_VERSION="4.3.5" @@ -48,30 +48,26 @@ _jukebox_core_configure_pulseaudio() { } _jukebox_core_build_libzmq_with_drafts() { - echo " Building libzmq with drafts support" | tee /dev/fd/3 - - LIBSODIUM_VERSION="1.0.18" - echo " Building libsodium v${LIBSODIUM_VERSION}" | tee /dev/fd/3 - { cd "${HOME_PATH}" && mkdir "${ZMQ_TMP_DIR}" && cd "${ZMQ_TMP_DIR}"; } || exit_on_error - wget --quiet https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VERSION}-RELEASE/libsodium-${LIBSODIUM_VERSION}.tar.gz - tar -zxvf libsodium-${LIBSODIUM_VERSION}.tar.gz - cd libsodium-${LIBSODIUM_VERSION} || exit_on_error - ./configure - make && make install - - echo " Building libzmq v${ZMQ_VERSION}" | tee /dev/fd/3 - cd "${HOME}/${ZMQ_TMP_DIR}" || exit_on_error - wget --quiet https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz - tar -xzf libzmq.tar.gz - zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts - make && make install + echo " Building libzmq v${ZMQ_VERSION} with drafts support" | tee /dev/fd/3 + local zmq_filename="zeromq-${ZMQ_VERSION}" + local zmq_tar_filename="${zmq_filename}.tar.gz" + local cpu_count=${CPU_COUNT:-$(python3 -c "import os; print(os.cpu_count())")} + + cd "${ZMQ_TMP_DIR}" || exit_on_error + wget --quiet https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/${zmq_tar_filename} + tar -xzf ${zmq_tar_filename} + rm -f ${zmq_tar_filename} + cd ${zmq_filename} || exit_on_error + ./configure --prefix=${ZMQ_PREFIX} --enable-drafts --disable-Werror + make -j${cpu_count} && sudo make install } _jukebox_core_download_prebuilt_libzmq_with_drafts() { - echo " Download pre-compiled libzmq" + echo " Download pre-compiled libzmq with drafts support" local ZMQ_TAR_FILENAME="libzmq.tar.gz" ARCH=$(get_architecture) + cd "${ZMQ_TMP_DIR}" || exit_on_error wget --quiet https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O ${ZMQ_TAR_FILENAME} tar -xzf ${ZMQ_TAR_FILENAME} rm -f ${ZMQ_TAR_FILENAME} @@ -89,12 +85,12 @@ _jukebox_core_build_and_install_pyzmq() { echo " Install pyzmq with libzmq-drafts to support WebSockets" | tee /dev/fd/3 if ! pip list | grep -F pyzmq >> /dev/null; then - { cd "${HOME_PATH}" && mkdir "${ZMQ_TMP_DIR}" && cd "${ZMQ_TMP_DIR}"; } || exit_on_error if [[ $(uname -m) == "armv6l" ]]; then _show_slow_hardware_message fi + mkdir -p "${ZMQ_TMP_DIR}" || exit_on_error if [ "$BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE" = true ] ; then _jukebox_core_build_libzmq_with_drafts else @@ -151,8 +147,8 @@ _jukebox_core_check() { _run_setup_jukebox_core() { _jukebox_core_install_os_dependencies _jukebox_core_install_python_requirements - _jukebox_core_configure_pulseaudio _jukebox_core_build_and_install_pyzmq + _jukebox_core_configure_pulseaudio _jukebox_core_install_settings _jukebox_core_register_as_service _jukebox_core_check From 435bd6fdf0dcc8c074c901c7afad4a777e23d016 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:26:19 +0100 Subject: [PATCH 043/121] add CI test for local libzmq build --- .../test_docker_debian_codename_sub_v3.yml | 2 +- ci/installation/run_install_libzmq_local.sh | 40 +++++++++++++++++++ installation/includes/01_default_config.sh | 2 +- 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 ci/installation/run_install_libzmq_local.sh diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml index 97924c4bf..b4df11b3c 100644 --- a/.github/workflows/test_docker_debian_codename_sub_v3.yml +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -143,7 +143,7 @@ jobs: strategy: fail-fast: false matrix: - test_script: ['run_install_common.sh', 'run_install_faststartup.sh', 'run_install_webapp_local.sh', 'run_install_webapp_download.sh'] + test_script: ['run_install_common.sh', 'run_install_faststartup.sh', 'run_install_webapp_local.sh', 'run_install_webapp_download.sh', 'run_install_libzmq_local.sh'] steps: - name: Set up QEMU diff --git a/ci/installation/run_install_libzmq_local.sh b/ci/installation/run_install_libzmq_local.sh new file mode 100644 index 000000000..20f246ff8 --- /dev/null +++ b/ci/installation/run_install_libzmq_local.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for the libzmq local build (no precompiled download) + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=true +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# n - setup autohotspot +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# n - setup webapp +# - - setup kiosk mode (only with webapp = y) +# - - install node (only with webapp = y) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +n +n +n +n +n +n +n +' diff --git a/installation/includes/01_default_config.sh b/installation/includes/01_default_config.sh index b2f37a1de..fa1bafb61 100644 --- a/installation/includes/01_default_config.sh +++ b/installation/includes/01_default_config.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=false +BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=${BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE:-"false"} ENABLE_STATIC_IP=true DISABLE_IPv6=true ENABLE_AUTOHOTSPOT=false From 23cf06cb90cb7fe23ee6347fdd86c5741540f8cc Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:26:19 +0100 Subject: [PATCH 044/121] update var names and docs --- installation/routines/setup_jukebox_core.sh | 33 +++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index b02b8418b..6b070ff6c 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # Constants -ZMQ_TMP_DIR="${HOME_PATH}/libzmq" -ZMQ_PREFIX="/usr/local" -ZMQ_VERSION="4.3.5" +JUKEBOX_ZMQ_TMP_DIR="${HOME_PATH}/libzmq" +JUKEBOX_ZMQ_PREFIX="/usr/local" +JUKEBOX_ZMQ_VERSION="4.3.5" JUKEBOX_PULSE_CONFIG="${HOME_PATH}"/.config/pulse/default.pa JUKEBOX_SERVICE_NAME="${SYSTEMD_USR_PATH}/jukebox-daemon.service" @@ -48,30 +48,30 @@ _jukebox_core_configure_pulseaudio() { } _jukebox_core_build_libzmq_with_drafts() { - echo " Building libzmq v${ZMQ_VERSION} with drafts support" | tee /dev/fd/3 - local zmq_filename="zeromq-${ZMQ_VERSION}" + echo " Building libzmq v${JUKEBOX_ZMQ_VERSION} with drafts support" | tee /dev/fd/3 + local zmq_filename="zeromq-${JUKEBOX_ZMQ_VERSION}" local zmq_tar_filename="${zmq_filename}.tar.gz" local cpu_count=${CPU_COUNT:-$(python3 -c "import os; print(os.cpu_count())")} - cd "${ZMQ_TMP_DIR}" || exit_on_error - wget --quiet https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/${zmq_tar_filename} + cd "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error + wget --quiet https://github.com/zeromq/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/${zmq_tar_filename} tar -xzf ${zmq_tar_filename} rm -f ${zmq_tar_filename} cd ${zmq_filename} || exit_on_error - ./configure --prefix=${ZMQ_PREFIX} --enable-drafts --disable-Werror + ./configure --prefix=${JUKEBOX_ZMQ_PREFIX} --enable-drafts --disable-Werror make -j${cpu_count} && sudo make install } _jukebox_core_download_prebuilt_libzmq_with_drafts() { echo " Download pre-compiled libzmq with drafts support" - local ZMQ_TAR_FILENAME="libzmq.tar.gz" + local zmq_tar_filename="libzmq.tar.gz" ARCH=$(get_architecture) - cd "${ZMQ_TMP_DIR}" || exit_on_error - wget --quiet https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O ${ZMQ_TAR_FILENAME} - tar -xzf ${ZMQ_TAR_FILENAME} - rm -f ${ZMQ_TAR_FILENAME} - sudo rsync -a ./* ${ZMQ_PREFIX}/ + cd "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error + wget --quiet https://github.com/pabera/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/libzmq5-${ARCH}-${JUKEBOX_ZMQ_VERSION}.tar.gz -O ${zmq_tar_filename} + tar -xzf ${zmq_tar_filename} + rm -f ${zmq_tar_filename} + sudo rsync -a ./* ${JUKEBOX_ZMQ_PREFIX}/ } _jukebox_core_build_and_install_pyzmq() { @@ -82,6 +82,7 @@ _jukebox_core_build_and_install_pyzmq() { # Sources: # https://pyzmq.readthedocs.io/en/latest/howto/draft.html # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md + # https://github.com/zeromq/pyzmq/issues/1523#issuecomment-1593120264 echo " Install pyzmq with libzmq-drafts to support WebSockets" | tee /dev/fd/3 if ! pip list | grep -F pyzmq >> /dev/null; then @@ -90,14 +91,14 @@ _jukebox_core_build_and_install_pyzmq() { _show_slow_hardware_message fi - mkdir -p "${ZMQ_TMP_DIR}" || exit_on_error + mkdir -p "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error if [ "$BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE" = true ] ; then _jukebox_core_build_libzmq_with_drafts else _jukebox_core_download_prebuilt_libzmq_with_drafts fi - ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ + ZMQ_PREFIX="${JUKEBOX_ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ pip install -v --no-binary pyzmq --pre pyzmq else echo " Skipping. pyzmq already installed" | tee /dev/fd/3 From 55b1c9e59ae1a556a9953826db002499492df8dd Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:02:04 +0100 Subject: [PATCH 045/121] add platform in scope and description --- .../test_docker_debian_codename_sub_v3.yml | 17 +++++++++-------- .github/workflows/test_docker_debian_v3.yml | 10 ++++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml index b4df11b3c..dde60cb2a 100644 --- a/.github/workflows/test_docker_debian_codename_sub_v3.yml +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -6,14 +6,13 @@ on: debian_codename: required: true type: string + platform: + required: true + type: string docker_image_name: required: false type: string default: rpi-jukebox-rfid-v3 - platform: - required: false - type: string - default: linux/arm/v7 cache_scope: required: false type: string @@ -34,7 +33,7 @@ env: # let only one instance run the test so cache is not corrupted. # cancel already running instances as only the last run will be relevant concurrency: - group: ${{ inputs.cache_scope }}-${{ inputs.debian_codename }} + group: ${{ inputs.cache_scope }}-${{ inputs.debian_codename }}-${{ inputs.platform }} cancel-in-progress: true jobs: @@ -73,10 +72,12 @@ jobs: DEBIAN_CODENAME: ${{ inputs.debian_codename }} DOCKER_IMAGE_NAME: ${{ inputs.docker_image_name }} CACHE_SCOPE: ${{ inputs.cache_scope }} + PLATFORM: ${{ inputs.platform }} run: | - echo "image_tag_name=${{ env.DOCKER_IMAGE_NAME }}:${{ env.DEBIAN_CODENAME }}-test" >> $GITHUB_OUTPUT - echo "image_file_name=${{ env.DOCKER_IMAGE_NAME }}-${{ env.DEBIAN_CODENAME }}.tar" >> $GITHUB_OUTPUT - echo "cache_scope=${{ env.CACHE_SCOPE }}-${{ env.DEBIAN_CODENAME }}" >> $GITHUB_OUTPUT + PLATFORM=${PLATFORM////_} + echo "image_tag_name=${{ env.DOCKER_IMAGE_NAME }}:${{ env.DEBIAN_CODENAME }}-${PLATFORM}-test" >> $GITHUB_OUTPUT + echo "image_file_name=${{ env.DOCKER_IMAGE_NAME }}-${{ env.DEBIAN_CODENAME }}-${PLATFORM}.tar" >> $GITHUB_OUTPUT + echo "cache_scope=${{ env.CACHE_SCOPE }}-${{ env.DEBIAN_CODENAME }}-${PLATFORM}" >> $GITHUB_OUTPUT - name: Set Output vars id: vars diff --git a/.github/workflows/test_docker_debian_v3.yml b/.github/workflows/test_docker_debian_v3.yml index 6cf1648aa..6f90048ec 100644 --- a/.github/workflows/test_docker_debian_v3.yml +++ b/.github/workflows/test_docker_debian_v3.yml @@ -18,14 +18,16 @@ concurrency: jobs: # Build container and run tests. Duplication of job intended for better visualization. - run_bookworm: - name: 'bookworm' + run_bookworm_armv7: + name: 'bookworm armv7' uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml with: debian_codename: 'bookworm' + platform: linux/arm/v7 - run_bullseye: - name: 'bullseye' + run_bullseye_armv7: + name: 'bullseye armv7' uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml with: debian_codename: 'bullseye' + platform: linux/arm/v7 From c00cde685104746fc3e1cee495b36d0a7464d713 Mon Sep 17 00:00:00 2001 From: s-martin Date: Fri, 8 Dec 2023 20:53:13 +0100 Subject: [PATCH 046/121] Add file upload via WebUI --- documentation/developers/status.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/developers/status.md b/documentation/developers/status.md index 50968293c..eac874608 100644 --- a/documentation/developers/status.md +++ b/documentation/developers/status.md @@ -227,6 +227,7 @@ Topics marked _in progress_ are already in the process of implementation by comm - [x] Enable/Disable Auto-Hotspot - [x] `run_npm_build` script - [x] Must consider `export NODE_OPTIONS=--max-old-space-size=512` +- [ ] Upload audio files via WebUI https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2138 ## Installation Procedure From 18857d7336af4dca68cfd72f7ca92e1341fe68f1 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sun, 10 Dec 2023 18:29:47 +0100 Subject: [PATCH 047/121] [Bugfix] Webapp allows to assign single song to card (#2143) * [Bugfix][Dockerfile] Mount jukebox code as volume to support restart without build * Fix typo * [get_song_by_url] Search for relative paths in MPD database only --- docker/docker-compose.yml | 1 + src/jukebox/components/playermpd/__init__.py | 6 ++++++ src/webapp/src/components/Library/lists/index.js | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b2216894e..1679bbeb5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -40,6 +40,7 @@ services: restart: unless-stopped tty: true volumes: + - ../src/jukebox:/root/RPi-Jukebox-RFID/src/jukebox - ../shared:/root/RPi-Jukebox-RFID/shared - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf command: python run_jukebox.py diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index ecac65ab8..3975fdb67 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -80,6 +80,7 @@ # Toggle (und 2nd Swipe generell) ist immer vom Status des Zielsystems abhängig und kann damit nur vom Zielsystem geändert # werden. Bei Wifi also braucht man 3 Funktionen: on / off / toggle. Toggle ist dann first swipe / second swipe +import os import mpd import threading import logging @@ -574,6 +575,11 @@ def list_song_by_artist_and_album(self, albumartist, album): @plugs.tag def get_song_by_url(self, song_url): + # MPD can play absolute paths but can find songs in its database only by relative path + # In certain situations, `song_url` can be an absolute path. Then, it will be trimed to be relative + _music_library_path_absolute = os.path.expanduser(components.player.get_music_library_path()) + song_url = song_url.replace(f'{_music_library_path_absolute}/', '') + with self.mpd_lock: song = self.mpd_retry_with_mutex(self.mpd_client.find, 'file', song_url) diff --git a/src/webapp/src/components/Library/lists/index.js b/src/webapp/src/components/Library/lists/index.js index a153619e6..22970d9a9 100644 --- a/src/webapp/src/components/Library/lists/index.js +++ b/src/webapp/src/components/Library/lists/index.js @@ -28,7 +28,7 @@ const LibraryLists = () => { const [cardId] = useState(searchParams.get('cardId')); const [musicFilter, setMusicFilter] = useState(''); - const handleMusicFilder = (event) => { + const handleMusicFolder = (event) => { setMusicFilter(event.target.value); }; @@ -49,7 +49,7 @@ const LibraryLists = () => { {isSelecting && } Date: Sun, 10 Dec 2023 22:28:55 +0100 Subject: [PATCH 048/121] [Docs] Inform about Python venv (#2147) --- documentation/developers/README.md | 2 +- .../developers/development-environment.md | 59 +++++++------------ documentation/developers/pyhton.md | 18 ++++++ 3 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 documentation/developers/pyhton.md diff --git a/documentation/developers/README.md b/documentation/developers/README.md index 9136c5f31..6c973d6ba 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -3,7 +3,7 @@ ## Getting started * [Development Environment](./development-environment.md) -* [Setting up Docker](./docker.md) +* [Python Development Notes](pyhton.md) ## Reference diff --git a/documentation/developers/development-environment.md b/documentation/developers/development-environment.md index d79a6101e..9abefbee0 100644 --- a/documentation/developers/development-environment.md +++ b/documentation/developers/development-environment.md @@ -1,30 +1,30 @@ # Development Environment -You have 3 development options: -## Directly on Raspberry Pi -The full setup is running on the RPi and you access files via SSH. -Pretty easy to set up as you simply do a normal install and switch to -the `future3/develop` branch. +You have 3 development options. Each option has its pros and cons. To interact with GPIO or other hardware, it's required to develop directly on a Raspberry Pi. For general development of Python code (Jukebox) or JavaScript (Webapp), we recommend Docker. Developing on your local machine (Linux, Mac, Windows) works as well and requires all dependencies to be installed locally. + +* [Develop in Docker](#develop-in-docker) +* [Develop on Raspberry Pi](#develop-on-raspberry-pi) +* [Develop on local machine](#develop-on-local-machine) + +## Develop in Docker + +There is a complete [Docker setup](./docker.md). + +## Develop on Raspberry Pi + +The full setup is running on the RPi and you access files via SSH. Pretty easy to set up as you simply do a normal install and switch to the `future3/develop` branch. ### Steps to install -We recommend to use at least a Pi 3 or Pi Zero 2 for development. This -hardware won\'t be needed in production, but it can be slow while -developing. +We recommend to use at least a Pi 3 or Pi Zero 2 for development. This hardware won\'t be needed in production, but it can be slow while developing. 1. Install the latest Pi OS on a SD card. 2. Boot up your Raspberry Pi. -3. [Install](../builders/installation.md) the Jukebox software as if you were building a - Phoniebox. You can install from your own fork and feature branch if - you wish which can be changed later as well. The original repository - will be set as `upstream`. +3. [Install](../builders/installation.md) the Jukebox software as if you were building a Phoniebox. You can install from your own fork and feature branch you wish which can be changed later as well. The original repository will be set as `upstream`. 4. Once the installation has successfully ran, reboot your Pi. -5. Due to some resource constraints, the Webapp does not build the - latest changes and instead consumes the latest official release. To - change that, you need to install NodeJS and build the Webapp - locally. +5. Due to some resource constraints, the Webapp does not build the latest changes and instead consumes the latest official release. To change that, you need to install NodeJS and build the Webapp locally. 6. Install NodeJS using the existing installer ``` bash @@ -34,8 +34,7 @@ developing. ``` 7. To free up RAM, reboot your Pi. -8. Build the Webapp using the existing build command. If the build - fails, you might have forgotten to reboot. +8. Build the Webapp using the existing build command. If the build fails, you might have forgotten to reboot. ``` bash cd ~/RPi-Jukebox-RFID/src/webapp; \ @@ -43,30 +42,16 @@ developing. ``` 9. The Webapp should now be updated. -10. To continuously update Webapp, pull the latest changes from your - repository and rerun the command above. +10. To continuously update Webapp, pull the latest changes from your repository and rerun the command above. -## Locally on any Linux machine +## Develop on local machine -The jukebox also runs on any Linux machine. The Raspberry Pi specific -stuff will not work of course. That is no issue depending our your -development area. USB RFID Readers, however, will work. You will have -to install and configure [MPD (Music Player -Daemon)](https://www.musicpd.org/). +The jukebox also runs on any Linux machine. The Raspberry Pi specific stuff will not work of course. That is no issue depending our your development area. USB RFID Readers, however, will work. You will have to install and configure [MPD (Music Player Daemon)](https://www.musicpd.org/). -In addition to the `requirements.txt`, you will this -dependency. On the Raspberry PI, the latest stable release of ZMQ does -not support WebSockets. We need to compile the latest version from -Github, which is taken care of by the installation script. For regular -machines, the normal package can be installed: +In addition to the `requirements.txt`, you will this dependency. On the Raspberry PI, the latest stable release of ZMQ does not support WebSockets. We need to compile the latest version from Github, which is taken care of by the installation script. For regular machines, the normal package can be installed: ``` bash pip install pyzmq ``` -You will have to start Jukebox core application and the WebUI -separately. The MPD usually runs as a service. - -## Using Docker container - -There is a complete [Docker setup](./docker.md). +You will have to start Jukebox core application and the WebUI separately. The MPD usually runs as a service. diff --git a/documentation/developers/pyhton.md b/documentation/developers/pyhton.md new file mode 100644 index 000000000..e9a071e34 --- /dev/null +++ b/documentation/developers/pyhton.md @@ -0,0 +1,18 @@ +# Python Development Notes + +## Prerequisites + +> [!NOTE] +> All Python scripts must be run within a [virtual environment](https://docs.python.org/3/library/venv.html) (`.venv`). All Python plugins are installed encapsulated within this environment. + +Before you can run Python code, you need to enable the virtual environment. On the Raspberry Pi, it's located in the project root `~/RPi-Jukebox-RFID/.venv`. Depending on your setup, the absolute path can vary. + +``` +$ ~/RPi-Jukebox-RFID/.venv/bin/activate +``` + +If the virtual environment has been activated correctly, your terminal will now show a prefix (`.venv`). If you want to leave the venv again execute deactivate. + +``` +$ ~/RPi-Jukebox-RFID/.venv/bin/deactivate +``` From dd7f6e943903162b816336eaf0c7496240a13273 Mon Sep 17 00:00:00 2001 From: s-martin Date: Mon, 11 Dec 2023 00:10:13 +0100 Subject: [PATCH 049/121] Add running pytest to GitHub action (#2146) * Add running pytest * Trigger running the action * Remove Python 3.7 to check * support only bullseye and bookworm python versions * Add coverage check with coveralls * use coverage for ci * add coverage package * consider review comments --- .flake8 | 2 +- .github/workflows/pythonpackage_future3.yml | 23 ++++++++++++++++++--- requirements.txt | 1 + run_pytest.sh | 2 +- src/jukebox/misc/loggingext.py | 1 + 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.flake8 b/.flake8 index d11edbe8a..0c2cd276e 100644 --- a/.flake8 +++ b/.flake8 @@ -15,7 +15,7 @@ per-file-ignores = count = True max-complexity = 12 statistics = True -filename = *.py,*.py.* +filename = *.py extend-exclude = # Ignore all scratch development directories scratch*, diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index 48e27b15d..3834d83db 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -6,13 +6,11 @@ on: - 'future3/**' paths: - '**.py' - - '**.py.*' pull_request: branches: - 'future3/**' paths: - '**.py' - - '**.py.*' jobs: build: @@ -21,7 +19,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 @@ -41,8 +39,27 @@ jobs: pip3 install -r src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt pip3 install -r src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt pip3 install -r src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt + - name: Run pytest + run: | + ./run_pytest.sh --cov --cov-report xml + - name: Report to Coveralls (parallel) + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: coverage.xml + format: cobertura + parallel: true - name: Lint with flake8 run: | pip3 install flake8 # Stop the build if linting fails ./run_flake8.sh + + finish: + needs: build + runs-on: ubuntu-latest + steps: + - name: Close parallel build + uses: coverallsapp/github-action@v2 + with: + parallel-finished: true diff --git a/requirements.txt b/requirements.txt index 0f8c8c86d..bd2ea6651 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,4 +31,5 @@ gpiozero # Code quality flake8>=4.0.0 pytest +pytest-cov mock diff --git a/run_pytest.sh b/run_pytest.sh index a3cbd8df6..766f05182 100755 --- a/run_pytest.sh +++ b/run_pytest.sh @@ -10,4 +10,4 @@ SCRIPT_DIR="$(dirname "$SOURCE")" cd "$SCRIPT_DIR" || (echo "Could not change to top-level project directory" && exit 1) # Run pytest -pytest -c pytest.ini +pytest -c pytest.ini $@ diff --git a/src/jukebox/misc/loggingext.py b/src/jukebox/misc/loggingext.py index 9328cfea8..771248d8f 100644 --- a/src/jukebox/misc/loggingext.py +++ b/src/jukebox/misc/loggingext.py @@ -1,6 +1,7 @@ """ ############## Logger + ############## We use a hierarchical Logger structure based on pythons logging module. It can be finely configured with a yaml file. From afc30671f509aa333393ef3161845bbaa56ad35b Mon Sep 17 00:00:00 2001 From: ben0r33 <72662304+ben0r33@users.noreply.github.com> Date: Mon, 11 Dec 2023 08:52:57 +0100 Subject: [PATCH 050/121] [Bugfix] setup_autohotspot.sh for multiple wifi country codes, use first (#2150) --- installation/routines/setup_autohotspot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installation/routines/setup_autohotspot.sh b/installation/routines/setup_autohotspot.sh index cc40b7794..a083b3fcf 100644 --- a/installation/routines/setup_autohotspot.sh +++ b/installation/routines/setup_autohotspot.sh @@ -14,7 +14,7 @@ AUTOHOTSPOT_TARGET_PATH="/usr/bin/autohotspot" _get_interface() { # interfaces may vary WIFI_INTERFACE=$(iw dev | grep "Interface"| awk '{ print $2 }') - WIFI_REGION=$(iw reg get | grep country | awk '{ print $2}' | cut -d: -f1) + WIFI_REGION=$(iw reg get | grep country | head -n 1 | awk '{ print $2}' | cut -d: -f1) # fix for CI runs on docker if [ "${CI_RUNNING}" == "true" ]; then From bd345213496bd2288b3feb86d71e7c4d215464f7 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:07:55 +0100 Subject: [PATCH 051/121] some documentation updates (#2151) harmonize paths updated doc for coreapps and references fix execution docs for run_jukebox.py --- documentation/builders/audio.md | 14 +++--- documentation/builders/concepts.md | 4 +- documentation/builders/configuration.md | 10 +++-- documentation/builders/rpc-commands.md | 2 +- documentation/builders/system.md | 4 +- documentation/builders/troubleshooting.md | 18 ++++---- documentation/builders/update.md | 6 +++ documentation/developers/coreapps.md | 43 ++++++++----------- documentation/developers/developer-issues.md | 1 + documentation/developers/docker.md | 2 +- documentation/developers/rfid/basics.md | 5 +-- documentation/developers/rfid/genericusb.md | 2 +- documentation/developers/rfid/mfrc522_spi.md | 2 +- .../developers/rfid/template_reader.md | 4 +- .../default-settings/jukebox.default.yaml | 2 +- .../hardware/fake_reader_gui/requirements.txt | 1 + .../hardware/pn532_i2c_py532/requirements.txt | 1 + .../hardware/rdm6300_serial/requirements.txt | 3 ++ 18 files changed, 65 insertions(+), 59 deletions(-) diff --git a/documentation/builders/audio.md b/documentation/builders/audio.md index 07d24e40f..0bb5a7163 100644 --- a/documentation/builders/audio.md +++ b/documentation/builders/audio.md @@ -10,14 +10,14 @@ Stream transfer happens on user input or automatically on the connection of an a This is mainly targeted at Bluetooth Headsets/Speakers. Audio outputs run via PulseAudio and the basic configuration should be easy. -There is a [configuration tool](../developers/coreapps.md#run_configure_audio.py), +There is a [configuration tool](../developers/coreapps.md#Audio), to setup the configuration for the Jukebox Core App. To set up the audio 1. Follow the setup steps according to your sound card 2. Check that the sound output works [as described below](audio.md#checking-system-sound-output) -3. Run the the tool [run_configure_audio](../developers/coreapps.md#run_configure_audio.py) +3. Run the [audio configuration tool](../developers/coreapps.md#Audio) 4. [Fine-tune audio parameters](audio.md#additional-options) ## Checking system sound output @@ -31,7 +31,7 @@ $ pactl list sinks short 1 bluez_sink.C4_FB_20_63_CO_FE.a2dp_sink module-bluez5-device.c s16le 2ch 44100Hz # Set the default sink (this will be reset at reboot) -$ pactl set-default-sink sink_name +$ pactl set-default-sink # Check default sink is correctly set $ pactl info @@ -50,7 +50,7 @@ You can also try different PulseAudio sinks without setting the default sink. In volume level for this sink: ```bash -$ paplay -d sink_name /usr/share/sounds/alsa/Front_Center.wav +$ paplay -d /usr/share/sounds/alsa/Front_Center.wav ``` # Bluetooth @@ -134,8 +134,4 @@ You are, of course, free to modify the PulseAudio configuration to your needs. R 1. [PulseAudio Documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User) 2. [PulseAudio Examples](https://wiki.archlinux.org/title/PulseAudio/Examples) -In this case, run the configuration tool with below parameter to avoid touching the PulseAudio configuration file. - -```bash -$ ./run_configure_audio.py --ro_pulse -``` +In this case, run the [audio configuration tool](../developers/coreapps.md#Audio) with the parameter `--ro_pulse` to avoid touching the PulseAudio configuration file. diff --git a/documentation/builders/concepts.md b/documentation/builders/concepts.md index c08a33caa..37c13db89 100644 --- a/documentation/builders/concepts.md +++ b/documentation/builders/concepts.md @@ -16,10 +16,10 @@ The Remote Procedure Call (RPC) server allows remotely triggering actions (e.g., Why should you care? Because we use the same protocol when triggering actions from other inputs like a card swipe, a GPIO button press, etc. How that works is described in [RPC Commands](rpc-commands.md). -We also have a tool to send RPC commands to the running Jukebox application: [run_rpc_tool.py](../developers/coreapps.md#run_rpc_toolpy) +We also have a [tool to send RPC commands](../developers/coreapps.md#RPC) to the running Jukebox application. ## Publishing Message Queue The Publishing Message Queue is the complementary part to the RPC where the core application publishes its status and status updates. As a user, you need not worry about it. -If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming status messages: [run_publicity_sniffer.py](../developers/coreapps.md#run_publicity_snifferpy). +If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A [sniffer tool](../developers/coreapps.md#Publicity-Sniffer) exists which listens and prints the incoming status messages. diff --git a/documentation/builders/configuration.md b/documentation/builders/configuration.md index e0240ddbe..f09e8098d 100644 --- a/documentation/builders/configuration.md +++ b/documentation/builders/configuration.md @@ -1,13 +1,13 @@ # Jukebox Configuration -The Jukebox configuration is managed by set of files located in `../shared/settings`. +The Jukebox configuration is managed by a set of files located in `shared/settings`. Some configuration changes can be made through the WebUI and take immediate effect. The majority of configuration options is only available by editing the config files - *when the service is not running!* Don't fear (overly), they contain commentaries. -For several aspects we have :ref:`developer/coreapps:Configuration Tools` and detailed guides: +For several aspects we have [configuration tools](../developers/coreapps.md#configuration-tools) and detailed guides: * [Audio Configuration](./audio.md#audio-configuration) * [RFID Reader Configuration](../developers/rfid/basics.md#reader-configuration) @@ -24,7 +24,8 @@ $ systemctl --user stop jukebox-daemon $ nano ./shared/settings/jukebox.yaml # Start Jukebox in console and check the log output (optional) -$ ./src/jukebox/run_jukebox.py +$ cd src/jukebox +$ ./run_jukebox.py # and if OK, press Ctrl-C and restart the service # Restart the service @@ -36,5 +37,6 @@ This could be useful if you want your Jukebox to only allow a lower volume when at night time when there is time to go to bed :-) ```bash -$./run_jukebox.py --conf ../path/to/custom/config.yaml +$ cd src/jukebox +$ ./run_jukebox.py --conf path/to/custom/config.yaml ``` diff --git a/documentation/builders/rpc-commands.md b/documentation/builders/rpc-commands.md index f578ccd99..f98da8cb4 100644 --- a/documentation/builders/rpc-commands.md +++ b/documentation/builders/rpc-commands.md @@ -107,6 +107,6 @@ You will find some more examples the configuration of the [Card Database](card-d ## For developers -To send RPC commands for testing and debugging purpose you can use the CLI Tool [`run_rpc_tool.py`](../developers/coreapps.md#run_rpc_toolpy) +To send RPC commands for testing and debugging purpose you can use the [CLI Tool](../developers/coreapps.md#RPC). Also here is a ready-to-use decoding functions which decodes an RPC command (with or without alias) from a YAML entry:func:`jukebox.utils.decode_rpc_command`. diff --git a/documentation/builders/system.md b/documentation/builders/system.md index 873d489f6..0e2c19485 100644 --- a/documentation/builders/system.md +++ b/documentation/builders/system.md @@ -70,7 +70,7 @@ Service control and service configuration file location is identical to MPD. ## Jukebox Core Service -The :ref:`developer/coreapps:Jukebox Core` runs as a *user-local* service with the name `jukebox-daemon`. +The [Jukebox Core Service](../developers/coreapps.md#Jukebox-Core) runs as a *user-local* service with the name `jukebox-daemon`. Similar to MPD, it's important that it does run as system-wide service to be able to interact with PulseAudio. The service can be controlled with the `systemctl`-command by adding the parameter `--user` @@ -101,7 +101,7 @@ Starting and stopping the service can be useful for debugging or configuration c The Web UI is served using nginx. Nginx runs as a system service. The home directory is localed at ``` -~/RPi-Jukebox-RFID/src/webapp/build +./src/webapp/build ``` The Nginx configuration is located at diff --git a/documentation/builders/troubleshooting.md b/documentation/builders/troubleshooting.md index a0618ab5d..a83384ec5 100644 --- a/documentation/builders/troubleshooting.md +++ b/documentation/builders/troubleshooting.md @@ -15,8 +15,8 @@ Debugging your setup runs in several steps ## The short answer ```bash -../shared/logs/app.log : Complete Debug Messages -../shared/logs/errors.log: Only Errors and Warnings +shared/logs/app.log : Complete Debug Messages +shared/logs/errors.log: Only Errors and Warnings ``` These files always contain the messages of the current run only. @@ -33,9 +33,9 @@ http://ip.of.your.box/logs ## The long answer: A few more details -If started without parameters, the Jukebox checks for the existence of `../shared/settings/logger.yaml` +If started without parameters, the Jukebox checks for the existence of `shared/settings/logger.yaml` and if present, uses that configuration for logging. This file is created by the installation process. -The default configuration file is also provided in `../resources/default-settings/logger.default.yaml`. +The default configuration file is also provided in `resources/default-settings/logger.default.yaml`. We use Python's logging module to provide the debug messages which is configured through this file. **We are still in the Pre-Release phase which means full debug logging is enabled by default.** @@ -47,8 +47,8 @@ The default logging config does 2 things: 1. It writes 2 log files: ```bash -../shared/logs/app.log : Complete Debug Messages -../shared/logs/errors.log : Only Errors and Warnings +shared/logs/app.log : Complete Debug Messages +shared/logs/errors.log : Only Errors and Warnings ``` 2. Prints logging messages to the console. If run as a service, only error messages are emitted to console to avoid spamming the system log files. @@ -63,11 +63,12 @@ on the console log. $ systemctl --user stop jukebox-daemon # Start the Jukebox in debug mode: +$ cd src/jukebox + # with default logger: $ ./run_jukebox.py - # or with custom logger configuration: -$ ./run_jukebox.py --logger ../path/to/logger.yaml +$ ./run_jukebox.py --logger path/to/custom/logger.yaml ``` ### Fallback configuration @@ -77,6 +78,7 @@ Attention: This only emits messages to the console and does not write to the log This is more a fallback features: ``` bash +$ cd src/jukebox $ ./run_jukebox.py -vv ``` diff --git a/documentation/builders/update.md b/documentation/builders/update.md index e09137ca3..f6dab2c91 100644 --- a/documentation/builders/update.md +++ b/documentation/builders/update.md @@ -2,6 +2,12 @@ ## Updating your Jukebox Version 3 +### Update from v3.2.1 and prior + +As there are some significant changes in the installation, a new setup on a fresh image is required. + +### General + Things on Version 3 are moving fast and you may want to keep up with recent changes. Since we are in Alpha Release stage, a fair number of fixes are expected to be committed in the near future. diff --git a/documentation/developers/coreapps.md b/documentation/developers/coreapps.md index 2fa8b84e9..3e4fc7b1f 100644 --- a/documentation/developers/coreapps.md +++ b/documentation/developers/coreapps.md @@ -4,14 +4,14 @@ The Jukebox\'s core apps are located in `src/jukebox`. Run the following command to learn more about each app and its parameters: ``` bash -$ ./run_app_name.py -h +$ cd src/jukebox +$ ./ -h ``` -## Jukebox Core -### `run_jukebox.py` +## Jukebox Core -[run_jukebox.py](../../src/jukebox/run_jukebox.py) +**Scriptname:** [run_jukebox.py](../../src/jukebox/run_jukebox.py) This is the main app and starts the Jukebox Core. @@ -19,49 +19,44 @@ Usually this runs as a service, which is started automatically after boot-up. At For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. See [Troubleshooting](../builders/troubleshooting.md). + ## Configuration Tools Before running the configuration tools, stop the Jukebox Core service. See [Best practice procedure](../builders/configuration.md#best-practice-procedure). -### `run_configure_audio.py` - -[run_configure_audio.py](../../src/jukebox/run_configure_audio.py) +### Audio -Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. +**Scriptname:** [run_configure_audio.py](../../src/jukebox/run_configure_audio.py) -Will also setup equalizer and mono down mixer in the pulseaudio config file. +Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. -Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). +Will also setup equalizer and mono down mixer in the pulseaudio config file. Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). -### `run_register_rfid_reader.py` +### RFID Reader -[run_register_rfid_reader.py](../../src/jukebox/run_register_rfid_reader.py) +**Scriptname:** [run_register_rfid_reader.py](../../src/jukebox/run_register_rfid_reader.py) -Setup tool to configure the RFID Readers. +Setup tool to configure the RFID Readers. Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change the settings. For more information see [RFID Readers](./rfid/README.md). > [!NOTE] > This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). Any manual modifications to the settings will have to be re-applied -## Developer Tools - -### `run_rpc_tool.py` -[run_rpc_tool.py](../../src/jukebox/run_rpc_tool.py) - -Command Line Interface to the Jukebox RPC Server. +## Developer Tools -A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging. +### RPC -The tool features auto-completion and command history. +**Scriptname:** [run_rpc_tool.py](../../src/jukebox/run_rpc_tool.py) -The list of available commands is fetched from the running Jukebox service. +Command Line Interface to the Jukebox RPC Server. +A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging.The tool features auto-completion and command history. The list of available commands is fetched from the running Jukebox service. -### `run_publicity_sniffer.py` +### Publicity Sniffer - [run_publicity_sniffer.py](../../src/jukebox/run_publicity_sniffer.py) +**Scriptname:** [run_publicity_sniffer.py](../../src/jukebox/run_publicity_sniffer.py) A command line tool that monitors all messages being sent out from the Jukebox via the publishing interface. Received messages are printed in the console. Mainly used for debugging. diff --git a/documentation/developers/developer-issues.md b/documentation/developers/developer-issues.md index 1ed531738..c8dd3b6c3 100644 --- a/documentation/developers/developer-issues.md +++ b/documentation/developers/developer-issues.md @@ -46,6 +46,7 @@ Alternatively, use the provided script, which sets the variable for you (provided your swap size is large enough): ``` bash +$ cd src/webapp $ ./run_rebuild.sh ``` diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 96b5a64b6..39518e248 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -33,7 +33,7 @@ need to adapt some of those commands to your needs. * Override/Merge the values from the following [Override file](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/docker/config/jukebox.overrides.yaml) in your `jukebox.yaml`. * **\[Currently required\]** Update all relative paths (`../..`) in to `/home/pi/RPi-Jukebox-RFID`. -4. Change directory into the `./RPi-Jukebox-RFID/shared/audiofolders` +4. Change directory into the `./shared/audiofolders` and copy a set of MP3 files into this folder (for more fun when testing). diff --git a/documentation/developers/rfid/basics.md b/documentation/developers/rfid/basics.md index 7a5aa37d9..7d557ed06 100644 --- a/documentation/developers/rfid/basics.md +++ b/documentation/developers/rfid/basics.md @@ -35,8 +35,7 @@ Readers operate on one of two different frequencies: 125kHz or 13.56 MHz. Make s ## Reader Configuration During the installation process, you can already configure a RFID -reader. To manually configure RFID reader(s), -[please run the tool](../coreapps.md#run_register_rfid_reader.py), (`src/jukebox/run_register_rfid_reader.py`). +reader. To manually configure RFID reader(s) run the [RFID reader configuration tool](../coreapps.md#RFID-Reader). It will generate a reader configuration file at `shared/settings/rfid.yaml`. You can re-run the tool to change the @@ -67,7 +66,7 @@ Indicates the Python package used for this reader. Filled by the RFID configurat #### config: -Filled by the [RFID configuration tool](../coreapps.md#run_register_rfid_reader.py) (`src/jukebox/run_register_rfid_reader.py`) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. +Filled by the [RFID reader configuration tool](../coreapps.md#RFID-Reader) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. #### same_id_delay: float \| integer diff --git a/documentation/developers/rfid/genericusb.md b/documentation/developers/rfid/genericusb.md index 23975cf14..544f0245c 100644 --- a/documentation/developers/rfid/genericusb.md +++ b/documentation/developers/rfid/genericusb.md @@ -4,7 +4,7 @@ This module covers all types of USB-based RFID input readers. If you plan to connect multiple USB-based RFID readers to the Jukebox, make -sure to connect all of them before running the registration tool [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_readerpy). +sure to connect all of them before running the [RFID reader configuration tool](../coreapps.md#RFID-Reader). > [!NOTE] > The user needs to be part of the group \'input\' for evdev to work. This should usually be the case. However, a user can be added with: diff --git a/documentation/developers/rfid/mfrc522_spi.md b/documentation/developers/rfid/mfrc522_spi.md index f7710b2b1..5dc46aab6 100644 --- a/documentation/developers/rfid/mfrc522_spi.md +++ b/documentation/developers/rfid/mfrc522_spi.md @@ -6,7 +6,7 @@ RC522 RFID reader via SPI connection. ## Installation -Run the [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_reader.py) tool for guided +Run the [RFID reader configuration tool](../coreapps.md#RFID-Reader) for guided installation. ## Options diff --git a/documentation/developers/rfid/template_reader.md b/documentation/developers/rfid/template_reader.md index 0f4c3e939..6a7a8b0c3 100644 --- a/documentation/developers/rfid/template_reader.md +++ b/documentation/developers/rfid/template_reader.md @@ -9,9 +9,9 @@ This template provides the skeleton API for a new Reader. If you follow the conventions outlined below, your new reader will be picked up automatically There is no extra need to register the reader module with -the Phoniebox. Just re-run `the reader config tool `. +the Phoniebox. Just re-run [RFID reader configuration tool](../coreapps.md#RFID-Reader). -Follow the instructions in [template_new_reader.py] +Follow the instructions in [template_new_reader.py](../../../src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py) Also have a look at the other reader subpackages to see how stuff works with an example diff --git a/resources/default-settings/jukebox.default.yaml b/resources/default-settings/jukebox.default.yaml index 91ada74c6..36661c992 100644 --- a/resources/default-settings/jukebox.default.yaml +++ b/resources/default-settings/jukebox.default.yaml @@ -30,7 +30,7 @@ pulse: toggle_on_connect: true # Limit maximum volume range to XX % - can be changed through the UI (for temporary use) soft_max_volume: 70 - # Run the tool run_configure_audio.py to configure the pulseaudio sinks + # Run the audio configuration tool to configure the pulseaudio sinks # # After startup, the audio output defaults to primary # Any Bluetooth device should be the secondary (as it may not always be available directly after boot) diff --git a/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt b/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt index 931c9db0f..937256e86 100644 --- a/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt +++ b/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt @@ -1,5 +1,6 @@ # This GUI-based mock reader also requires: tkinter # tkinter is a standard Python package and needs not be installed separately # It is available on most Unix systems (but not on headless Raspbian RPi where running a GUI is difficult anyway) +# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` ttkthemes diff --git a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt index 9b156854b..f7fe9563f 100644 --- a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt +++ b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt @@ -1,3 +1,4 @@ # PN532 related requirements # You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` + py532lib diff --git a/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt b/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt index f6c1a1f57..df92852d9 100644 --- a/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt +++ b/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt @@ -1 +1,4 @@ +# RDM6300 related requirements +# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` + pyserial From 50e8446bdff37ab87faadf3938eef0413df89946 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:03:28 +0100 Subject: [PATCH 052/121] Better logging functions for Installation script (#2152) * Introduce proper logging functions * Fix a few minor things * Introduce clear_c * A few minor bugfixes * Introduce run_and_log() function * Rename function to run_and_print_lc * Log IP address if static is chosen * Revert "Log IP address if static is chosen" This reverts commit 8852d97b4b11b3eb34781db5b8da32d405a4183a. * Update commit for run_and_print_lc --- installation/README.md | 9 +- installation/includes/02_helpers.sh | 68 +++++------ installation/includes/03_welcome.sh | 10 +- installation/includes/05_finish.sh | 14 +-- installation/install-jukebox.sh | 114 ++++++++++-------- installation/routines/customize_options.sh | 113 ++++++++--------- installation/routines/install.sh | 4 +- installation/routines/optimize_boot_time.sh | 30 ++--- installation/routines/set_raspi_config.sh | 12 +- installation/routines/set_ssh_qos.sh | 2 +- installation/routines/setup_git.sh | 76 ++++++------ installation/routines/setup_jukebox_core.sh | 22 ++-- installation/routines/setup_jukebox_webapp.sh | 12 +- installation/routines/setup_kiosk_mode.sh | 4 +- installation/routines/setup_mpd.sh | 8 +- installation/routines/setup_rfid_reader.sh | 2 +- installation/routines/setup_samba.sh | 6 +- 17 files changed, 261 insertions(+), 245 deletions(-) diff --git a/installation/README.md b/installation/README.md index b2a496348..178f1b7be 100644 --- a/installation/README.md +++ b/installation/README.md @@ -3,10 +3,11 @@ ## Logging - Bash Script output rules ```bash -Output to both console and logfile: "$ command | tee /dev/fd/3" -Output to console only "$ command 1>&3" -Output to logfile only: "$ command" -No output to both console and logfile: "$ command > /dev/null" +run_and_print_lc "Run a command and log its output to both console and logfile" +print_lc "This message will be logged to both console and logfile" +print_c "This message will only be logged to the console" +log "This message will only be logged to the logfile" +clear_c "Clears the console screen" ``` [Learn more about bash script outputs](https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console) diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index a8deca9c3..7520241c6 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -17,21 +17,21 @@ run_with_timer() { $1; # Executes the function passed as an argument - calc_runtime_and_print time_start $(date +%s) | tee /dev/fd/3 + run_and_print_lc calc_runtime_and_print time_start $(date +%s) } run_with_log_frame() { local time_start=$(date +%s); local description="$2" - echo -e "\n\n" - echo "#########################################################" - echo "${description}" | tee /dev/fd/3 + log "\n\n" + log "#########################################################" + print_lc "${description}" $1; # Executes the function passed as an argument local done_in=$(calc_runtime_and_print time_start $(date +%s)) - echo -e "\n${done_in} - ${description}" - echo "#########################################################" + log "\n${done_in} - ${description}" + log "#########################################################" } get_architecture() { @@ -69,16 +69,16 @@ get_version_string() { ### Verify helpers print_verify_installation() { - echo "" - echo " -------------------------------------------------------" - echo " Check installation" - echo "" + log "\n + ------------------------------------------------------- + Check installation +" } # Check if the file(s) exists verify_files_exists() { local files="$@" - echo " Verify '${files}' exists" + log " Verify '${files}' exists" if [[ -z "${files}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -88,13 +88,13 @@ verify_files_exists() { do test ! -f ${file} && exit_on_error "ERROR: '${file}' does not exists or is not a file!" done - echo " CHECK" + log " CHECK" } # Check if the dir(s) exists verify_dirs_exists() { local dirs="$@" - echo " Verify '${dirs}' exists" + log " Verify '${dirs}' exists" if [[ -z "${dirs}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -104,7 +104,7 @@ verify_dirs_exists() { do test ! -d ${dir} && exit_on_error "ERROR: '${dir}' does not exists or is not a dir!" done - echo " CHECK" + log " CHECK" } # Check if the file(s) has/have the expected owner and modifications @@ -113,7 +113,7 @@ verify_files_chmod_chown() { local user_expected=$2 local group_expected=$3 local files="${@:4}" - echo " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${files}'" + log " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${files}'" if [[ -z "${mod_expected}" || -z "${user_expected}" || -z "${group_expected}" || -z "${files}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -130,7 +130,7 @@ verify_files_chmod_chown() { test ! "${user_expected}" == "${user_actual}" && exit_on_error "ERROR: '${file}' actual owner '${user_actual}' differs from expected '${user_expected}'!" test ! "${group_expected}" == "${group_actual}" && exit_on_error "ERROR: '${file}' actual group '${group_actual}' differs from expected '${group_expected}'!" done - echo " CHECK" + log " CHECK" } # Check if the dir(s) has/have the expected owner and modifications @@ -139,7 +139,7 @@ verify_dirs_chmod_chown() { local user_expected=$2 local group_expected=$3 local dirs="${@:4}" - echo " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${dirs}'" + log " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${dirs}'" if [[ -z "${mod_expected}" || -z "${user_expected}" || -z "${group_expected}" || -z "${dirs}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -156,13 +156,13 @@ verify_dirs_chmod_chown() { test ! "${user_expected}" == "${user_actual}" && exit_on_error "ERROR: '${dir}' actual owner '${user_actual}' differs from expected '${user_expected}'!" test ! "${group_expected}" == "${group_actual}" && exit_on_error "ERROR: '${dir}' actual group '${group_actual}' differs from expected '${group_expected}'!" done - echo " CHECK" + log " CHECK" } verify_file_contains_string() { local string="$1" local file="$2" - echo " Verify '${string}' found in '${file}'" + log " Verify '${string}' found in '${file}'" if [[ -z "${string}" || -z "${file}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -171,13 +171,13 @@ verify_file_contains_string() { if [[ ! $(grep -iw "${string}" "${file}") ]]; then exit_on_error "ERROR: '${string}' not found in '${file}'" fi - echo " CHECK" + log " CHECK" } verify_file_contains_string_once() { local string="$1" local file="$2" - echo " Verify '${string}' found in '${file}'" + log " Verify '${string}' found in '${file}'" if [[ -z "${string}" || -z "${file}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -189,14 +189,14 @@ verify_file_contains_string_once() { elif [ "$file_contains_string_count" -gt 1 ]; then exit_on_error "ERROR: '${string}' found more than once in '${file}'" fi - echo " CHECK" + log " CHECK" } verify_service_state() { local service="$1" local desired_state="$2" local option="${3:+$3 }" # optional, dont't quote in next call! - echo " Verify service '${option}${service}' is '${desired_state}'" + log " Verify service '${option}${service}' is '${desired_state}'" if [[ -z "${service}" || -z "${desired_state}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -206,14 +206,14 @@ verify_service_state() { if [[ ! "${actual_state}" == "${desired_state}" ]]; then exit_on_error "ERROR: service '${option}${service}' is not '${desired_state}' (state: '${actual_state}')." fi - echo " CHECK" + log " CHECK" } verify_service_enablement() { local service="$1" local desired_enablement="$2" local option="${3:+$3 }" # optional, dont't quote in next call! - echo " Verify service ${option}${service} is ${desired_enablement}" + log " Verify service ${option}${service} is ${desired_enablement}" if [[ -z "${service}" || -z "${desired_enablement}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -223,14 +223,14 @@ verify_service_enablement() { if [[ ! "${actual_enablement}" == "${desired_enablement}" ]]; then exit_on_error "ERROR: service ${option}${service} is not ${desired_enablement} (state: ${actual_enablement})." fi - echo " CHECK" + log " CHECK" } verify_optional_service_enablement() { local service="$1" local desired_enablement="$2" local option="${3:+$3 }" # optional, dont't quote in next call! - echo " Verify service ${option}${service} is ${desired_enablement}" + log " Verify service ${option}${service} is ${desired_enablement}" if [[ -z "${service}" || -z "${desired_enablement}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -238,13 +238,13 @@ verify_optional_service_enablement() { local actual_enablement=$(systemctl is-enabled ${option}${service}) 2>/dev/null if [[ -z "${actual_enablement}" ]]; then - echo " INFO: optional service ${option}${service} is not installed." + log " INFO: optional service ${option}${service} is not installed." elif [[ "${actual_enablement}" == "static" ]]; then - echo " INFO: optional service ${option}${service} is set static." + log " INFO: optional service ${option}${service} is set static." elif [[ ! "${actual_enablement}" == "${desired_enablement}" ]]; then exit_on_error "ERROR: service ${option}${service} is not ${desired_enablement} (state: ${actual_enablement})." fi - echo " CHECK" + log " CHECK" } # Reads a textfile and returns all lines as args. @@ -259,7 +259,7 @@ get_args_from_file() { # Check if all passed packages are installed. Fail on first missing. verify_apt_packages() { local packages="$@" - echo " Verify packages are installed: '${packages}'" + log " Verify packages are installed: '${packages}'" if [[ -z "${packages}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -272,13 +272,13 @@ verify_apt_packages() { exit_on_error "ERROR: ${package} is not installed" fi done - echo " CHECK" + log " CHECK" } # Check if all passed modules are installed. Fail on first missing. verify_pip_modules() { local modules="$@" - echo " Verify modules are installed: '${modules}'" + log " Verify modules are installed: '${modules}'" if [[ -z "${modules}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" @@ -291,5 +291,5 @@ verify_pip_modules() { exit_on_error "ERROR: ${module} is not installed" fi done - echo " CHECK" + log " CHECK" } diff --git a/installation/includes/03_welcome.sh b/installation/includes/03_welcome.sh index 7aeaa56ab..5b3ee84be 100644 --- a/installation/includes/03_welcome.sh +++ b/installation/includes/03_welcome.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash welcome() { - clear 1>&3 - echo "######################################################### + clear_c + print_c "######################################################### # # # ___ __ ______ _ __________ ____ __ _ _ # # / _ \/ // / __ \/ |/ / _/ __/( _ \ / \( \/ ) # @@ -29,16 +29,16 @@ in a separate SSH session: cd; tail -f ${INSTALLATION_LOGFILE} Let's set up your Phoniebox. -Do you want to start the installation? [Y/n]" 1>&3 +Do you want to start the installation? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) exit ;; *) - echo "Starting installation + print_c "Starting installation --------------------- -" 1>&3 +" ;; esac } diff --git a/installation/includes/05_finish.sh b/installation/includes/05_finish.sh index 55489ff46..22ba6ae80 100644 --- a/installation/includes/05_finish.sh +++ b/installation/includes/05_finish.sh @@ -2,7 +2,7 @@ finish() { local local_hostname=$(hostname) - echo -e "####################### FINISHED ######################## + print_lc "####################### FINISHED ######################## Installation complete! @@ -14,19 +14,19 @@ Your SSH connection will disconnect. After the reboot, you can access the WebApp in your browser at http://${local_hostname}.local or http://${CURRENT_IP_ADDRESS} Don't forget to upload files. -" | tee /dev/fd/3 -echo "Do you want to reboot now? [Y/n]" 1>&3 +" +print_c "Do you want to reboot now? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) - echo "Reboot aborted" | tee /dev/fd/3 - echo "DONE: finish" + print_lc "Reboot aborted" + log "DONE: finish" exit ;; *) - echo "Rebooting ..." | tee /dev/fd/3 - echo "DONE: finish" + print_lc "Rebooting ..." + log "DONE: finish" sudo reboot ;; esac diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 90f78800f..84827f99d 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -23,42 +23,12 @@ echo GIT_URL $GIT_URL CURRENT_USER="${SUDO_USER:-$(whoami)}" CURRENT_USER_GROUP=$(id -gn "$CURRENT_USER") HOME_PATH=$(getent passwd "$CURRENT_USER" | cut -d: -f6) -echo "Current User: $CURRENT_USER" -echo "User home dir: $HOME_PATH" INSTALLATION_PATH="${HOME_PATH}/${GIT_REPO_NAME}" INSTALL_ID=$(date +%s) INSTALLATION_LOGFILE="${HOME_PATH}/INSTALL-${INSTALL_ID}.log" -# Check if current distro is a 32 bit version -# Support for 64 bit Distros has not been checked (or precisely: is known not to work) -# All RaspianOS versions report as machine "armv6l" or "armv7l", if 32 bit (even the ARMv8 cores!) -_check_os_type() { - local os_type=$(uname -m) - - echo -e "\nChecking OS type '$os_type'" - - if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then - echo -e " ... OK!\n" - else - echo "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" - echo "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" - exit 1 - fi -} - # Manipulate file descriptor for logging -# Behavior: -# Write To logfile: -# default stdout will only write to logfile -# default stderr will only write to logfile -# e.g echo "write only to logfile" -# Write To console (user window): -# redirect to fd 3 will only write to the console -# e.g. echo "write only to console" 1>&3 -# Write To both: -# use tee to write output to logfile and console -# e.g. echo "write to both" | tee /dev/fd/3 _setup_logging(){ if [ "$CI_RUNNING" == "true" ]; then exec 3>&1 2>&1 @@ -68,40 +38,84 @@ _setup_logging(){ echo "Log start: ${INSTALL_ID}" } +# Function to log to both console and logfile +print_lc() { + local message="$1" + echo -e "$message" | tee /dev/fd/3 +} + +# Function to log to logfile only +log() { + local message="$1" + echo -e "$message" +} + +# Function to run a command where the output will be logged to both console and logfile +run_and_print_lc() { + "$@" | tee /dev/fd/3 +} + +# Function to log to console only +print_c() { + local message="$1" + echo -e "$message" 1>&3 +} + +# Function to clear console screen +clear_c() { + clear 1>&3 +} + # Generic emergency error handler that exits the script immediately # Print additional custom message if passed as first argument # Examples: # a command || exit_on_error # a command || exit_on_error "Execution of command failed" exit_on_error () { - echo -e "\n****************************************" | tee /dev/fd/3 - echo "ERROR OCCURRED! + print_lc "\n****************************************" + print_lc "ERROR OCCURRED! A non-recoverable error occurred. -Check install log for details:" | tee /dev/fd/3 - echo "$INSTALLATION_LOGFILE" | tee /dev/fd/3 - echo "****************************************" | tee /dev/fd/3 +Check install log for details:" + print_lc "$INSTALLATION_LOGFILE" + print_lc "****************************************" if [[ -n $1 ]]; then - echo "$1" | tee /dev/fd/3 - echo "****************************************" | tee /dev/fd/3 + print_lc "$1" + print_lc "****************************************" fi - echo "Abort!" + log "Abort!" exit 1 } +# Check if current distro is a 32 bit version +# Support for 64 bit Distros has not been checked (or precisely: is known not to work) +# All RaspianOS versions report as machine "armv6l" or "armv7l", if 32 bit (even the ARMv8 cores!) +_check_os_type() { + local os_type=$(uname -m) + + print_lc "\nChecking OS type '$os_type'" + + if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then + print_lc " ... OK!\n" + else + print_lc "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" + print_lc "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" + exit 1 + fi +} + _download_jukebox_source() { - echo -e "\n\n" - echo "#########################################################" - echo "Downloading Phoniebox software from Github ..." 1>&3 - echo "Download Source: ${GIT_URL}/${GIT_BRANCH}" | tee /dev/fd/3 + log "#########################################################" + print_c "Downloading Phoniebox software from Github ..." + print_lc "Download Source: ${GIT_URL}/${GIT_BRANCH}" cd "${HOME_PATH}" || exit_on_error "ERROR: Changing to home dir failed." wget -qO- "${GIT_URL}/tarball/${GIT_BRANCH}" | tar xz # Use case insensitive search/sed because user names in Git Hub are case insensitive local git_repo_download=$(find . -maxdepth 1 -type d -iname "${GIT_USER}-${GIT_REPO_NAME}-*") - echo "GIT REPO DOWNLOAD = $git_repo_download" + log "GIT REPO DOWNLOAD = $git_repo_download" GIT_HASH=$(echo "$git_repo_download" | sed -rn "s/.*${GIT_USER}-${GIT_REPO_NAME}-([0-9a-fA-F]+)/\1/ip") # Save the git hash for this particular download for later git repo initialization - echo "GIT HASH = $GIT_HASH" + log "GIT HASH = $GIT_HASH" if [[ -z "${git_repo_download}" ]]; then exit_on_error "ERROR: Couldn't find git download." fi @@ -109,8 +123,8 @@ _download_jukebox_source() { exit_on_error "ERROR: Couldn't determine git hash from download." fi mv "$git_repo_download" "$GIT_REPO_NAME" - echo -e "\nDONE: Downloading Phoniebox software from Github" - echo "#########################################################" + log "\nDONE: Downloading Phoniebox software from Github" + log "#########################################################" } _load_sources() { @@ -124,14 +138,16 @@ _load_sources() { done } +### SETUP LOGGING +_setup_logging ### CHECK PREREQUISITE _check_os_type -### SETUP LOGGING -_setup_logging - ### RUN INSTALLATION +log "Current User: $CURRENT_USER" +log "User home dir: $HOME_PATH" + _download_jukebox_source cd "${INSTALLATION_PATH}" || exit_on_error "ERROR: Changing to install dir failed." _load_sources diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index 1590383d1..4007b88f5 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -8,15 +8,15 @@ _option_static_ip() { CURRENT_GATEWAY=$(echo "${CURRENT_ROUTE}" | awk '{ print $3; exit }') CURRENT_INTERFACE=$(echo "${CURRENT_ROUTE}" | awk '{ print $5; exit }') CURRENT_IP_ADDRESS=$(echo "${CURRENT_ROUTE}" | awk '{ print $7; exit }') - clear 1>&3 - echo "----------------------- STATIC IP ----------------------- + clear_c + print_c "----------------------- STATIC IP ----------------------- Setting a static IP will save a lot of start up time. The static adress will be '${CURRENT_IP_ADDRESS}' from interface '${CURRENT_INTERFACE}' with the gateway '${CURRENT_GATEWAY}'. -Set a static IP? [Y/n]" 1>&3 +Set a static IP? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -25,18 +25,18 @@ Set a static IP? [Y/n]" 1>&3 *) ;; esac - echo "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" + log "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" } _option_ipv6() { # DISABLE_IPv6 - clear 1>&3 - echo "------------------------- IP V6 ------------------------- + clear_c + print_c "------------------------- IP V6 ------------------------- IPv6 is only needed if you intend to use it. Otherwise it can be disabled. -Do you want to disable IPv6? [Y/n]" 1>&3 +Do you want to disable IPv6? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -45,19 +45,19 @@ Do you want to disable IPv6? [Y/n]" 1>&3 *) ;; esac - echo "DISABLE_IPv6=${DISABLE_IPv6}" + log "DISABLE_IPv6=${DISABLE_IPv6}" } _option_autohotspot() { # ENABLE_AUTOHOTSPOT - clear 1>&3 - echo "---------------------- AUTOHOTSPOT ---------------------- + clear_c + print_c "---------------------- AUTOHOTSPOT ---------------------- When enabled, this service spins up a WiFi hotspot when the Phoniebox is unable to connect to a known WiFi. This way you can still access it. -Do you want to enable an Autohotpot? [y/N]" 1>&3 +Do you want to enable an Autohotpot? [y/N]" read -r response case "$response" in [yY][eE][sS]|[yY]) @@ -68,13 +68,13 @@ Do you want to enable an Autohotpot? [y/N]" 1>&3 esac if [ "$ENABLE_AUTOHOTSPOT" = true ]; then - echo "Do you want to set a custom Password? (default: ${AUTOHOTSPOT_PASSWORD}) [y/N] " 1>&3 + print_c "Do you want to set a custom Password? (default: ${AUTOHOTSPOT_PASSWORD}) [y/N] " read -r response_pw_q case "$response_pw_q" in [yY][eE][sS]|[yY]) while [ $(echo ${response_pw}|wc -m) -lt 8 ] do - echo "Please type the new password (at least 8 character)." 1>&3 + print_c "Please type the new password (at least 8 character)." read -r response_pw done AUTOHOTSPOT_PASSWORD="${response_pw}" @@ -84,29 +84,27 @@ Do you want to enable an Autohotpot? [y/N]" 1>&3 esac if [ "$ENABLE_STATIC_IP" = true ]; then - echo "Wifi hotspot cannot be enabled with static IP. Disabling static IP configuration." 1>&3 - echo "--------------------- - " 1>&3 + print_c "Wifi hotspot cannot be enabled with static IP. Disabling static IP configuration." ENABLE_STATIC_IP=false - echo "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" + log "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" fi fi - echo "ENABLE_AUTOHOTSPOT=${ENABLE_AUTOHOTSPOT}" + log "ENABLE_AUTOHOTSPOT=${ENABLE_AUTOHOTSPOT}" if [ "$ENABLE_AUTOHOTSPOT" = true ]; then - echo "AUTOHOTSPOT_PASSWORD=${AUTOHOTSPOT_PASSWORD}" + log "AUTOHOTSPOT_PASSWORD=${AUTOHOTSPOT_PASSWORD}" fi } _option_bluetooth() { # DISABLE_BLUETOOTH - clear 1>&3 - echo "----------------------- BLUETOOTH ----------------------- + clear_c + print_c "----------------------- BLUETOOTH ----------------------- Turning off Bluetooth will save energy and start up time, if you do not plan to use it. -Do you want to disable Bluetooth? [Y/n]" 1>&3 +Do you want to disable Bluetooth? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -115,18 +113,18 @@ Do you want to disable Bluetooth? [Y/n]" 1>&3 *) ;; esac - echo "DISABLE_BLUETOOTH=${DISABLE_BLUETOOTH}" + log "DISABLE_BLUETOOTH=${DISABLE_BLUETOOTH}" } _option_mpd() { - clear 1>&3 + clear_c if [[ "$SETUP_MPD" == true ]]; then if [[ -f "${MPD_CONF_PATH}" || -f "${SYSTEMD_USR_PATH}/mpd.service" ]]; then - echo "-------------------------- MPD -------------------------- + print_c "-------------------------- MPD -------------------------- It seems there is a MPD already installed. Note: It is important that MPD runs as a user service! -Would you like to overwrite your configuration? [Y/n]" 1>&3 +Would you like to overwrite your configuration? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -138,23 +136,23 @@ Would you like to overwrite your configuration? [Y/n]" 1>&3 fi fi - echo "SETUP_MPD=${SETUP_MPD}" + log "SETUP_MPD=${SETUP_MPD}" if [ "$SETUP_MPD" == true ]; then - echo "ENABLE_MPD_OVERWRITE_INSTALL=${ENABLE_MPD_OVERWRITE_INSTALL}" + log "ENABLE_MPD_OVERWRITE_INSTALL=${ENABLE_MPD_OVERWRITE_INSTALL}" fi } _option_rfid_reader() { # ENABLE_RFID_READER - clear 1>&3 - echo "---------------------- RFID READER ---------------------- + clear_c + print_c "---------------------- RFID READER ---------------------- Phoniebox can be controlled with rfid cards/tags, if you have a rfid reader connected. Choose yes to setup a reader. You get prompted for the type selection and configuration later on. -Do you want to setup a rfid reader? [Y/n]" 1>&3 +Do you want to setup a rfid reader? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -163,20 +161,20 @@ Do you want to setup a rfid reader? [Y/n]" 1>&3 *) ;; esac - echo "ENABLE_RFID_READER=${ENABLE_RFID_READER}" + log "ENABLE_RFID_READER=${ENABLE_RFID_READER}" } _option_samba() { # ENABLE_SAMBA - clear 1>&3 - echo "------------------------- SAMBA ------------------------- + clear_c + print_c "------------------------- SAMBA ------------------------- Samba is required to conveniently copy files to your Phoniebox via a network share. If you don't need it, feel free to skip the installation. If you are unsure, stick to YES! -Do you want to install Samba? [Y/n]" 1>&3 +Do you want to install Samba? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -185,18 +183,18 @@ Do you want to install Samba? [Y/n]" 1>&3 *) ;; esac - echo "ENABLE_SAMBA=${ENABLE_SAMBA}" + log "ENABLE_SAMBA=${ENABLE_SAMBA}" } _option_webapp() { # ENABLE_WEBAPP - clear 1>&3 - echo "------------------------ WEBAPP ------------------------- + clear_c + print_c "------------------------ WEBAPP ------------------------- This is only required if you want to use a graphical interface to manage your Phoniebox! -Would you like to install the web application? [Y/n]" 1>&3 +Would you like to install the web application? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -206,20 +204,20 @@ Would you like to install the web application? [Y/n]" 1>&3 *) ;; esac - echo "ENABLE_WEBAPP=${ENABLE_WEBAPP}" + log "ENABLE_WEBAPP=${ENABLE_WEBAPP}" } _option_kiosk_mode() { # ENABLE_KIOSK_MODE - clear 1>&3 - echo "----------------------- KIOSK MODE ---------------------- + clear_c + print_c "----------------------- KIOSK MODE ---------------------- If you have a screen attached to your RPi, this will launch the web application right after boot. It will only install the necessary xserver dependencies and not the entire RPi desktop environment. -Would you like to enable the Kiosk Mode? [y/N]" 1>&3 +Would you like to enable the Kiosk Mode? [y/N]" read -r response case "$response" in [yY][eE][sS]|[yY]) @@ -228,18 +226,18 @@ Would you like to enable the Kiosk Mode? [y/N]" 1>&3 *) ;; esac - echo "ENABLE_KIOSK_MODE=${ENABLE_KIOSK_MODE}" + log "ENABLE_KIOSK_MODE=${ENABLE_KIOSK_MODE}" } _options_update_raspi_os() { # UPDATE_RASPI_OS - clear 1>&3 - echo "----------------------- UPDATE OS ----------------------- + clear_c + print_c "----------------------- UPDATE OS ----------------------- This shall be done eventually, but increases the installation time a lot. -Would you like to update the operating system? [Y/n]" 1>&3 +Would you like to update the operating system? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -248,14 +246,14 @@ Would you like to update the operating system? [Y/n]" 1>&3 *) ;; esac - echo "UPDATE_RASPI_OS=${UPDATE_RASPI_OS}" + log "UPDATE_RASPI_OS=${UPDATE_RASPI_OS}" } _option_disable_onboard_audio() { # Disable BCM on-chip audio (typically Headphones) # not needed when external sound card is sued - clear 1>&3 - echo "--------------------- ON-CHIP AUDIO --------------------- + clear_c + print_c "--------------------- ON-CHIP AUDIO --------------------- If you are using an external sound card (e.g. USB, HifiBerry, PirateAudio, etc), we recommend to disable @@ -269,7 +267,7 @@ We will do our best not to mess anything up. However, a backup copy will be written to ${DISABLE_ONBOARD_AUDIO_BACKUP} ) -Disable Pi's on-chip audio (headphone / jack output)? [y/N]" 1>&3 +Disable Pi's on-chip audio (headphone / jack output)? [y/N]" read -r response case "$response" in [yY][eE][sS]|[yY]) @@ -278,7 +276,7 @@ Disable Pi's on-chip audio (headphone / jack output)? [y/N]" 1>&3 *) ;; esac - echo "DISABLE_ONBOARD_AUDIO=${DISABLE_ONBOARD_AUDIO}" + log "DISABLE_ONBOARD_AUDIO=${DISABLE_ONBOARD_AUDIO}" } @@ -291,14 +289,14 @@ _option_webapp_devel_build() { ENABLE_WEBAPP_PROD_DOWNLOAD=false fi if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == false ]]; then - clear 1>&3 - echo "--------------------- WEBAPP NODE --------------------- + clear_c + print_c "--------------------- WEBAPP NODE --------------------- You are installing from a non-release branch. This means, you will need to build the web app locally. For that you'll need Node. -Do you want to install Node? [Y/n]" 1>&3 +Do you want to install Node? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -316,6 +314,11 @@ Do you want to install Node? [Y/n]" 1>&3 FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" fi fi + + log "ENABLE_INSTALL_NODE=${ENABLE_INSTALL_NODE}" + if [ "$ENABLE_INSTALL_NODE" != true ]; then + log "ENABLE_WEBAPP_PROD_DOWNLOAD=${ENABLE_WEBAPP_PROD_DOWNLOAD}" + fi } _run_customize_options() { diff --git a/installation/routines/install.sh b/installation/routines/install.sh index d241658b6..62d602f17 100644 --- a/installation/routines/install.sh +++ b/installation/routines/install.sh @@ -1,7 +1,7 @@ install() { - clear 1>&3 + clear_c customize_options - clear 1>&3 + clear_c set_raspi_config set_ssh_qos update_raspi_os diff --git a/installation/routines/optimize_boot_time.sh b/installation/routines/optimize_boot_time.sh index 383f790c0..bb6f71902 100644 --- a/installation/routines/optimize_boot_time.sh +++ b/installation/routines/optimize_boot_time.sh @@ -9,17 +9,17 @@ OPTIMIZE_IPV6_CONF_HEADER="## Jukebox IPV6 Config" OPTIMIZE_BOOT_CONF_HEADER="## Jukebox Boot Config" _optimize_disable_irrelevant_services() { - echo " Disable keyboard-setup.service" + log " Disable keyboard-setup.service" sudo systemctl disable keyboard-setup.service - echo " Disable triggerhappy.service" + log " Disable triggerhappy.service" sudo systemctl disable triggerhappy.service sudo systemctl disable triggerhappy.socket - echo " Disable raspi-config.service" + log " Disable raspi-config.service" sudo systemctl disable raspi-config.service - echo " Disable apt-daily.service & apt-daily-upgrade.service" + log " Disable apt-daily.service & apt-daily-upgrade.service" sudo systemctl disable apt-daily.service sudo systemctl disable apt-daily-upgrade.service sudo systemctl disable apt-daily.timer @@ -29,7 +29,7 @@ _optimize_disable_irrelevant_services() { # TODO: If false, actually make sure bluetooth is enabled _optimize_handle_bluetooth() { if [ "$DISABLE_BLUETOOTH" = true ] ; then - echo " Disable bluetooth" | tee /dev/fd/3 + print_lc " Disable bluetooth" sudo systemctl disable hciuart.service sudo systemctl disable bluetooth.service fi @@ -39,14 +39,14 @@ _optimize_handle_bluetooth() { _optimize_static_ip() { # Static IP Address and DHCP optimizations if [ "$ENABLE_STATIC_IP" = true ] ; then - echo " Set static IP address" | tee /dev/fd/3 + print_lc " Set static IP address" if grep -q "${OPTIMIZE_DHCP_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then - echo " Skipping. Already set up!" + log " Skipping. Already set up!" else # DHCP has not been configured - echo " ${CURRENT_INTERFACE} is the default network interface" - echo " ${CURRENT_GATEWAY} is the Router Gateway address" - echo " Using ${CURRENT_IP_ADDRESS} as the static IP for now" + log " ${CURRENT_INTERFACE} is the default network interface" + log " ${CURRENT_GATEWAY} is the Router Gateway address" + log " Using ${CURRENT_IP_ADDRESS} as the static IP for now" sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF @@ -65,9 +65,9 @@ EOF # TODO: Allow both Enable and Disable _optimize_ipv6_arp() { if [ "$DISABLE_IPv6" = true ] ; then - echo " Disabling IPV6" | tee /dev/fd/3 + print_lc " Disabling IPV6" if grep -q "${OPTIMIZE_IPV6_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then - echo " Skipping. Already set up!" + log " Skipping. Already set up!" else sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF @@ -84,9 +84,9 @@ EOF # TODO: Allow both Enable and Disable _optimize_handle_boot_screen() { if [ "$DISABLE_BOOT_SCREEN" = true ] ; then - echo " Disable RPi rainbow screen" + log " Disable RPi rainbow screen" if grep -q "${OPTIMIZE_BOOT_CONF_HEADER}" "$RPI_BOOT_CONFIG_FILE"; then - echo " Skipping. Already set up!" + log " Skipping. Already set up!" else sudo tee -a $RPI_BOOT_CONFIG_FILE <<-EOF @@ -101,7 +101,7 @@ EOF # TODO: Allow both Enable and Disable _optimize_handle_boot_logs() { if [ "$DISABLE_BOOT_LOGS_PRINT" = true ] ; then - echo " Disable boot logs" + log " Disable boot logs" if [ ! -s "${RPI_BOOT_CMDLINE_FILE}" ];then sudo tee "${RPI_BOOT_CMDLINE_FILE}" <<-EOF diff --git a/installation/routines/set_raspi_config.sh b/installation/routines/set_raspi_config.sh index a9cb7b6f1..7f39a0ba5 100644 --- a/installation/routines/set_raspi_config.sh +++ b/installation/routines/set_raspi_config.sh @@ -4,26 +4,26 @@ _run_set_raspi_config() { # Source: https://raspberrypi.stackexchange.com/a/66939 # Autologin - echo " Enable Autologin for user" + log " Enable Autologin for user" sudo raspi-config nonint do_boot_behaviour B2 # Wait for network at boot - # echo " Enable 'Wait for network at boot'" + # log " Enable 'Wait for network at boot'" # sudo raspi-config nonint do_boot_wait 1 # power management of wifi: switch off to avoid disconnecting - echo " Disable Wifi power management to avoid disconnecting" + log " Disable Wifi power management to avoid disconnecting" sudo iwconfig wlan0 power off # On-board audio if [ "$DISABLE_ONBOARD_AUDIO" == true ]; then - echo " Disable on-chip BCM audio" + log " Disable on-chip BCM audio" if grep -q -E "^dtparam=([^,]*,)*audio=(on|true|yes|1).*" "${RPI_BOOT_CONFIG_FILE}" ; then - echo " Backup ${RPI_BOOT_CONFIG_FILE} --> ${DISABLE_ONBOARD_AUDIO_BACKUP}" + log " Backup ${RPI_BOOT_CONFIG_FILE} --> ${DISABLE_ONBOARD_AUDIO_BACKUP}" sudo cp "${RPI_BOOT_CONFIG_FILE}" "${DISABLE_ONBOARD_AUDIO_BACKUP}" sudo sed -i "s/^\(dtparam=\([^,]*,\)*\)audio=\(on\|true\|yes\|1\)\(.*\)/\1audio=off\4/g" "${RPI_BOOT_CONFIG_FILE}" else - echo " On board audio seems to be off already. Not touching ${RPI_BOOT_CONFIG_FILE}" + log " On board audio seems to be off already. Not touching ${RPI_BOOT_CONFIG_FILE}" fi fi } diff --git a/installation/routines/set_ssh_qos.sh b/installation/routines/set_ssh_qos.sh index eaca62fed..76242f712 100644 --- a/installation/routines/set_ssh_qos.sh +++ b/installation/routines/set_ssh_qos.sh @@ -4,7 +4,7 @@ set_ssh_qos() { if [ "$DISABLE_SSH_QOS" == true ] ; then # The latest version of SSH installed on the Raspberry Pi 3 uses QoS headers, which disagrees with some # routers and other hardware. This causes immense delays when remotely accessing the RPi over ssh. - echo " Set SSH QoS to best effort" + log " Set SSH QoS to best effort" echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/sshd_config echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/ssh_config fi diff --git a/installation/routines/setup_git.sh b/installation/routines/setup_git.sh index 613062f43..c04ff7acf 100644 --- a/installation/routines/setup_git.sh +++ b/installation/routines/setup_git.sh @@ -2,7 +2,7 @@ GIT_ABORT_MSG="Aborting dir to git repo conversion. Your directory content is untouched, you simply cannot use git for updating / developing" _git_install_os_dependencies() { - echo " Install Git dependencies" + log " Install Git dependencies" sudo apt-get -y update; sudo apt-get -y install \ git \ --no-install-recommends \ @@ -12,9 +12,9 @@ _git_install_os_dependencies() { } _git_convert_tardir_git_repo() { - echo "****************************************************" - echo "*** Converting tar-ball download into git repository" - echo "****************************************************" + log "**************************************************** +*** Converting tar-ball download into git repository +****************************************************" # Just in case, the git version is not new enough, we split up git init -b "${GIT_BRANCH}" into: git -c init.defaultBranch=main init @@ -30,21 +30,19 @@ _git_convert_tardir_git_repo() { # We simply get everything from the beginning of future 3 development but excluding Version 2.X if [[ $GIT_USE_SSH == true ]]; then git remote add origin "git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git" - echo "" - echo "*** Git fetch (SSH) *******************************" + log "\n*** Git fetch (SSH) *******************************" # Prevent: The authenticity of host 'github.com (140.82.121.4)' can't be established. # Do only for this one command, so we do not disable the checks forever if ! git -c core.sshCommand='ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' fetch origin "${GIT_BRANCH}" --set-upstream --shallow-since=2021-04-21 --tags; then - echo "" - echo "*** NOTICE *****************************************" - echo "* Error in getting Git Repository using SSH! USING FALLBACK HTTPS." - echo "* Note: This is only relevant for developers!" - echo "* Did you forget to upload the ssh key for this machine to GitHub?" - echo "* Defaulting to HTTPS protocol. You can change back to SSH later with" - echo "* git remote set-url origin git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git" - echo "* git remote set-url upstream git@github.com:${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git" - echo "" + log "\n*** NOTICE ***************************************** +* Error in getting Git Repository using SSH! USING FALLBACK HTTPS. +* Note: This is only relevant for developers! +* Did you forget to upload the ssh key for this machine to GitHub? +* Defaulting to HTTPS protocol. You can change back to SSH later with +* git remote set-url origin git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git +* git remote set-url upstream git@github.com:${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git\n" + git remote remove origin GIT_USE_SSH=false else @@ -60,32 +58,31 @@ _git_convert_tardir_git_repo() { if [[ "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then git remote add upstream "https://github.com/${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git" fi - echo "" - echo "*** Git fetch (HTTPS) *****************************" + log "\n*** Git fetch (HTTPS) *****************************" if ! git fetch origin --set-upstream --shallow-since=2021-04-21 --tags "${GIT_BRANCH}"; then - echo "Error: Could not fetch repository!" - echo -e "$GIT_ABORT_MSG" + log "Error: Could not fetch repository!" + log "$GIT_ABORT_MSG" return fi fi HASH_BRANCH=$(git rev-parse FETCH_HEAD) || { echo -e "$GIT_ABORT_MSG"; return; } - echo "" - echo "*** FETCH_HEAD ($GIT_BRANCH) = $HASH_BRANCH" + + log "\n*** FETCH_HEAD ($GIT_BRANCH) = $HASH_BRANCH" git add . # Checkout the exact hash that we have downloaded as tarball - echo "*** Git checkout commit" + log "*** Git checkout commit" git -c advice.detachedHead=false checkout "$GIT_HASH" || { echo -e "$GIT_ABORT_MSG"; return; } HASH_HEAD=$(git rev-parse HEAD) || { echo -e "$GIT_ABORT_MSG"; return; } - echo "*** REQUESTED COMMIT = $HASH_HEAD" + log "*** REQUESTED COMMIT = $HASH_HEAD" # Let's move onto the relevant branch, WITHOUT touching the current checked-out commit # Since we have fetched with --set-upstream above this initializes the tracking branch - echo "*** Git initialize branch" + log "*** Git initialize branch" git checkout -b "$GIT_BRANCH" if [[ "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then - echo "*** Get upstream release tags" + log "*** Get upstream release tags" # Always get the upstream release branch to get all release tags # in case they have not been copied to user repository git fetch upstream --shallow-since=2021-04-21 --tags "${GIT_BRANCH_RELEASE}" @@ -101,7 +98,7 @@ _git_convert_tardir_git_repo() { if [[ $GIT_BRANCH != "${GIT_BRANCH_RELEASE}" ]]; then OUTPUT=$(git fetch origin --shallow-since=2021-04-21 --tags "${GIT_BRANCH_RELEASE}" 2>&1) if [[ $? -ne 128 ]]; then - echo "*** Preparing ${GIT_BRANCH_RELEASE} in background" + log "*** Preparing ${GIT_BRANCH_RELEASE} in background" echo -e "$OUTPUT" fi unset OUTPUT @@ -109,7 +106,7 @@ _git_convert_tardir_git_repo() { if [[ $GIT_BRANCH != "${GIT_BRANCH_DEVELOP}" ]]; then OUTPUT=$(git fetch origin --shallow-since=2021-04-21 --tags "${GIT_BRANCH_DEVELOP}" 2>&1) if [[ $? -ne 128 ]]; then - echo "*** Preparing ${GIT_BRANCH_DEVELOP} in background" + log "*** Preparing ${GIT_BRANCH_DEVELOP} in background" echo -e "$OUTPUT" fi unset OUTPUT @@ -117,25 +114,24 @@ _git_convert_tardir_git_repo() { # Provide some status outputs to the user if [[ "${HASH_BRANCH}" != "${HASH_HEAD}" ]]; then - echo "" - echo "*** IMPORTANT NOTICE *******************************" - echo "* Your requested branch has moved on while you were installing." - echo "* Don't worry! We will stay within the exact download version!" - echo "* But we set up the git repo to be ready for updating." - echo "* To start updating (observe updating guidelines!), do:" - echo "* $ git pull origin $GIT_BRANCH" - echo "" + log "\n*** IMPORTANT NOTICE ******************************* +* Your requested branch has moved on while you were installing. +* Don't worry! We will stay within the exact download version! +* But we set up the git repo to be ready for updating. +* To start updating (observe updating guidelines!), do: +* $ git pull origin $GIT_BRANCH\n" + fi - echo "*** Git remotes ************************************" + log "*** Git remotes ************************************" git remote -v - echo "*** Git status *************************************" + log "*** Git status *************************************" git status -sb - echo "*** Git log ****************************************" + log "*** Git log ****************************************" git log --oneline "HEAD^..origin/$GIT_BRANCH" - echo "*** Git describe ***********************************" + log "*** Git describe ***********************************" git describe --tag --dirty='-dirty' - echo "****************************************************" + log "****************************************************" cp -f .githooks/* .git/hooks diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 6b070ff6c..a7d0f29b6 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -9,16 +9,16 @@ JUKEBOX_PULSE_CONFIG="${HOME_PATH}"/.config/pulse/default.pa JUKEBOX_SERVICE_NAME="${SYSTEMD_USR_PATH}/jukebox-daemon.service" _show_slow_hardware_message() { -echo " -------------------------------------------------------------------- + print_c " -------------------------------------------------------------------- | Your hardware is a little slower so this step will take a while. | | Go watch a movie but don't let your computer go to sleep for the | | SSH connection to remain intact. | - --------------------------------------------------------------------" 1>&3 + --------------------------------------------------------------------" } # Functions _jukebox_core_install_os_dependencies() { - echo " Install Jukebox OS dependencies" | tee /dev/fd/3 + print_lc " Install Jukebox OS dependencies" local apt_packages=$(get_args_from_file "${INSTALLATION_PATH}/packages-core.txt") sudo apt-get -y update && sudo apt-get -y install \ @@ -30,7 +30,7 @@ _jukebox_core_install_os_dependencies() { } _jukebox_core_install_python_requirements() { - echo " Install Python requirements" | tee /dev/fd/3 + print_lc " Install Python requirements" cd "${INSTALLATION_PATH}" || exit_on_error @@ -42,13 +42,13 @@ _jukebox_core_install_python_requirements() { } _jukebox_core_configure_pulseaudio() { - echo " Copy PulseAudio configuration" | tee /dev/fd/3 + print_lc " Copy PulseAudio configuration" mkdir -p $(dirname "$JUKEBOX_PULSE_CONFIG") cp -f "${INSTALLATION_PATH}/resources/default-settings/pulseaudio.default.pa" "${JUKEBOX_PULSE_CONFIG}" } _jukebox_core_build_libzmq_with_drafts() { - echo " Building libzmq v${JUKEBOX_ZMQ_VERSION} with drafts support" | tee /dev/fd/3 + print_lc " Building libzmq v${JUKEBOX_ZMQ_VERSION} with drafts support" local zmq_filename="zeromq-${JUKEBOX_ZMQ_VERSION}" local zmq_tar_filename="${zmq_filename}.tar.gz" local cpu_count=${CPU_COUNT:-$(python3 -c "import os; print(os.cpu_count())")} @@ -63,7 +63,7 @@ _jukebox_core_build_libzmq_with_drafts() { } _jukebox_core_download_prebuilt_libzmq_with_drafts() { - echo " Download pre-compiled libzmq with drafts support" + log " Download pre-compiled libzmq with drafts support" local zmq_tar_filename="libzmq.tar.gz" ARCH=$(get_architecture) @@ -83,7 +83,7 @@ _jukebox_core_build_and_install_pyzmq() { # https://pyzmq.readthedocs.io/en/latest/howto/draft.html # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md # https://github.com/zeromq/pyzmq/issues/1523#issuecomment-1593120264 - echo " Install pyzmq with libzmq-drafts to support WebSockets" | tee /dev/fd/3 + print_lc " Install pyzmq with libzmq-drafts to support WebSockets" if ! pip list | grep -F pyzmq >> /dev/null; then @@ -101,18 +101,18 @@ _jukebox_core_build_and_install_pyzmq() { ZMQ_PREFIX="${JUKEBOX_ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ pip install -v --no-binary pyzmq --pre pyzmq else - echo " Skipping. pyzmq already installed" | tee /dev/fd/3 + print_lc " Skipping. pyzmq already installed" fi } _jukebox_core_install_settings() { - echo " Register Jukebox settings" | tee /dev/fd/3 + print_lc " Register Jukebox settings" cp -f "${INSTALLATION_PATH}/resources/default-settings/jukebox.default.yaml" "${SETTINGS_PATH}/jukebox.yaml" cp -f "${INSTALLATION_PATH}/resources/default-settings/logger.default.yaml" "${SETTINGS_PATH}/logger.yaml" } _jukebox_core_register_as_service() { - echo " Register Jukebox Core user service" | tee /dev/fd/3 + print_lc " Register Jukebox Core user service" sudo cp -f "${INSTALLATION_PATH}/resources/default-services/jukebox-daemon.service" "${JUKEBOX_SERVICE_NAME}" sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${JUKEBOX_SERVICE_NAME}" diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 9dc74bf12..f7407f96c 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -15,13 +15,13 @@ _jukebox_webapp_install_node() { sudo apt-get -y update if which node > /dev/null; then - echo " Found existing NodeJS. Hence, updating NodeJS" | tee /dev/fd/3 + print_lc " Found existing NodeJS. Hence, updating NodeJS" sudo npm cache clean -f sudo npm install --silent -g n sudo n --quiet latest sudo npm update --silent -g else - echo " Install NodeJS" | tee /dev/fd/3 + print_lc " Install NodeJS" # Zero and older versions of Pi with ARMv6 only # support experimental NodeJS @@ -45,7 +45,7 @@ _jukebox_webapp_install_node() { # TODO: Avoid building the app locally # Instead implement a Github Action that prebuilds on commititung a git tag _jukebox_webapp_build() { - echo " Building web application" | tee /dev/fd/3 + print_lc " Building web application" cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error npm ci --prefer-offline --no-audit --production rm -rf build @@ -54,11 +54,11 @@ _jukebox_webapp_build() { } _jukebox_webapp_download() { - echo " Downloading web application" | tee /dev/fd/3 + print_lc " Downloading web application" local JUKEBOX_VERSION=$(get_version_string "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") local TAR_FILENAME="webapp-build.tar.gz" local DOWNLOAD_URL="https://github.com/MiczFlor/RPi-Jukebox-RFID/releases/download/v${JUKEBOX_VERSION}/webapp-v${JUKEBOX_VERSION}.tar.gz" - echo " DOWNLOAD_URL: ${DOWNLOAD_URL}" + log " DOWNLOAD_URL: ${DOWNLOAD_URL}" cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error # URL must be set to default repo as installation can be run from different repos as well where releases may not exist @@ -69,7 +69,7 @@ _jukebox_webapp_download() { } _jukebox_webapp_register_as_system_service_with_nginx() { - echo " Install and configure nginx" | tee /dev/fd/3 + print_lc " Install and configure nginx" sudo apt-get -qq -y update sudo apt-get -y purge apache2 sudo apt-get -y install nginx diff --git a/installation/routines/setup_kiosk_mode.sh b/installation/routines/setup_kiosk_mode.sh index f8d07971c..b6e543768 100644 --- a/installation/routines/setup_kiosk_mode.sh +++ b/installation/routines/setup_kiosk_mode.sh @@ -7,7 +7,7 @@ KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK='/etc/chromium-browser/customiza KIOSK_MODE_CHROMIUM_FLAG_UPDATE_INTERVAL='--check-for-update-interval=31536000' _kiosk_mode_install_os_dependencies() { - echo " Install Kiosk Mode dependencies" | tee /dev/fd/3 + print_lc " Install Kiosk Mode dependencies" # Resource: # https://blog.r0b.io/post/minimal-rpi-kiosk/ sudo apt-get -qq -y install --no-install-recommends \ @@ -19,7 +19,7 @@ _kiosk_mode_install_os_dependencies() { } _kiosk_mode_set_autostart() { - echo " Configure Kiosk Mode" | tee /dev/fd/3 + print_lc " Configure Kiosk Mode" local _DISPLAY='$DISPLAY' local _XDG_VTNR='$XDG_VTNR' diff --git a/installation/routines/setup_mpd.sh b/installation/routines/setup_mpd.sh index 40ffe8aac..6a95a95e5 100644 --- a/installation/routines/setup_mpd.sh +++ b/installation/routines/setup_mpd.sh @@ -4,11 +4,11 @@ AUDIOFOLDERS_PATH="${SHARED_PATH}/audiofolders" PLAYLISTS_PATH="${SHARED_PATH}/playlists" _mpd_install_os_dependencies() { - echo " Install MPD OS dependencies" + log " Install MPD OS dependencies" sudo apt-get -y update - echo "Note: Installing MPD might cause a message: 'Job failed. See journalctl -xe for details'" - echo "It can be ignored! It's an artefact of the MPD installation - nothing we can do about it." + log "Note: Installing MPD might cause a message: 'Job failed. See journalctl -xe for details' +It can be ignored! It's an artefact of the MPD installation - nothing we can do about it." sudo apt-get -y install \ mpd mpc \ --no-install-recommends \ @@ -18,7 +18,7 @@ _mpd_install_os_dependencies() { } _mpd_configure() { - echo " Configure MPD as user local service" | tee /dev/fd/3 + print_lc " Configure MPD as user local service" # Make sure system-wide mpd is disabled sudo systemctl stop mpd.socket diff --git a/installation/routines/setup_rfid_reader.sh b/installation/routines/setup_rfid_reader.sh index b5e8b4bbc..3003d79a4 100644 --- a/installation/routines/setup_rfid_reader.sh +++ b/installation/routines/setup_rfid_reader.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash _run_setup_rfid_reader() { - python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" | tee /dev/fd/3 + run_and_print_lc python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" } setup_rfid_reader() { diff --git a/installation/routines/setup_samba.sh b/installation/routines/setup_samba.sh index 100d7370d..c1875113e 100644 --- a/installation/routines/setup_samba.sh +++ b/installation/routines/setup_samba.sh @@ -4,7 +4,7 @@ SMB_CONF="/etc/samba/smb.conf" SMB_CONF_HEADER="## Jukebox Samba Config" _samba_install_os_dependencies() { - echo " Install Samba Core dependencies" + log " Install Samba Core dependencies" sudo apt-get -qq -y update; sudo apt-get -qq -y install \ samba samba-common-bin \ --no-install-recommends \ @@ -14,12 +14,12 @@ _samba_install_os_dependencies() { } _samba_set_user() { - echo " Configure Samba" | tee /dev/fd/3 + print_lc " Configure Samba" local SMB_PASSWD="raspberry" # Samba has not been configured if grep -q "$SMB_CONF_HEADER" "$SMB_CONF"; then - echo " Skipping. Already set up!" | tee /dev/fd/3 + print_lc " Skipping. Already set up!" else # Create Samba user (echo "${SMB_PASSWD}"; echo "${SMB_PASSWD}") | sudo smbpasswd -s -a "${CURRENT_USER}" From 7250211502ef8bb5b5f8c4c289c0c2e519844fe8 Mon Sep 17 00:00:00 2001 From: s-martin Date: Mon, 11 Dec 2023 21:18:13 +0100 Subject: [PATCH 053/121] Minor doc improvements (#2149) * some improvements in the docs * improve docs * Revert change * Fix whitespace * Fix deactivate command vor venv --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --- documentation/builders/audio.md | 6 +++--- documentation/builders/installation.md | 8 +++++--- documentation/developers/pyhton.md | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/documentation/builders/audio.md b/documentation/builders/audio.md index 0bb5a7163..4fd82e457 100644 --- a/documentation/builders/audio.md +++ b/documentation/builders/audio.md @@ -13,14 +13,14 @@ Audio outputs run via PulseAudio and the basic configuration should be easy. There is a [configuration tool](../developers/coreapps.md#Audio), to setup the configuration for the Jukebox Core App. -To set up the audio +### To set up the audio 1. Follow the setup steps according to your sound card 2. Check that the sound output works [as described below](audio.md#checking-system-sound-output) 3. Run the [audio configuration tool](../developers/coreapps.md#Audio) 4. [Fine-tune audio parameters](audio.md#additional-options) -## Checking system sound output +#### Checking system sound output Run the following steps in a console: @@ -53,7 +53,7 @@ volume level for this sink: $ paplay -d /usr/share/sounds/alsa/Front_Center.wav ``` -# Bluetooth +## Bluetooth Bluetooth setup consists of three steps diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index fadc77aac..756e2a03c 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -85,22 +85,24 @@ Run the following command in your SSH terminal and follow the instructions cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh) ``` -This will get the latest stable release from the branch future3/main. +This will get the latest **stable release** from the branch *future3/main*. + To install directly from a specific branch and/or a different repository specify the variables like this: ```bash - cd; GIT_USER='MiczFlor' GIT_BRANCH='future3/develop' bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) ``` This will switch directly to the specified feature branch during installation. > [!NOTE] -> For all branches *except* the current Release, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Steps to install](../developers/development-environment.md#steps-to-install) +> For all branches *except* the current Release future3/main, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Developer steps to install](../developers/development-environment.md#steps-to-install) If you suspect an error you can monitor the installation-process with ```bash cd; tail -f INSTALL-.log ``` + +After successful installation, continue with [configuring your Phoniebox](configuration.md). diff --git a/documentation/developers/pyhton.md b/documentation/developers/pyhton.md index e9a071e34..31659ef3c 100644 --- a/documentation/developers/pyhton.md +++ b/documentation/developers/pyhton.md @@ -8,11 +8,11 @@ Before you can run Python code, you need to enable the virtual environment. On the Raspberry Pi, it's located in the project root `~/RPi-Jukebox-RFID/.venv`. Depending on your setup, the absolute path can vary. ``` -$ ~/RPi-Jukebox-RFID/.venv/bin/activate +$ source ~/RPi-Jukebox-RFID/.venv/bin/activate ``` If the virtual environment has been activated correctly, your terminal will now show a prefix (`.venv`). If you want to leave the venv again execute deactivate. ``` -$ ~/RPi-Jukebox-RFID/.venv/bin/deactivate +$ deactivate ``` From b8b68c8f565001af7f06127caaff98d46e4adffc Mon Sep 17 00:00:00 2001 From: s-martin Date: Mon, 11 Dec 2023 21:22:04 +0100 Subject: [PATCH 054/121] Add coverage badge (#2153) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ada3e8b3..5824bebbb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/MiczFlor/RPi-Jukebox-RFID/future3/develop) -[![Test Install Scripts Debian v3](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml) [![Python + Docs Checks and Tests](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml) +[![Test Install Scripts Debian v3](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml) [![Python + Docs Checks and Tests](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml) [![Coverage Status](https://coveralls.io/repos/github/MiczFlor/RPi-Jukebox-RFID/badge.svg?branch=future3/develop)](https://coveralls.io/github/MiczFlor/RPi-Jukebox-RFID?branch=future3/develop) [![Matrix chat](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#phoniebox_community:gitter.im) From 24423c36d26c4b9e2656870d9fd5a27c22eb6e65 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Mon, 11 Dec 2023 23:49:20 +0100 Subject: [PATCH 055/121] Update version to 3.4.0 --- src/jukebox/jukebox/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index de575e0b4..03fc34a66 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,6 +1,6 @@ VERSION_MAJOR = 3 -VERSION_MINOR = 3 +VERSION_MINOR = 4 VERSION_PATCH = 0 VERSION_EXTRA = "" From 6ebfba550815376d02c29d994df3898573bc40b9 Mon Sep 17 00:00:00 2001 From: s-martin Date: Wed, 13 Dec 2023 21:32:57 +0100 Subject: [PATCH 056/121] Ignore test directories for coverage (#2158) * ignore test dir for coverage * trigger a py file change * don't calculate coverage for test files itself * Rename . coveragerc to .coveragerc * Use coveragerc --- .coveragerc | 2 ++ .github/workflows/pythonpackage_future3.yml | 4 ++-- src/jukebox/misc/loggingext.py | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..d057a8b57 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = test/* diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index 3834d83db..f2174d0c0 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -39,9 +39,9 @@ jobs: pip3 install -r src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt pip3 install -r src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt pip3 install -r src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt - - name: Run pytest + - name: Run pytest with coverage run: | - ./run_pytest.sh --cov --cov-report xml + ./run_pytest.sh --cov --cov-report xml --cov-config=.coveragerc - name: Report to Coveralls (parallel) uses: coverallsapp/github-action@v2 with: diff --git a/src/jukebox/misc/loggingext.py b/src/jukebox/misc/loggingext.py index 771248d8f..9328cfea8 100644 --- a/src/jukebox/misc/loggingext.py +++ b/src/jukebox/misc/loggingext.py @@ -1,7 +1,6 @@ """ ############## Logger - ############## We use a hierarchical Logger structure based on pythons logging module. It can be finely configured with a yaml file. From 9dceb84de8c4366241f9e8ccc04382ba33550cf6 Mon Sep 17 00:00:00 2001 From: Steffen Klemer Date: Thu, 14 Dec 2023 20:52:15 +0100 Subject: [PATCH 057/121] Adapt pbc.c to current json rpc spec (#1857) * pbc.c: add 'package' to rpc commands * pbc.c: Also recognise negative numbers as int in values (somehow hacky solution) --- src/cli_client/pbc.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cli_client/pbc.c b/src/cli_client/pbc.c index 8dcc462cb..6653ade86 100644 --- a/src/cli_client/pbc.c +++ b/src/cli_client/pbc.c @@ -47,6 +47,7 @@ int g_verbose = 0; typedef struct { char object [MAX_STRLEN]; + char package [MAX_STRLEN]; char method [MAX_STRLEN]; char params [MAX_PARAMS][MAX_STRLEN]; int num_params; @@ -126,7 +127,7 @@ void * connect_and_send_request(t_request * tr) } else sprintf(kwargs, "\"kwargs\":{},"); - snprintf(json_request,MAX_REQEST_STRLEN,"{\"plugin\": \"%s\", \"method\": \"%s\", %s\"id\":%d}",tr->object,tr->method,kwargs,123); + snprintf(json_request,MAX_REQEST_STRLEN,"{\"package\": \"%s\", \"plugin\": \"%s\", \"method\": \"%s\", %s\"id\":%d}",tr->package,tr->object,tr->method,kwargs,123); json_len = strlen(json_request); if (g_verbose) printf("Sending Request (%ld Bytes):\n%s\n",json_len,json_request); @@ -146,7 +147,7 @@ int check_and_map_parameters_to_json(char * arg, t_request * tr) { name = strtok(arg, ":"); value = strtok(NULL, ":"); - fmt = (isdigit(*value)) ? "\"%s\":%s" : "\"%s\":\"%s\""; + fmt = (isdigit(*value)||*value=='-') ? "\"%s\":%s" : "\"%s\":\"%s\""; snprintf (tr->params[tr->num_params++],MAX_STRLEN, fmt,name,value); ret = 1; } @@ -156,9 +157,10 @@ int check_and_map_parameters_to_json(char * arg, t_request * tr) void usage(void) { - fprintf(stderr,"\npbc -> PhonieBox Command line interface\nusage: pbc -o object -m method param_name:value\n\n"); + fprintf(stderr,"\npbc -> PhonieBox Command line interface\nusage: pbc -p package -o plugin -m method param_name:value\n\n"); fprintf(stderr," -h this screen\n"); - fprintf(stderr," -o, --object object\n"); + fprintf(stderr," -p, --package package\n"); + fprintf(stderr," -o, --object plugin\n"); fprintf(stderr," -m, --method method\n"); fprintf(stderr," -a, --address default=tcp://localhost:5555\n"); fprintf(stderr," -v verbose\n"); @@ -184,6 +186,7 @@ int HandleOptions(int argc,char *argv[], t_request * tr) /* These options don't set a flag. We distinguish them by their indices. */ {"help", no_argument, 0, 'h'}, + {"package", required_argument, 0, 'p'}, {"object", required_argument, 0, 'o'}, {"method", required_argument, 0, 'm'}, {"address", required_argument, 0, 'a'}, @@ -208,7 +211,9 @@ int HandleOptions(int argc,char *argv[], t_request * tr) usage(); puts ("option -a\n"); break; - + case 'p': + strncpy (tr->package,optarg,MAX_STRLEN); + break; case 'o': strncpy (tr->object,optarg,MAX_STRLEN); break; From 0e6dbd6dbbbc2d67087ea387c64c38effc037513 Mon Sep 17 00:00:00 2001 From: s-martin Date: Tue, 19 Dec 2023 16:18:23 +0100 Subject: [PATCH 058/121] Add docs for command line client for RPC (#2162) * fix typos and add docs for pbc * fix typo and fix markdown warnings * move cli docs to builders * move cli client to builders * fix markdown warnings --- documentation/builders/README.md | 2 ++ documentation/builders/cli-client.md | 21 +++++++++++++++++++++ documentation/builders/rpc-commands.md | 4 ---- documentation/builders/system.md | 10 +++++----- documentation/developers/README.md | 6 ++++++ documentation/developers/coreapps.md | 9 +++------ documentation/developers/libzmq.md | 24 ++++++++++++++++-------- documentation/developers/pyhton.md | 4 ++-- src/cli_client/pbc.c | 2 +- 9 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 documentation/builders/cli-client.md diff --git a/documentation/builders/README.md b/documentation/builders/README.md index 3d70bca15..86e98aca1 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -19,6 +19,8 @@ * [Auto Hotspot](./autohotspot.md) * [Concepts](./concepts.md) * [System](./system.md) +* [RPC Commands](./rpc-commands.md) +* [CLI Client for RPC](./cli-client.md) * [Feature Status](../developers/status.md) * [Known Issues](../developers/known-issues.md) * [Developer Reference](../developers/README.md) diff --git a/documentation/builders/cli-client.md b/documentation/builders/cli-client.md new file mode 100644 index 000000000..b75e942ee --- /dev/null +++ b/documentation/builders/cli-client.md @@ -0,0 +1,21 @@ +# CLI Client + +The CLI (command line interface) client can be used to send [RPC commands](./rpc-commands.md) from command line to Phoniebox. + +## Installation + +* Install prerequisites: `sudo apt-get install libczmq-dev` +* Change to directory: `cd ~/RPi-Jukebox-RFID/src/cli_client` +* Compile CLI client: `gcc pbc.c -o pbc -lzmq -Wall` + +## Usage + +* Get help info: `./pbc -h` +* Example shutdown: `./pbc -p host -o shutdown` + +See also [RPC Commands](./rpc-commands.md) reference. + +## Reference + +* +* diff --git a/documentation/builders/rpc-commands.md b/documentation/builders/rpc-commands.md index f98da8cb4..374c27d97 100644 --- a/documentation/builders/rpc-commands.md +++ b/documentation/builders/rpc-commands.md @@ -1,6 +1,5 @@ # RPC Commands - We use the RPC commands when triggering actions from different inputs like a card swipe, a GPIO button press, etc. Triggering an action is equal to sending an RPC function call. In many places the command to send when an input is triggered is configurable in a YAML-file. @@ -37,7 +36,6 @@ The keyword ``method`` is optional. If needs to be used depends on the function ## Aliases - Not so complicated, right? It will get even easier. For common commands we have defined aliases. An alias simply maps to a pre-defined RPC command, e.g. ``play_card`` maps to ``player.ctrl.play_card``. @@ -98,11 +96,9 @@ do exactly the same, but use different ways of specifying the command. folder: path/to/folder recursive: True - .. important:: *args* must be a **list** of arguments to be passed! Even if only a single argument is passed. So, use *args: [value]*. We try catch mis-uses but that might not always work. - You will find some more examples the configuration of the [Card Database](card-database.md) ## For developers diff --git a/documentation/builders/system.md b/documentation/builders/system.md index 0e2c19485..e981678c0 100644 --- a/documentation/builders/system.md +++ b/documentation/builders/system.md @@ -46,7 +46,7 @@ $ journalctl --user -b -u mpd The `systemd` service file is located at the default location for user services: -``` +```text /usr/lib/systemd/user/mpd.service ``` @@ -62,7 +62,7 @@ There is a number of reasons for that: The PulseAudio configuration file is located at -``` +```text ~/.config/pulse/default.pa ``` @@ -90,7 +90,7 @@ $ journalctl --user -b -u jukebox-daemon The `systemd` service file is located at the default location for user services: -``` +```text /usr/lib/systemd/user/jukebox-daemon.service ``` @@ -100,12 +100,12 @@ Starting and stopping the service can be useful for debugging or configuration c The Web UI is served using nginx. Nginx runs as a system service. The home directory is localed at -``` +```text ./src/webapp/build ``` The Nginx configuration is located at -``` +```text /etc/nginx/sites-available/default ``` diff --git a/documentation/developers/README.md b/documentation/developers/README.md index 6c973d6ba..64fee44c1 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -11,3 +11,9 @@ * [RFID Readers](./rfid) * [Feature Status](./status.md) * [Known Issues](./known-issues.md) + +## RPC + +* [LibZMQ](./libzmq.md) +* [CLI Client for RPC](../builders/cli-client.md) +* [RPC Commands](../builders/rpc-commands.md) diff --git a/documentation/developers/coreapps.md b/documentation/developers/coreapps.md index 3e4fc7b1f..4af784d50 100644 --- a/documentation/developers/coreapps.md +++ b/documentation/developers/coreapps.md @@ -8,7 +8,6 @@ $ cd src/jukebox $ ./ -h ``` - ## Jukebox Core **Scriptname:** [run_jukebox.py](../../src/jukebox/run_jukebox.py) @@ -19,7 +18,6 @@ Usually this runs as a service, which is started automatically after boot-up. At For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. See [Troubleshooting](../builders/troubleshooting.md). - ## Configuration Tools Before running the configuration tools, stop the Jukebox Core service. @@ -29,7 +27,7 @@ See [Best practice procedure](../builders/configuration.md#best-practice-procedu **Scriptname:** [run_configure_audio.py](../../src/jukebox/run_configure_audio.py) -Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. +Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. Will also setup equalizer and mono down mixer in the pulseaudio config file. Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). @@ -37,21 +35,20 @@ Will also setup equalizer and mono down mixer in the pulseaudio config file. Run **Scriptname:** [run_register_rfid_reader.py](../../src/jukebox/run_register_rfid_reader.py) -Setup tool to configure the RFID Readers. +Setup tool to configure the RFID Readers. Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change the settings. For more information see [RFID Readers](./rfid/README.md). > [!NOTE] > This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). Any manual modifications to the settings will have to be re-applied - ## Developer Tools ### RPC **Scriptname:** [run_rpc_tool.py](../../src/jukebox/run_rpc_tool.py) -Command Line Interface to the Jukebox RPC Server. +Command Line Interface to the Jukebox RPC Server. A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging.The tool features auto-completion and command history. The list of available commands is fetched from the running Jukebox service. diff --git a/documentation/developers/libzmq.md b/documentation/developers/libzmq.md index 3407d971e..a9d26991a 100644 --- a/documentation/developers/libzmq.md +++ b/documentation/developers/libzmq.md @@ -4,12 +4,12 @@ The Jukebox requires `libzmq` to work properly. We provide downloadable builds to speed up the installation process of the Phoniebox. -* https://github.com/pabera/libzmq/releases +* > [!NOTE] > We can't use stable builds that are distributed by [zeromq](https://github.com/zeromq/libzmq/releases) directly because the Jukebox requires draft builds to support WebSockets. These [draft builds](https://pyzmq.readthedocs.io/en/latest/howto/draft.html) are not officially provided through zeromq for Raspberry Pi architecture (e.g. `armv6` or `armv7`). -## Building `libzmp` +## Building `libzmq` If you need to update the `libzmq` version in the future, follow these steps. @@ -17,13 +17,17 @@ If you need to update the `libzmq` version in the future, follow these steps. First, you need to install Dockcross. Dockcross provides Docker images for cross-compilation. -1. **Pull the Dockcross Image**: For Raspberry Pi B, 4 or Zero 2 we need `linux-armv7`, for older models `linux-armv6`. The following example shows how to compile for `armv7` (32 bit, `arm32v7`). If you want to compile for another target, change the commands accordingly. For Docker Development environments, other targets like `arm64` or `x86_64` become relevant. +#### 1. Pull the Dockcross Image + +For Raspberry Pi B, 4 or Zero 2 we need `linux-armv7`, for older models `linux-armv6`. The following example shows how to compile for `armv7` (32 bit, `arm32v7`). If you want to compile for another target, change the commands accordingly. For Docker Development environments, other targets like `arm64` or `x86_64` become relevant. ```bash docker pull dockcross/linux-armv7 ``` -3. **Create a Dockcross Script**: After pulling the image, you create a Dockcross script which will be used to run the cross-compilation tools in the Docker container. +#### 2. Create a Dockcross Script + +After pulling the image, you create a Dockcross script which will be used to run the cross-compilation tools in the Docker container. ```bash docker run --rm dockcross/linux-armv7 > ./dockcross-linux-armv7 @@ -36,7 +40,9 @@ This command creates a script named `dockcross-linux-armv7` in your current dire With Dockcross installed, you can now modify your `libzmq` compilation process to use the Dockcross environment. -1. **Download `libzmq` Source**: Similar to your original process, download the `libzmq` source code: +#### 1. Download `libzmq` Source + +Similar to your original process, download the `libzmq` source code: ```bash ZMQ_VERSION=4.3.5 @@ -44,7 +50,7 @@ wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-$ tar -xzf libzmq.tar.gz ``` -2. **Cross-Compilation using Dockcross**: +#### 2. Cross-Compilation using Dockcross Modify your build process to run inside the Dockcross environment: @@ -61,8 +67,10 @@ make install DESTDIR=$(pwd)/../installed' This command configures and builds `libzmq` inside the Docker container. The `DESTDIR` variable is used to specify where to install the files inside the container. -3. **Compress the Compiled Binaries**: After compilation, the binaries are located in the `installed` directory inside your `zeromq-${ZMQ_VERSION}` directory. +#### 3. Compress the Compiled Binaries -``` +After compilation, the binaries are located in the `installed` directory inside your `zeromq-${ZMQ_VERSION}` directory. + +```bash tar -czvf libzmq5-armv7-${ZMQ_VERSION}.tar.gz -C installed/usr/local --exclude='.' include lib ``` diff --git a/documentation/developers/pyhton.md b/documentation/developers/pyhton.md index 31659ef3c..5b2172c5c 100644 --- a/documentation/developers/pyhton.md +++ b/documentation/developers/pyhton.md @@ -7,12 +7,12 @@ Before you can run Python code, you need to enable the virtual environment. On the Raspberry Pi, it's located in the project root `~/RPi-Jukebox-RFID/.venv`. Depending on your setup, the absolute path can vary. -``` +```bash $ source ~/RPi-Jukebox-RFID/.venv/bin/activate ``` If the virtual environment has been activated correctly, your terminal will now show a prefix (`.venv`). If you want to leave the venv again execute deactivate. -``` +```bash $ deactivate ``` diff --git a/src/cli_client/pbc.c b/src/cli_client/pbc.c index 6653ade86..0b5cfb762 100644 --- a/src/cli_client/pbc.c +++ b/src/cli_client/pbc.c @@ -28,7 +28,7 @@ pbc -> PhonieBox Command line interface - depenmds on libczmq: + depends on libczmq: apt-get install libczmq-dev how to compile: From 9e65de46cef97225f916772a0d7d64c9831ffd38 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Tue, 19 Dec 2023 20:03:36 +0100 Subject: [PATCH 059/121] Add Workflow Action to build and upload webapp bundle to Release (#2161) * fix editorconfig for yml files * add workflow for webapp build on release * filter paths for CI installation worlflow * abort installation if needed download failed * fix SemVer definition * add workflow for webapp bundle build and releases. remove old workflow * add main to version.py * fix prebuild webapp bundle download add download for latest bundle: used if no bundle for the commit is found and force download is set. * changes to webapp build option changed order: kiosk_mode and node option change message to better reflect the behavior. move local build finmessage to the 'yes' case: Download is forced and node is not installed. the message is irritating * add checks for correct version for branch * activate official repo check * set next develop version * Update message * make webapp downloads on forks possible * bugfix elif * add abort if sources failed to load * Updated WEBAPP NODE message * change filename to short hash (10 chars) * add semver ref to version.py --- .editorconfig | 2 +- .../bundle_webapp_and_release_v3.yml | 183 ++++++++++++++++++ .github/workflows/test_docker_debian_v3.yml | 20 +- ci/installation/run_install_common.sh | 2 +- ci/installation/run_install_faststartup.sh | 2 +- ci/installation/run_install_libzmq_local.sh | 2 +- .../run_install_webapp_download.sh | 4 +- ci/installation/run_install_webapp_local.sh | 2 +- installation/includes/02_helpers.sh | 27 ++- installation/install-jukebox.sh | 4 +- installation/routines/customize_options.sh | 23 ++- installation/routines/setup_jukebox_core.sh | 4 +- installation/routines/setup_jukebox_webapp.sh | 26 ++- src/jukebox/jukebox/version.py | 12 +- 14 files changed, 265 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/bundle_webapp_and_release_v3.yml diff --git a/.editorconfig b/.editorconfig index 21e257ee9..1c436d1ea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,5 +15,5 @@ indent_size = 4 [*.md] trim_trailing_whitespace = false -[*.{js,yaml}] +[*.{js,yaml,yml}] indent_size = 2 diff --git a/.github/workflows/bundle_webapp_and_release_v3.yml b/.github/workflows/bundle_webapp_and_release_v3.yml new file mode 100644 index 000000000..548f2b47a --- /dev/null +++ b/.github/workflows/bundle_webapp_and_release_v3.yml @@ -0,0 +1,183 @@ +name: Bundle Webapp and Release + +on: + push: + branches: + - 'future3/main' + - 'future3/develop' + +jobs: + + check: + if: ${{ github.repository_owner == 'MiczFlor' }} + runs-on: ubuntu-latest + + outputs: + tag_name: ${{ steps.vars.outputs.tag_name }} + release_type: ${{ steps.vars.outputs.release_type }} + check_abort: ${{ steps.vars.outputs.check_abort }} + + steps: + - uses: actions/checkout@v3 + + - name: Set Output vars + id: vars + env: + BRANCH_NAME: ${{ github.ref_name }} + run: | + # Official SemVer Regex definition + # https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + # Needed changes to the regex: + # - '?:' capture command needed to be removed as it wasn't working in shell + # - '\d' had to be replaced with [0-9] + # + # Release versions like 1.0.0, 3.5.0, 100.4.50000+metadata + REGEX_VERSION_RELEASE="^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$" + # + # Prerelease versions like 1.0.0-alpha, 3.5.0-whatsoever.12, 100.4.50000-identifier.12+metadata + REGEX_VERSION_PRERELEASE="^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$" + + + # Get the version and calculate release type + VERSION=$(python ./src/jukebox/jukebox/version.py) + if echo "$VERSION" | grep -qoE "$REGEX_VERSION_RELEASE" ; then + RELEASE_TYPE=release + elif echo "$VERSION" | grep -qoE "$REGEX_VERSION_PRERELEASE" ; then + RELEASE_TYPE=prerelease + else + RELEASE_TYPE=none + fi + + if [ "$BRANCH_NAME" == 'future3/main' -a "$RELEASE_TYPE" == 'release' ] || [ "$BRANCH_NAME" == 'future3/develop' -a "$RELEASE_TYPE" == 'prerelease' ] ; then + CHECK_ABORT=false + else + echo "::notice title=Abort due to not matching ${RELEASE_TYPE} version for branch!::'${VERSION}' on '${BRANCH_NAME}'" + CHECK_ABORT=true + fi + + echo "::group::Output values" + echo "Version: ${VERSION}" + echo "RELEASE_TYPE: ${RELEASE_TYPE}" + echo "BRANCH_NAME: ${BRANCH_NAME}" + echo "CHECK_ABORT: ${CHECK_ABORT}" + + echo "tag_name=v${VERSION}" >> $GITHUB_OUTPUT + echo "release_type=${RELEASE_TYPE}" >> $GITHUB_OUTPUT + echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "check_abort=${CHECK_ABORT}" >> $GITHUB_OUTPUT + echo "::endgroup::" + + build: + needs: [check] + if: ${{ needs.check.outputs.check_abort == 'false' }} + runs-on: ubuntu-latest + + env: + WEBAPP_ROOT_PATH: ./src/webapp + + outputs: + tag_name: ${{ needs.check.outputs.tag_name }} + release_type: ${{ needs.check.outputs.release_type }} + commit_sha: ${{ steps.vars.outputs.commit_sha }} + webapp_bundle_name: ${{ steps.vars.outputs.webapp_bundle_name }} + webapp_bundle_name_latest: ${{ steps.vars.outputs.webapp_bundle_name_latest }} + + steps: + - uses: actions/checkout@v3 + + - name: Set Output vars + id: vars + env: + COMMIT_SHA: ${{ github.sha }} + run: | + echo "commit_sha=${COMMIT_SHA}" >> $GITHUB_OUTPUT + echo "webapp_bundle_name=webapp-build-${COMMIT_SHA:0:10}.tar.gz" >> $GITHUB_OUTPUT + echo "webapp_bundle_name_latest=webapp-build-latest.tar.gz" >> $GITHUB_OUTPUT + + - name: Setup Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + - name: npm install + working-directory: ${{ env.WEBAPP_ROOT_PATH }} + run: npm install + - name: npm build + working-directory: ${{ env.WEBAPP_ROOT_PATH }} + env: + CI: false + run: npm run build + + - name: Create Bundle + working-directory: ${{ env.WEBAPP_ROOT_PATH }} + run: | + tar -czvf ${{ steps.vars.outputs.webapp_bundle_name }} build + + - name: Artifact Upload + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.vars.outputs.webapp_bundle_name }} + path: ${{ env.WEBAPP_ROOT_PATH }}/${{ steps.vars.outputs.webapp_bundle_name }} + retention-days: 5 + + release: + needs: [build] + runs-on: ubuntu-latest + + concurrency: + group: ${{ needs.build.outputs.tag_name }} + + permissions: + contents: write + + steps: + - name: Artifact Download + uses: actions/download-artifact@v3 + with: + name: ${{ needs.build.outputs.webapp_bundle_name }} + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + commit: ${{ needs.build.outputs.commit_sha }} + tag: ${{ needs.build.outputs.tag_name }} + body: "Automated Release for ${{ needs.build.outputs.tag_name }}" + makeLatest: 'false' + prerelease: ${{ needs.build.outputs.release_type == 'prerelease' }} + generateReleaseNotes: ${{ needs.build.outputs.release_type == 'release' }} + skipIfReleaseExists: false + allowUpdates: true + removeArtifacts: false + replacesArtifacts: false + omitBodyDuringUpdate: true + omitNameDuringUpdate: true + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get Release by tag + id: get_release + uses: joutvhu/get-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ needs.build.outputs.tag_name }} + + - name: Upload Release Asset + uses: shogo82148/actions-upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_name: ${{ needs.build.outputs.webapp_bundle_name }} + asset_path: ${{ needs.build.outputs.webapp_bundle_name }} + asset_content_type: application/gzip + overwrite: true + + - name: Upload Release Asset as Latest + uses: shogo82148/actions-upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_name: ${{ needs.build.outputs.webapp_bundle_name_latest }} + asset_path: ${{ needs.build.outputs.webapp_bundle_name }} + asset_content_type: application/gzip + overwrite: true diff --git a/.github/workflows/test_docker_debian_v3.yml b/.github/workflows/test_docker_debian_v3.yml index 6f90048ec..f333d15eb 100644 --- a/.github/workflows/test_docker_debian_v3.yml +++ b/.github/workflows/test_docker_debian_v3.yml @@ -5,9 +5,27 @@ on: # run at 17:00 every sunday - cron: '0 17 * * 0' push: + branches: + - 'future3/**' + paths: + - 'installation/**' + - 'ci/**' + - 'resources/**' + - 'src/jukebox/jukebox/version.py' + - 'packages*.txt' + - 'requirements*.txt' pull_request: # The branches below must be a subset of the branches above - branches: [ future3/develop ] + branches: + - future3/develop + - future3/main + paths: + - 'installation/**' + - 'ci/**' + - 'resources/**' + - 'src/jukebox/jukebox/version.py' + - 'packages*.txt' + - 'requirements*.txt' # let only one instance run the test so cache is not corrupted. # cancel already running instances as only the last run will be relevant diff --git a/ci/installation/run_install_common.sh b/ci/installation/run_install_common.sh index 102c71aa4..3d4c59778 100644 --- a/ci/installation/run_install_common.sh +++ b/ci/installation/run_install_common.sh @@ -24,8 +24,8 @@ export ENABLE_WEBAPP_PROD_DOWNLOAD=true # n - setup rfid reader # y - setup samba # y - setup webapp -# n - setup kiosk mode # - - install node (forced WebApp Download) +# n - setup kiosk mode # n - reboot "${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y diff --git a/ci/installation/run_install_faststartup.sh b/ci/installation/run_install_faststartup.sh index 46cda25ec..249d78ffc 100644 --- a/ci/installation/run_install_faststartup.sh +++ b/ci/installation/run_install_faststartup.sh @@ -22,8 +22,8 @@ LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" # n - setup rfid reader # n - setup samba # n - setup webapp -# - - setup kiosk mode (only with webapp = y) # - - install node (only with webapp = y) +# - - setup kiosk mode (only with webapp = y) # n - reboot "${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y diff --git a/ci/installation/run_install_libzmq_local.sh b/ci/installation/run_install_libzmq_local.sh index 20f246ff8..aa3726b2f 100644 --- a/ci/installation/run_install_libzmq_local.sh +++ b/ci/installation/run_install_libzmq_local.sh @@ -23,8 +23,8 @@ export BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=true # n - setup rfid reader # n - setup samba # n - setup webapp -# - - setup kiosk mode (only with webapp = y) # - - install node (only with webapp = y) +# - - setup kiosk mode (only with webapp = y) # n - reboot "${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y diff --git a/ci/installation/run_install_webapp_download.sh b/ci/installation/run_install_webapp_download.sh index 69496e8e4..698e057b9 100644 --- a/ci/installation/run_install_webapp_download.sh +++ b/ci/installation/run_install_webapp_download.sh @@ -11,7 +11,6 @@ SCRIPT_DIR="$(dirname "$SOURCE")" LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" -export ENABLE_WEBAPP_PROD_DOWNLOAD=true # Run installation (in interactive mode) # y - start setup # n - use static ip @@ -23,8 +22,8 @@ export ENABLE_WEBAPP_PROD_DOWNLOAD=true # n - setup rfid reader # n - setup samba # y - setup webapp +# n - install node # y - setup kiosk mode -# - - install node (forced webapp download) # n - reboot "${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y @@ -36,6 +35,7 @@ n n n y +n y n ' diff --git a/ci/installation/run_install_webapp_local.sh b/ci/installation/run_install_webapp_local.sh index d4f122fd5..917f985af 100644 --- a/ci/installation/run_install_webapp_local.sh +++ b/ci/installation/run_install_webapp_local.sh @@ -23,8 +23,8 @@ export ENABLE_WEBAPP_PROD_DOWNLOAD=false # n - setup rfid reader # n - setup samba # y - setup webapp -# y - setup kiosk mode # y - install node +# y - setup kiosk mode # n - reboot "${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 7520241c6..239a18ccf 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -49,22 +49,17 @@ get_architecture() { echo $arch } -get_version_string() { - local python_file="$1" - local version_major - local version_minor - local version_patch - - version_major=$(grep 'VERSION_MAJOR\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') - version_minor=$(grep 'VERSION_MINOR\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') - version_patch=$(grep 'VERSION_PATCH\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') - - if [ -n "$version_major" ] && [ -n "$version_minor" ] && [ -n "$version_patch" ]; then - local version_string="${version_major}.${version_minor}.${version_patch}" - echo ${version_string} - else - exit_on_error "ERROR: Unable to extract version information from ${python_file}" - fi +validate_url() { + local url=$1 + wget --spider ${url} >/dev/null 2>&1 + return $? +} + +download_from_url() { + local url=$1 + local output_filename=$2 + wget --quiet ${url} -O ${output_filename} || exit_on_error "Download failed" + return $? } ### Verify helpers diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 84827f99d..2df7ab528 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -130,11 +130,11 @@ _download_jukebox_source() { _load_sources() { # Load / Source dependencies for i in "${INSTALLATION_PATH}"/installation/includes/*; do - source "$i" + source "$i" || exit_on_error done for j in "${INSTALLATION_PATH}"/installation/routines/*; do - source "$j" + source "$j" || exit_on_error done } diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index 4007b88f5..7409a6e07 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -292,9 +292,14 @@ _option_webapp_devel_build() { clear_c print_c "--------------------- WEBAPP NODE --------------------- -You are installing from a non-release branch. -This means, you will need to build the web app locally. -For that you'll need Node. +You are installing from an unofficial branch. +Therefore a prebuilt web app is not available and +you will have to build it locally. +This requires Node to be installed. + +You can choose to decline the Node installation and +the lastest prebuilt version from the main repository +will be installed. This can lead to incompatibilities. Do you want to install Node? [Y/n]" read -r response @@ -304,14 +309,14 @@ Do you want to install Node? [Y/n]" ENABLE_WEBAPP_PROD_DOWNLOAD=true ;; *) - ;; - esac - # This message will be displayed at the end of the installation process - local tmp_fin_message="ATTENTION: You need to build the web app locally with + # This message will be displayed at the end of the installation process + local tmp_fin_message="ATTENTION: You need to build the web app locally with $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u This must be done after reboot, due to memory restrictions. Read the documentation regarding local Web App builds!" - FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" + FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" + ;; + esac fi fi @@ -332,8 +337,8 @@ _run_customize_options() { _option_samba _option_webapp if [[ $ENABLE_WEBAPP == true ]] ; then - _option_kiosk_mode _option_webapp_devel_build + _option_kiosk_mode fi # Bullseye is currently under active development and should be updated in any case. # Hence, removing the step below as it becomse mandatory diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index a7d0f29b6..1c524abb0 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -54,7 +54,7 @@ _jukebox_core_build_libzmq_with_drafts() { local cpu_count=${CPU_COUNT:-$(python3 -c "import os; print(os.cpu_count())")} cd "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error - wget --quiet https://github.com/zeromq/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/${zmq_tar_filename} + wget --quiet https://github.com/zeromq/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/${zmq_tar_filename} || exit_on_error "Download failed" tar -xzf ${zmq_tar_filename} rm -f ${zmq_tar_filename} cd ${zmq_filename} || exit_on_error @@ -68,7 +68,7 @@ _jukebox_core_download_prebuilt_libzmq_with_drafts() { ARCH=$(get_architecture) cd "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error - wget --quiet https://github.com/pabera/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/libzmq5-${ARCH}-${JUKEBOX_ZMQ_VERSION}.tar.gz -O ${zmq_tar_filename} + wget --quiet https://github.com/pabera/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/libzmq5-${ARCH}-${JUKEBOX_ZMQ_VERSION}.tar.gz -O ${zmq_tar_filename} || exit_on_error "Download failed" tar -xzf ${zmq_tar_filename} rm -f ${zmq_tar_filename} sudo rsync -a ./* ${JUKEBOX_ZMQ_PREFIX}/ diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index f7407f96c..2884f18cb 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -55,16 +55,26 @@ _jukebox_webapp_build() { _jukebox_webapp_download() { print_lc " Downloading web application" - local JUKEBOX_VERSION=$(get_version_string "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") - local TAR_FILENAME="webapp-build.tar.gz" - local DOWNLOAD_URL="https://github.com/MiczFlor/RPi-Jukebox-RFID/releases/download/v${JUKEBOX_VERSION}/webapp-v${JUKEBOX_VERSION}.tar.gz" - log " DOWNLOAD_URL: ${DOWNLOAD_URL}" + local jukebox_version=$(python "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") + local git_head_hash=$(git -C "${INSTALLATION_PATH}" rev-parse --verify --quiet HEAD) + local git_head_hash_short=${git_head_hash:0:10} + local tar_filename="webapp-build.tar.gz" + # URL must be set to default repo as installation can be run from different repos as well where releases may not exist + local download_url_commit="https://github.com/${GIT_UPSTREAM_USER}/RPi-Jukebox-RFID/releases/download/v${jukebox_version}/webapp-build-${git_head_hash_short}.tar.gz" + local download_url_latest="https://github.com/${GIT_UPSTREAM_USER}/RPi-Jukebox-RFID/releases/download/v${jukebox_version}/webapp-build-latest.tar.gz" cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error - # URL must be set to default repo as installation can be run from different repos as well where releases may not exist - wget --quiet ${DOWNLOAD_URL} -O ${TAR_FILENAME} - tar -xzf ${TAR_FILENAME} - rm -f ${TAR_FILENAME} + if validate_url ${download_url_commit} ; then + log " DOWNLOAD_URL ${download_url_commit}" + download_from_url ${download_url_commit} ${tar_filename} + elif [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true ]] && validate_url ${download_url_latest} ; then + log " DOWNLOAD_URL ${download_url_latest}" + download_from_url ${download_url_latest} ${tar_filename} + else + exit_on_error "No prebuild webapp bundle found!" + fi + tar -xzf ${tar_filename} + rm -f ${tar_filename} cd "${INSTALLATION_PATH}" || exit_on_error } diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index 03fc34a66..37382cee7 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,14 +1,16 @@ VERSION_MAJOR = 3 -VERSION_MINOR = 4 +VERSION_MINOR = 5 VERSION_PATCH = 0 -VERSION_EXTRA = "" +VERSION_EXTRA = "alpha" +# build a version string in compliance with the SemVer specification +# https://semver.org/#semantic-versioning-specification-semver __version__ = '%i.%i.%i' % (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) __version_info__ = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) if VERSION_EXTRA: - __version__ = "%s.%s" % (__version__, VERSION_EXTRA) + __version__ = "%s-%s" % (__version__, VERSION_EXTRA) __version_info__ = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_EXTRA) @@ -23,3 +25,7 @@ def version_info(): If this is a development version, an identifier string will be appended after the third integer. """ return __version_info__ + + +if __name__ == '__main__': + print(version()) From 0a5d42967b72c4bf3291a72e460421097b6481b9 Mon Sep 17 00:00:00 2001 From: s-martin Date: Tue, 19 Dec 2023 23:19:33 +0100 Subject: [PATCH 060/121] Improve the run_rpc_tool script (#2166) * add possibility to execute commands directly * add documentation * ignore node modules in flakek8 * fix comments --- .flake8 | 4 ++- documentation/developers/coreapps.md | 9 +++++- src/jukebox/run_rpc_tool.py | 45 +++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/.flake8 b/.flake8 index 0c2cd276e..7f201b078 100644 --- a/.flake8 +++ b/.flake8 @@ -21,5 +21,7 @@ extend-exclude = scratch*, # Ignore dirs and files that have not been ported yet */jukebox/NvManager.py - # ignore GitHub Codespaces + # ignore Python venv's .venv/* + # ignore node modules + */node_modules/* diff --git a/documentation/developers/coreapps.md b/documentation/developers/coreapps.md index 4af784d50..1b5b746aa 100644 --- a/documentation/developers/coreapps.md +++ b/documentation/developers/coreapps.md @@ -50,7 +50,14 @@ Run this once to register and configure the RFID readers with the Jukebox. Can b Command Line Interface to the Jukebox RPC Server. -A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging.The tool features auto-completion and command history. The list of available commands is fetched from the running Jukebox service. +A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging. Use `./run_rpc_tool.py` to start the tool in interactive mode. + +The tool features auto-completion and command history. + +The list of available commands is fetched from the running Jukebox service. + +The tool can also be used to send commands directly, when passing a `-c` argument, e.g. `./run_rpc_tool.py -c host.shutdown`. + ### Publicity Sniffer diff --git a/src/jukebox/run_rpc_tool.py b/src/jukebox/run_rpc_tool.py index 5f84702d4..1593d7796 100755 --- a/src/jukebox/run_rpc_tool.py +++ b/src/jukebox/run_rpc_tool.py @@ -309,6 +309,42 @@ def main(scr): scr.addstr(f"\n:: Response =\n{response}\n\n") +def runcmd(cmd): + """ + Just run a command. + Right now duplicates more or less main() + :todo remove duplication of code + """ + + # Split on whitespaces to separate cmd and arg list + dec = [v for v in cmd.strip().split(' ') if len(v) > 0] + if len(dec) == 0: + return + # Split cmd on '.' into package.plugin.method + # Remove duplicate '.' along the way + sl = [v for v in dec[0].split('.') if len(v) > 0] + fargs = [tonum(a) for a in dec[1:]] + response = None + method = None + if not (2 <= len(sl) <= 3): + print(":: Error = Ill-formatted command\n") + return + if len(sl) == 3: + method = sl[2] + try: + response = client.enque(sl[0], sl[1], method, args=fargs) + except zmq.error.Again: + print("\n\n" + '-' * 70 + "\n") + print("Could not reach RPC Server. Jukebox running? Correct Port?\n") + print('-' * 70 + "\n\n") + return + except Exception as e: + print(f":: Exception response =\n{e}\n") + return + else: + print(f"\n:: Response =\n{response}\n\n") + + if __name__ == '__main__': default_tcp = 5555 default_ws = 5556 @@ -324,6 +360,9 @@ def main(scr): help=f"Use tcp protocol on PORT [default: {default_tcp}]", nargs='?', const=default_tcp, metavar="PORT", default=None) + port_group.add_argument("-c", "--command", + help="Send command to Jukebox server", + default=None) args = argparser.parse_args() if args.websocket is not None: @@ -335,6 +374,10 @@ def main(scr): client = rpc.RpcClient(url) - curses.wrapper(main) + if args.command is not None: + runcmd(args.command) + exit(0) + else: + curses.wrapper(main) print(">>> RPC Client exited!") From 480ce20ad5dadaadbc5d0d7909eec49422cb92eb Mon Sep 17 00:00:00 2001 From: s-martin Date: Thu, 21 Dec 2023 06:29:00 +0100 Subject: [PATCH 061/121] Improve Docs: Fix GPIO and add WSL (#2173) * move GPIO docs and convert to markdown * add wsl * fix markdown warnings --- documentation/builders/README.md | 1 + documentation/builders/audio.md | 2 +- documentation/builders/card-database.md | 5 +- documentation/builders/configuration.md | 1 + .../builders/gpio.md | 235 ++++++++---------- documentation/builders/installation.md | 2 +- documentation/builders/troubleshooting.md | 2 +- documentation/developers/coreapps.md | 1 - documentation/developers/developer-issues.md | 12 +- .../developers/development-environment.md | 22 +- documentation/developers/docker.md | 11 +- documentation/developers/known-issues.md | 2 +- documentation/developers/status.md | 2 +- 13 files changed, 137 insertions(+), 161 deletions(-) rename src/jukebox/components/gpio/gpioz/README.rst => documentation/builders/gpio.md (70%) diff --git a/documentation/builders/README.md b/documentation/builders/README.md index 86e98aca1..f9fef397e 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -10,6 +10,7 @@ * [Audio](./audio.md) * [RFID](./rfid.md) +* [GPIO Recipes](./gpio.md) * [Card Database](./card-database.md) * [Troubleshooting](./troubleshooting.md) diff --git a/documentation/builders/audio.md b/documentation/builders/audio.md index 4fd82e457..54bfe72bf 100644 --- a/documentation/builders/audio.md +++ b/documentation/builders/audio.md @@ -105,7 +105,7 @@ Directly edit `jukebox.yaml` following the steps: [Best practice procedure](conf The optional processing stages *Equalizer* and *Mono down mix* are realized by PulseAudio plugins. The processing chain is -``` +```text player --> mono mix --> equalizer --> hardware sink ``` diff --git a/documentation/builders/card-database.md b/documentation/builders/card-database.md index 67dbf5dae..340933750 100644 --- a/documentation/builders/card-database.md +++ b/documentation/builders/card-database.md @@ -33,6 +33,7 @@ using the alias option: ``` > [!NOTE] +> > * Remember card ids must be strings! So, quote them! > * *args* must be a **list** of arguments to be passed! Even if ony a single argument is @@ -44,7 +45,7 @@ not always work. In addition to the RPC commands, these options may be specified for every card -#### ignore_card_removal_action: true \| false (default: false) +### ignore_card_removal_action: true \| false (default: false) Only applies when using a place-capable reader and *place_not_swipe* is *true*. This option is ignored otherwise, so it does not hurt. @@ -58,7 +59,7 @@ timer ignore_card_removal_action: true ``` -#### ignore_same_id_delay: true \| false (default: false) +### ignore_same_id_delay: true \| false (default: false) Override the `same_id_delay` parameter from the reader configuration for this card. If true, the `same_id_delay` for this card is treated diff --git a/documentation/builders/configuration.md b/documentation/builders/configuration.md index f09e8098d..dba2da7dc 100644 --- a/documentation/builders/configuration.md +++ b/documentation/builders/configuration.md @@ -11,6 +11,7 @@ For several aspects we have [configuration tools](../developers/coreapps.md#conf * [Audio Configuration](./audio.md#audio-configuration) * [RFID Reader Configuration](../developers/rfid/basics.md#reader-configuration) +* [GPIO Recipes](./gpio.md) Even after running the tools, certain aspects can only be changed by modifying the configuration files directly. diff --git a/src/jukebox/components/gpio/gpioz/README.rst b/documentation/builders/gpio.md similarity index 70% rename from src/jukebox/components/gpio/gpioz/README.rst rename to documentation/builders/gpio.md index 46ab0250a..feeb907ef 100644 --- a/src/jukebox/components/gpio/gpioz/README.rst +++ b/documentation/builders/gpio.md @@ -1,72 +1,60 @@ -.. RPI Jukebox RFID Version 3 -.. Copyright (c) See file LICENSE in project root folder +# GPIO Recipes -GPIO Recipes -************** - -Enabling GPIO ----------------- +## Enabling GPIO The GPIO module needs to be enabled in your main configuration file ``shared/settings/jukebox.yaml``. Look for the this entry and modify it accordingly: -.. code-block:: yaml - +```yml gpioz: enable: true config_file: ../../shared/settings/gpio.yaml +``` The GPIO configuration itself is stored in a separate file, in this case ``../../shared/settings/gpio.yaml``. -The GPIO module uses `GPIOZero `_ as a backend to access the RPi's GPIO pins. +The GPIO module uses [GPIOZero](https://gpiozero.readthedocs.io/) as a backend to access the RPi's GPIO pins. It's a wrapper to integrate GPIOZero into the Jukebox's API, allowing a YAML based configuration and providing helpful error messages on misconfiguration. -Pin Numbering ------------------ +## Pin Numbering The pin numbering is the BCM pin numbering, as is the -`default in GPIOZero `_. +[default in GPIOZero](https://gpiozero.readthedocs.io/en/stable/recipes.html#pin-numbering>). -Configuration -------------------- +## Configuration The GPIOZ configuration file has the following structure: -.. code-block:: yaml - +```yml pin_factory: type: rpigpio.RPiGPIOFactory output_devices: ... input_devices: ... +``` -There is no need to touch the header, but some `Developer options`_ can be found there. +There is no need to touch the header, but some `Developer options` can be found there. A file with examples can be found in ``resources/default-settings/gpio.yaml``. Below you will find easy to adapt recipes for various configuration snippets. - -Input devices ---------------- +## Input devices Configuring input devices consists of 2 aspects: - #. Define an input device and configure it's parameters. All available - input devices can be found in :class:`components.gpio.gpioz.core.input_devices`. - #. Assign an action to execute on input state change. - Actions are defined as :ref:`userguide/rpc_commands:RPC Commands`, - just the same as for assigning card actions. - +1. Define an input device and configure it's parameters. All available +input devices can be found in class `components.gpio.gpioz.core.input_devices`. +2. Assign an action to execute on input state change. +Actions are defined as [RPC Commands](rpc-commands.md), +just the same as for assigning card actions. -Button: Toggle Playback -^^^^^^^^^^^^^^^^^^^^^^^^ +### Button: Toggle Playback A button to toggle music playback on single press: -.. code-block:: yaml - +```yml input_devices: TogglePlayback: type: Button @@ -75,38 +63,37 @@ A button to toggle music playback on single press: actions: on_press: alias: toggle +``` Each device instantiation must be uniquely named, here ``TogglePlayback``. The name can be freely chosen, as long as it is unique. The parameter ``type`` directly matches the GPIO input device class, in this case -:class:`LED `. With ``kwargs`` you can set all the class initialization +class `LED `. With ``kwargs`` you can set all the class initialization parameters, which relate directly to the class' initialization parameters. -.. important:: You cannot set the class initialization parameters :attr:`pin_factory` or :attr:`name` +**Important:** You cannot set the class initialization parameters :attr:`pin_factory` or :attr:`name` from inside ``kwargs``. The name is automatically assigned from the unique name of configuration entry. Usually, only the pin(s) are mandatory parameters. In the section ``actions``, the RPC commands are linked, -either as alias (i.e. shortcut) or full :ref:`userguide/rpc_commands:RPC Commands` specification. +either as alias (i.e. shortcut) or full [RPC Commands](rpc-commands.md) specification. The default configuration of the Button uses the internal pull-up resistor. So, the physical connection to the RPi looks: -.. code-block:: text - +```text ----+ | 1 kOhm Button 13| -----======------/ ----+ | | ----+ - GND +``` -Button: Increase volume -^^^^^^^^^^^^^^^^^^^^^^^^ +### Button: Increase volume A button to increase the volume by 5 steps every 0.75 seconds as long as it is pressed: -.. code-block:: yaml - +```yml input_devices: IncreaseVolume: type: Button @@ -118,14 +105,13 @@ A button to increase the volume by 5 steps every 0.75 seconds as long as it is p on_press: alias: change_volume args: +5 +``` -Button: Shutdown -^^^^^^^^^^^^^^^^^^^^^^^^ +### Button: Shutdown A button to shutdown the Jukebox if it is pressed for more than 3 seconds. Note the different ``type`` here! -.. code-block:: yaml - +```yml input_devices: IncreaseVolume: type: LongPressButton @@ -135,9 +121,9 @@ A button to shutdown the Jukebox if it is pressed for more than 3 seconds. Note actions: on_press: alias: shutdown +``` -Button: Dual Action -^^^^^^^^^^^^^^^^^^^^^^^^ +### Button: Dual Action A button to act differently on short and long presses. Go to previous song on single short press, start playlist from the beginning on press longer than 1 second. @@ -145,8 +131,7 @@ the beginning on press longer than 1 second. Note: the short press action is executed on button release since we don't not know how much longer somebody is going to press the button. The long press action is executed as soon as the hold time has been reached. -.. code-block:: yaml - +```yml input_devices: PreviousSong: type: ShortLongPressButton @@ -158,10 +143,9 @@ to press the button. The long press action is executed as soon as the hold time alias: prev_song on_long_press: alias: replay +``` - -TwinButton: Six function beast -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +### TwinButton: Six function beast Use two buttons to encode up to six actions depending on single / dual press and also short / long press. Single button short presses skip to prev/next song, while long presses decrease/increase the volume. @@ -169,8 +153,7 @@ Here we also use make use of :attr:`hold_repeat`, to have the volume continue to down. Twin press toggle pause or changed the audio output sink from speakers to headset. The attribute :attr:`hold_repeat` only applies to single press actions, never to dual press actions. -.. code-block:: yaml - +```yml input_devices: SixActionBeast: type: TwinButton @@ -193,12 +176,12 @@ only applies to single press actions, never to dual press actions. args: 3 on_long_press_ab: alias: toggle_output +``` With a TwinButton not all functions need to be assigned. A button that only does prev/next song and causes as shutdown only on dual press with a minimum hold time of 2 seconds looks like this: -.. code-block:: yaml - +```yml input_devices: CombinationButton: type: TwinButton @@ -213,10 +196,9 @@ shutdown only on dual press with a minimum hold time of 2 seconds looks like thi alias: next_song on_long_press_ab: alias: toggle_output +``` - -Rotary Encoder: Volume Control -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +### Rotary Encoder: Volume Control A rotary encoder to change the volume: A common module is the KY-040, which can be picked up from numerous shops. It has four pins, typically labelled DT, CLK, SW, GND. Connect GND to ground. Connect DT and CLK to the @@ -225,8 +207,7 @@ direction does not match, simply swap the pins in the configuration file. The pi present. It is a button when the rotary encoder is pressed from the top. Configure a regular button entry separately for this button, e.g. `Button: Toggle Playback`_. -.. code-block:: yaml - +```yml input_devices: VolumeRotator: type: RotaryEncoder @@ -240,54 +221,51 @@ regular button entry separately for this button, e.g. `Button: Toggle Playback`_ on_rotate_counter_clockwise: alias: change_volume args: -5 +``` -Rotary Encoder: Previous/Next Song -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +### Rotary Encoder: Previous/Next Song From the previous recipe, simply replace the actions to have a rotary encoder to step through the playlist: -.. code-block:: yaml - +```yml ... actions: on_rotate_clockwise: alias: next_song on_rotate_counter_clockwise: alias: prev_song +``` - -Output devices --------------- +## Output devices Configuring output devices contains 2 aspects: - #. Define the the output device. Available output devices can be found in :class:`components.gpio.gpioz.core.output_devices`. - #. Connect the device to some Jukebox function which then - activates the device on e.g. RFID card read. There are many predefined connections - available. Check them all out in :class:`components.gpio.gpioz.plugin.connectivity` +1. Define the the output device. Available output devices can be found in class `components.gpio.gpioz.core.output_devices`. +2. Connect the device to some Jukebox function which then +activates the device on e.g. RFID card read. There are many predefined connections +available. Check them all out in class `components.gpio.gpioz.plugin.connectivity` -.. note:: There are two different types of buzzers: +**Note:** There are two different types of buzzers: - Active buzzer: - These buzzers make a single-frequency beep sound when a constant voltage is applied. A common module is - the KY-012. These buzzers must be mapped to :class:`Buzzer `. +_Active buzzer_ - Passive buzzer: - These buzzers must be used with a PWM signal but then can emit different frequency beeps. This is all handled - by us. A common module is the KY-006. These buzzers *must* be mapped to :class:`TonalBuzzer ` +These buzzers make a single-frequency beep sound when a constant voltage is applied. A common module is +the KY-012. These buzzers must be mapped to class `components.gpio.gpioz.core.output_devices.Buzzer`. - For many connection function is does not matter if a :class:`Buzzer ` - or a :class:`TonalBuzzer ` is connected. - The connection function takes care of the differences internally - as long as the class matches the physical - hardware device! +_Passive buzzer_ +These buzzers must be used with a PWM signal but then can emit different frequency beeps. This is all handled +by us. A common module is the KY-006. These buzzers _must_ be mapped to class `components.gpio.gpioz.core.output_devices.TonalBuzzer` -Status LED -^^^^^^^^^^^^^^ +For many connection function is does not matter if a class `components.gpio.gpioz.core.output_devices.Buzzer` +or a class `components.gpio.gpioz.core.output_devices.TonalBuzzer` is connected. +The connection function takes care of the differences internally - as long as the class matches the physical +hardware device! -An LED that lights up when the Jukebox service is operational. +### Status LED -.. code-block:: yaml +An LED that lights up when the Jukebox service is operational. +```yml output_devices: StatusLED: type: LED @@ -295,10 +273,11 @@ An LED that lights up when the Jukebox service is operational. - gpio.gpioz.plugin.connectivity.register_status_led_callback kwargs: pin: 17 +``` As with the input devices, every output device requires a unique, but freely chosen name - here ``StatusLED``. The parameter ``type`` directly matches the GPIO output device class, in this case -:class:`LED `. +class ``. The parameters in ``kwargs`` relate to the class initialization parameters. The ``connect`` option is a list of functions to call to connect this device with a function inside @@ -307,13 +286,11 @@ the Jukebox. An output device can be used by multiple functions. .. important:: You cannot set the class initialization parameters :attr:`pin_factory` or :attr:`name` from inside ``kwargs``. The name is automatically assigned from the unique name of configuration entry. -Card Read Buzzer -^^^^^^^^^^^^^^^^^^ +### Card Read Buzzer Sound a Piezzo Buzzer once when a card swipe has been detected. For unknown cards, sound it 3 times. -.. code-block:: yaml - +```yml output_devices: RfidBuzzer: type: Buzzer @@ -321,17 +298,15 @@ Sound a Piezzo Buzzer once when a card swipe has been detected. For unknown card - gpio.gpioz.plugin.connectivity.register_rfid_callback kwargs: pin: 12 +``` -Card Read + Volume + Status Buzzer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +### Card Read + Volume + Status Buzzer Extend the card read buzzer to also sound one long beep after completed boot up and two beeps on shutdown. On top sound a short beep when minimum or maximum volume is reached. The only difference is the two additional connection functions. - -.. code-block:: yaml - +```yml output_devices: RfidBuzzer: type: Buzzer @@ -341,15 +316,14 @@ The only difference is the two additional connection functions. - gpio.gpioz.plugin.connectivity.register_volume_buzzer_callback kwargs: pin: 12 +``` -Tonal Status Buzzer -^^^^^^^^^^^^^^^^^^^^^^ +### Tonal Status Buzzer Have an active buzzer play a 4 note melody on startup and a 3 note melody on closing down. Use the same buzzer to beep on RFID card swipes. -.. code-block:: yaml - +```yml output_devices: RfidBuzzer: type: TonalBuzzer @@ -358,15 +332,13 @@ Use the same buzzer to beep on RFID card swipes. - gpio.gpioz.plugin.connectivity.register_rfid_callback kwargs: pin: 12 +``` +### Card Read LED -Card Read LED -^^^^^^^^^^^^^^^^^^ - -Just like `Card Read Buzzer`_, but flash a LED instead of buzzing a sound. The difference is the output device type. - -.. code-block:: yaml +Just like `Card Read Buzzer`, but flash a LED instead of buzzing a sound. The difference is the output device type. +```yml output_devices: RfidLED: type: LED @@ -374,48 +346,44 @@ Just like `Card Read Buzzer`_, but flash a LED instead of buzzing a sound. The d - gpio.gpioz.plugin.connectivity.register_rfid_callback kwargs: pin: 12 +``` -Volume LED -^^^^^^^^^^^^ +### Volume LED Have a LED change it's brightness to reflect the current volume level. It also flashes when minimum or maximum volume level is reached. -.. code-block:: yaml - +```yml output_devices: VolumeLED: type: PWMLED connect: gpio.gpioz.plugin.connectivity.register_volume_led_callback kwargs: pin: 18 +``` -Color Volume LED -^^^^^^^^^^^^^^^^^^ +### Color Volume LED Have an RGBLED change it's color to reflect the current volume level. It also flashes when minimum or maximum volume level is reached. RGBLED's can be found as modules, e.g. KY-016 or KY-009, or as individual components from any electronics shop. -.. code-block:: yaml - +```yml output_devices: VolumeLED: type: RGBLED connect: gpio.gpioz.plugin.connectivity.register_volume_rgbled_callback kwargs: pin: 18 +``` - -Bluetooth audio output LED -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +### Bluetooth audio output LED Indicates the current audio output sink. LED is off when audio sink is primary sink, and on when audio sink is secondary sink (e.g. a bluetooth headset). When sink toggle fails, LED blinks 3 times. -.. code-block:: yaml - +```yml output_devices: HeadsetConnected: type: LED @@ -423,42 +391,37 @@ on when audio sink is secondary sink (e.g. a bluetooth headset). When sink toggl - gpio.gpioz.plugin.connectivity.register_audio_sink_change_callback kwargs: pin: 27 +``` - -Developer options ---------------------- +## Developer options For developers there are 2 options. Both replace the pin factory used by GPIOZero. -Use Mock Pins -^^^^^^^^^^^^^^^ +### Use Mock Pins -Using GPIOZero `Mock pins `_, +Using GPIOZero [Mock pins](https://gpiozero.readthedocs.io/en/stable/api_pins.html#mock-pins), allows to do function development on an arbitrary machine. If you have -configured the :ref:`Mock RFID Reader `, +configured the [Mock RFID Reader](../developers/rfid/mock_reader.md), the GPIO input and output devices are added to the GUI. Simply change the header in the configuration file to: -.. code-block:: yaml - +```yml pin_factory: type: mock.MockFactory +``` -.. image:: mock_gpio.png - :width: 80 % - :align: center +![Mock GPIO](mock_gpio.png) -Use Remote Pins -^^^^^^^^^^^^^^^^^^ +### Use Remote Pins -Using `GPIOZero's remote pins `_ +Using [GPIOZero's remote pins](https://gpiozero.readthedocs.io/en/stable/remote_gpio.html) allows to run the Jukebox code on a single machine and have the GPIO happen on a RPi board. See the GPIOZero documentation how to set it up. Simply change the header in the configuration file to enable it. Host is the IP address of your RPi board. -.. code-block:: yaml - +```yml pin_factory: type: pigpio.PiGPIOFactory pigpio.PiGPIOFactory: kwargs: host: 192.168.178.32 +``` diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index 756e2a03c..945368480 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -59,7 +59,7 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to 6. Insert the following content, update your country, Wifi credentials and save the file. - ``` + ```text country=DE ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 diff --git a/documentation/builders/troubleshooting.md b/documentation/builders/troubleshooting.md index a83384ec5..d68230f81 100644 --- a/documentation/builders/troubleshooting.md +++ b/documentation/builders/troubleshooting.md @@ -25,7 +25,7 @@ shutdown of the service. The logs are also available via the Web Server: -``` +```text http://ip.of.your.box/logs ``` diff --git a/documentation/developers/coreapps.md b/documentation/developers/coreapps.md index 1b5b746aa..f1402683d 100644 --- a/documentation/developers/coreapps.md +++ b/documentation/developers/coreapps.md @@ -58,7 +58,6 @@ The list of available commands is fetched from the running Jukebox service. The tool can also be used to send commands directly, when passing a `-c` argument, e.g. `./run_rpc_tool.py -c host.shutdown`. - ### Publicity Sniffer **Scriptname:** [run_publicity_sniffer.py](../../src/jukebox/run_publicity_sniffer.py) diff --git a/documentation/developers/developer-issues.md b/documentation/developers/developer-issues.md index c8dd3b6c3..bcb4d491a 100644 --- a/documentation/developers/developer-issues.md +++ b/documentation/developers/developer-issues.md @@ -21,11 +21,11 @@ Creating an optimized production build... FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory ``` -**Reason** +#### Reason Not enough memory for Node -**Solution** +#### Solution Prior to building set the node memory environment variable. @@ -50,11 +50,11 @@ $ cd src/webapp $ ./run_rebuild.sh ``` -**Changing Swap Size** +#### Changing Swap Size This will set the swapsize to 1024 MB (and will deactivate swapfactor). Change accordingly if you have a SD Card with small capacity. -``` +```bash sudo dphys-swapfile swapoff sudo sed -i "s|.*CONF_SWAPSIZE=.*|CONF_SWAPSIZE=1024|g" /etc/dphys-swapfile sudo sed -i "s|^\s*CONF_SWAPFACTOR=|#CONF_SWAPFACTOR=|g" /etc/dphys-swapfile @@ -76,11 +76,11 @@ The build failed because the process exited too early. This probably means the system ran out of memory or someone called 'kill -9' on the process. ``` -**Reason** +#### Reason Node tried to allocate more memory than available on the system. -**Solution** +#### Solution Adjust the node memory variable as described in [JavaScript heap out of memory](#javascript-heap-out-of-memory). But make sure to allocate less memory than the available memory. If that is not sufficient, increase the swap file size of your system and try again. diff --git a/documentation/developers/development-environment.md b/documentation/developers/development-environment.md index 9abefbee0..249f6263c 100644 --- a/documentation/developers/development-environment.md +++ b/documentation/developers/development-environment.md @@ -1,12 +1,13 @@ # Development Environment - - You have 3 development options. Each option has its pros and cons. To interact with GPIO or other hardware, it's required to develop directly on a Raspberry Pi. For general development of Python code (Jukebox) or JavaScript (Webapp), we recommend Docker. Developing on your local machine (Linux, Mac, Windows) works as well and requires all dependencies to be installed locally. -* [Develop in Docker](#develop-in-docker) -* [Develop on Raspberry Pi](#develop-on-raspberry-pi) -* [Develop on local machine](#develop-on-local-machine) +- [Development Environment](#development-environment) + - [Develop in Docker](#develop-in-docker) + - [Develop on Raspberry Pi](#develop-on-raspberry-pi) + - [Steps to install](#steps-to-install) + - [Develop on local machine](#develop-on-local-machine) + - [Using WSL](#using-wsl) ## Develop in Docker @@ -55,3 +56,14 @@ pip install pyzmq ``` You will have to start Jukebox core application and the WebUI separately. The MPD usually runs as a service. + +### Using WSL + +You can also use WSL on Windows 10 or 11. This section describes how to use WSL with Visual Studio Code. + +1. Install a Debian or Ubuntu image from Microsoft Store +2. Install the extension [Remote Explorer](https://marketplace.visualstudio.com/items?itemName=ms-vscode.remote-explorer) in Visual Studio Code +3. Select Remote Explorer +4. Select "WSL Targets" +5. Right-click on the previously installed WSL image and select "Connect in Current/New Window" +6. Follow the instructions from above diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 39518e248..79c2ada2a 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -139,7 +139,6 @@ Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already i Read these threads for details: [thread 1](https://unix.stackexchange.com/questions/456909/socket-already-in-use-but-is-not-listed-mpd) and [thread 2](https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num/5106755#5106755) - ## Test & Develop The Dockerfile is defined to start all Phoniebox related services. @@ -236,31 +235,31 @@ $ docker run -it --rm \ ### Resources -**Mac** +#### Mac * * * -**Windows** +#### Windows * * * * -**Audio** +#### Audio * * * -**MPD** +#### MPD * * * -**ZMQ** +#### ZMQ * diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index 1460c1cb4..817298c60 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -6,7 +6,7 @@ To speed up the Docker build process, we are distributing pre-build versions of Add `build-essential` to be installed additionally with `apt-get`. Additionally, replace the command to download the pre-built library with the following command. -``` +```docker # Compile ZMQ RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ diff --git a/documentation/developers/status.md b/documentation/developers/status.md index eac874608..2fb92897f 100644 --- a/documentation/developers/status.md +++ b/documentation/developers/status.md @@ -227,7 +227,7 @@ Topics marked _in progress_ are already in the process of implementation by comm - [x] Enable/Disable Auto-Hotspot - [x] `run_npm_build` script - [x] Must consider `export NODE_OPTIONS=--max-old-space-size=512` -- [ ] Upload audio files via WebUI https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2138 +- [ ] Upload audio files via WebUI ## Installation Procedure From ca9b5f1216762d203d14866a8ecbf35d1f08e4e8 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:14:59 +0100 Subject: [PATCH 062/121] Cover Art with Cache (#2177) * CoverArt with Cache in Docker, Cache Path on Pi missing * Make cache path available in both Docker and Pi * Fix flake8 errors --- .gitignore | 1 + docker/docker-compose.yml | 1 + requirements.txt | 1 + .../default-settings/jukebox.default.yaml | 3 +- .../components/music_cover_art/__init__.py | 30 ---------- src/jukebox/components/playermpd/__init__.py | 59 +++++++++++++++++-- .../playermpd/coverart_cache_manager.py | 22 +++++++ src/webapp/public/cover-cache/.gitkeep | 0 src/webapp/src/commands/index.js | 13 ++-- .../albums/album-list/album-list-item.js | 23 +++++++- src/webapp/src/components/Player/cover.js | 2 +- src/webapp/src/components/Player/index.js | 17 +++--- 12 files changed, 119 insertions(+), 53 deletions(-) delete mode 100755 src/jukebox/components/music_cover_art/__init__.py create mode 100644 src/jukebox/components/playermpd/coverart_cache_manager.py create mode 100644 src/webapp/public/cover-cache/.gitkeep diff --git a/.gitignore b/.gitignore index 9d38e1b8c..c715a314b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /shared/logs/*.log* /shared/*.* /shared/* +/src/webapp/public/cover-cache/*.* # Application /src/cli_client/pbc diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1679bbeb5..03191dbd5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -41,6 +41,7 @@ services: tty: true volumes: - ../src/jukebox:/root/RPi-Jukebox-RFID/src/jukebox + - ../src/webapp/public/cover-cache:/root/RPi-Jukebox-RFID/src/webapp/build/cover-cache - ../shared:/root/RPi-Jukebox-RFID/shared - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf command: python run_jukebox.py diff --git a/requirements.txt b/requirements.txt index bd2ea6651..ea9546315 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ pyalsaaudio pulsectl python-mpd2 ruamel.yaml +python-slugify # For playlistgenerator requests # For the publisher event reactor loop: diff --git a/resources/default-settings/jukebox.default.yaml b/resources/default-settings/jukebox.default.yaml index 36661c992..5c37f178a 100644 --- a/resources/default-settings/jukebox.default.yaml +++ b/resources/default-settings/jukebox.default.yaml @@ -20,7 +20,6 @@ modules: gpio: gpio.gpioz.plugin sync_rfidcards: synchronisation.rfidcards others: - - music_cover_art - misc pulse: # Reset system volume to this level after start. (Comment out disables and volume is not changed) @@ -146,3 +145,5 @@ speaking_text: sync_rfidcards: enable: false config_file: ../../shared/settings/sync_rfidcards.yaml +webapp: + coverart_cache_path: ../../src/webapp/build/cover-cache diff --git a/src/jukebox/components/music_cover_art/__init__.py b/src/jukebox/components/music_cover_art/__init__.py deleted file mode 100755 index c07f9441e..000000000 --- a/src/jukebox/components/music_cover_art/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Read all cover art from music save it to a cache for the UI to load - -.. note:: Not implemented. This is a feature planned for a future release. -""" -import logging -import jukebox.cfghandler -import jukebox.plugs as plugin - -logger = logging.getLogger('jb.music_cover_art') -cfg = jukebox.cfghandler.get_handler('jukebox') - - -class MusicCoverArt: - def __init__(self): - pass - - @plugin.tag - def get_by_filename_as_base64(self, audio_src: str): - """ - Not implemented. This is a feature planned for a future release. - """ - cover_base64_string = '' - return cover_base64_string - - -@plugin.initialize -def initialize(): - music_cover_art = MusicCoverArt() - plugin.register(music_cover_art, name='ctrl') diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index 3975fdb67..e854ee5ee 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -86,6 +86,7 @@ import logging import time import functools +from slugify import slugify import components.player import jukebox.cfghandler import jukebox.utils as utils @@ -97,6 +98,7 @@ from jukebox.NvManager import nv_manager from .playcontentcallback import PlayContentCallbacks, PlayCardState +from .coverart_cache_manager import CoverartCacheManager logger = logging.getLogger('jb.PlayerMPD') cfg = jukebox.cfghandler.get_handler('jukebox') @@ -154,6 +156,10 @@ def __init__(self): self.decode_2nd_swipe_option() self.mpd_client = mpd.MPDClient() + + coverart_cache_path = cfg.getn('webapp', 'coverart_cache_path') + self.coverart_cache_manager = CoverartCacheManager(os.path.expanduser(coverart_cache_path)) + # The timeout refer to the low-level socket time-out # If these are too short and the response is not fast enough (due to the PI being busy), # the current MPC command times out. Leave these at blocking calls, since we do not react on a timed out socket @@ -464,6 +470,49 @@ def play_card(self, folder: str, recursive: bool = False): self.play_folder(folder, recursive) + @plugs.tag + def get_single_coverart(self, song_url): + """ + Saves the album art image to a cache and returns the filename. + """ + base_filename = slugify(song_url) + + try: + metadata_list = self.mpd_client.listallinfo(song_url) + metadata = {} + if metadata_list: + metadata = metadata_list[0] + + if 'albumartist' in metadata and 'album' in metadata: + base_filename = slugify(f"{metadata['albumartist']}-{metadata['album']}") + + cache_filename = self.coverart_cache_manager.find_file_by_hash(base_filename) + + if cache_filename: + return cache_filename + + # Cache file does not exist + # Fetch cover art binary + album_art_data = self.mpd_client.readpicture(song_url) + + # Save to cache + cache_filename = self.coverart_cache_manager.save_to_cache(base_filename, album_art_data) + + return cache_filename + + except mpd.base.CommandError as e: + logger.error(f"{e.__class__.__qualname__}: {e} at uri {song_url}") + except Exception as e: + logger.error(f"{e.__class__.__qualname__}: {e} at uri {song_url}") + + return "" + + @plugs.tag + def get_album_coverart(self, albumartist: str, album: str): + song_list = self.list_songs_by_artist_and_album(albumartist, album) + + return self.get_single_coverart(song_list[0]['file']) + @plugs.tag def get_folder_content(self, folder: str): """ @@ -562,16 +611,16 @@ def list_all_dirs(self): @plugs.tag def list_albums(self): with self.mpd_lock: - albums = self.mpd_retry_with_mutex(self.mpd_client.list, 'album', 'group', 'albumartist') + album_list = self.mpd_retry_with_mutex(self.mpd_client.list, 'album', 'group', 'albumartist') - return albums + return album_list @plugs.tag - def list_song_by_artist_and_album(self, albumartist, album): + def list_songs_by_artist_and_album(self, albumartist, album): with self.mpd_lock: - albums = self.mpd_retry_with_mutex(self.mpd_client.find, 'albumartist', albumartist, 'album', album) + song_list = self.mpd_retry_with_mutex(self.mpd_client.find, 'albumartist', albumartist, 'album', album) - return albums + return song_list @plugs.tag def get_song_by_url(self, song_url): diff --git a/src/jukebox/components/playermpd/coverart_cache_manager.py b/src/jukebox/components/playermpd/coverart_cache_manager.py new file mode 100644 index 000000000..7883372ba --- /dev/null +++ b/src/jukebox/components/playermpd/coverart_cache_manager.py @@ -0,0 +1,22 @@ +import os + + +class CoverartCacheManager: + def __init__(self, cache_folder_path): + self.cache_folder_path = cache_folder_path + + def find_file_by_hash(self, hash_value): + for filename in os.listdir(self.cache_folder_path): + if filename.startswith(hash_value): + return filename + return None + + def save_to_cache(self, base_filename, album_art_data): + mime_type = album_art_data['type'] + file_extension = 'jpg' if mime_type == 'image/jpeg' else mime_type.split('/')[-1] + cache_filename = f"{base_filename}.{file_extension}" + + with open(os.path.join(self.cache_folder_path, cache_filename), 'wb') as file: + file.write(album_art_data['binary']) + + return cache_filename diff --git a/src/webapp/public/cover-cache/.gitkeep b/src/webapp/public/cover-cache/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/webapp/src/commands/index.js b/src/webapp/src/commands/index.js index bd8fd782e..2352e46b7 100644 --- a/src/webapp/src/commands/index.js +++ b/src/webapp/src/commands/index.js @@ -1,8 +1,13 @@ const commands = { - musicCoverByFilenameAsBase64: { - _package: 'music_cover_art', + getSingleCoverArt: { + _package: 'player', + plugin: 'ctrl', + method: 'get_single_coverart', + }, + getAlbumCoverArt: { + _package: 'player', plugin: 'ctrl', - method: 'get_by_filename_as_base64', + method: 'get_album_coverart', }, directoryTreeOfAudiofolder: { _package: 'player', @@ -17,7 +22,7 @@ const commands = { songList: { _package: 'player', plugin: 'ctrl', - method: 'list_song_by_artist_and_album', + method: 'list_songs_by_artist_and_album', }, getSongByUrl: { _package: 'player', diff --git a/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js b/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js index 8a6c54abf..75882dd0d 100644 --- a/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js +++ b/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js @@ -1,4 +1,4 @@ -import React, { forwardRef } from 'react'; +import React, { forwardRef, useEffect, useState } from 'react'; import { Link, useLocation, @@ -15,9 +15,28 @@ import { import noCover from '../../../../../assets/noCover.jpg'; +import request from '../../../../../utils/request'; + const AlbumListItem = ({ albumartist, album, isButton = true }) => { const { t } = useTranslation(); const { search: urlSearch } = useLocation(); + const [coverImage, setCoverImage] = useState(noCover); + + useEffect(() => { + const getCoverArt = async () => { + const { result } = await request('getAlbumCoverArt', { + albumartist: albumartist, + album: album + }); + if (result) { + setCoverImage(`/cover-cache/${result}`); + }; + } + + if (albumartist && album) { + getCoverArt(); + } + }, [albumartist, album]); const AlbumLink = forwardRef((props, ref) => { const { data } = props; @@ -41,7 +60,7 @@ const AlbumListItem = ({ albumartist, album, isButton = true }) => { > - + { {coverImage && {t('player.cover.title')}} {!coverImage && diff --git a/src/webapp/src/components/Player/index.js b/src/webapp/src/components/Player/index.js index 5efa5e10a..2d56adceb 100644 --- a/src/webapp/src/components/Player/index.js +++ b/src/webapp/src/components/Player/index.js @@ -9,34 +9,31 @@ import SeekBar from './seekbar'; import Volume from './volume'; import PlayerContext from '../../context/player/context'; -import PubSubContext from '../../context/pubsub/context'; import request from '../../utils/request'; -import { pluginIsLoaded } from '../../utils/utils'; const Player = () => { const { state: { playerstatus } } = useContext(PlayerContext); - const { state: { 'core.plugins.loaded': plugins } } = useContext(PubSubContext); const { file } = playerstatus || {}; const [coverImage, setCoverImage] = useState(undefined); const [backgroundImage, setBackgroundImage] = useState('none'); useEffect(() => { - const getMusicCover = async () => { - const { result } = await request('musicCoverByFilenameAsBase64', { audio_src: file }); + const getCoverArt = async () => { + const { result } = await request('getSingleCoverArt', { song_url: file }); if (result) { - setCoverImage(result); + setCoverImage(`/cover-cache/${result}`); setBackgroundImage([ 'linear-gradient(to bottom, rgba(18, 18, 18, 0.7), rgba(18, 18, 18, 1))', - `url(data:image/jpeg;base64,${result})` + `url(/cover-cache/${result})` ].join(',')); }; } - if (pluginIsLoaded(plugins, 'music_cover_art') && file) { - getMusicCover(); + if (file) { + getCoverArt(); } - }, [file, plugins]); + }, [file]); return ( Date: Mon, 25 Dec 2023 08:22:02 +0100 Subject: [PATCH 063/121] Improve docs (#2178) * move GPIO docs and convert to markdown * add wsl * fix markdown warnings * improve docs (remove rst stuff) --- documentation/README.md | 8 +- documentation/builders/gpio.md | 373 +++++++++--------- documentation/builders/rpc-commands.md | 101 ++--- documentation/builders/system.md | 6 +- documentation/builders/troubleshooting.md | 6 +- documentation/developers/rfid/basics.md | 20 +- documentation/developers/rfid/mfrc522_spi.md | 17 +- documentation/developers/rfid/mock_reader.md | 12 +- documentation/developers/rfid/pn532_i2c.md | 4 +- .../developers/rfid/template_reader.md | 18 +- 10 files changed, 286 insertions(+), 279 deletions(-) diff --git a/documentation/README.md b/documentation/README.md index 71ef9010f..b10941a55 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -39,8 +39,12 @@ project check out the [documentation of Version 2](https://github.com/MiczFlor/R ### Where are we? Help wanted! -The initial proof-of-concept phase has been left behind and there is quite some functionality available already. -This is still an ongoing process but the WebUI and RFID-triggered playback of local files work. +Version 3 has reached a mature state and will soon be the default version. +However, some features may still be missing. Please check the [Feature Status](./developers/status.md), if YOUR feature is already implemented. + +> ![NOTE] If version 3 has all the features you need, we recommend using Version 3. + +If there is a feature missing, please open an issue. Features/files from version 2.X will only be copied/merged once they can be integrated and tested. If you don't find your v2.X contributions, it doesn't mean they are obsolete. Things will be integrated step by step. diff --git a/documentation/builders/gpio.md b/documentation/builders/gpio.md index feeb907ef..37622edc0 100644 --- a/documentation/builders/gpio.md +++ b/documentation/builders/gpio.md @@ -6,16 +6,15 @@ The GPIO module needs to be enabled in your main configuration file ``shared/set this entry and modify it accordingly: ```yml - gpioz: - enable: true - config_file: ../../shared/settings/gpio.yaml +gpioz: + enable: true + config_file: ../../shared/settings/gpio.yaml ``` The GPIO configuration itself is stored in a separate file, in this case ``../../shared/settings/gpio.yaml``. The GPIO module uses [GPIOZero](https://gpiozero.readthedocs.io/) as a backend to access the RPi's GPIO pins. -It's a wrapper to integrate GPIOZero into the Jukebox's API, allowing a YAML based configuration and providing -helpful error messages on misconfiguration. +It's a wrapper to integrate GPIOZero into the Jukebox's API, allowing a YAML based configuration and providing helpful error messages on misconfiguration. ## Pin Numbering @@ -27,15 +26,15 @@ The pin numbering is the BCM pin numbering, as is the The GPIOZ configuration file has the following structure: ```yml - pin_factory: - type: rpigpio.RPiGPIOFactory - output_devices: - ... - input_devices: - ... +pin_factory: + type: rpigpio.RPiGPIOFactory +output_devices: + ... +input_devices: + ... ``` -There is no need to touch the header, but some `Developer options` can be found there. +There is no need to touch the header, but some [Developer options](#developer-options) can be found there. A file with examples can be found in ``resources/default-settings/gpio.yaml``. Below you will find easy to adapt recipes for various configuration snippets. @@ -55,14 +54,14 @@ just the same as for assigning card actions. A button to toggle music playback on single press: ```yml - input_devices: - TogglePlayback: - type: Button - kwargs: - pin: 13 - actions: - on_press: - alias: toggle +input_devices: + TogglePlayback: + type: Button + kwargs: + pin: 13 + actions: + on_press: + alias: toggle ``` Each device instantiation must be uniquely named, here ``TogglePlayback``. The name can be freely chosen, as @@ -72,8 +71,9 @@ The parameter ``type`` directly matches the GPIO input device class, in this cas class `LED `. With ``kwargs`` you can set all the class initialization parameters, which relate directly to the class' initialization parameters. -**Important:** You cannot set the class initialization parameters :attr:`pin_factory` or :attr:`name` - from inside ``kwargs``. The name is automatically assigned from the unique name of configuration entry. +> [!IMPORTANT] +> You cannot set the class initialization parameters attribute `pin_factory` or attribute `name` +> from inside ``kwargs``. The name is automatically assigned from the unique name of configuration entry. Usually, only the pin(s) are mandatory parameters. In the section ``actions``, the RPC commands are linked, either as alias (i.e. shortcut) or full [RPC Commands](rpc-commands.md) specification. @@ -94,17 +94,17 @@ the RPi looks: A button to increase the volume by 5 steps every 0.75 seconds as long as it is pressed: ```yml - input_devices: - IncreaseVolume: - type: Button - kwargs: - pin: 13 - hold_time: 0.75 - hold_repeat: True - actions: - on_press: - alias: change_volume - args: +5 +input_devices: + IncreaseVolume: + type: Button + kwargs: + pin: 13 + hold_time: 0.75 + hold_repeat: True + actions: + on_press: + alias: change_volume + args: +5 ``` ### Button: Shutdown @@ -112,15 +112,15 @@ A button to increase the volume by 5 steps every 0.75 seconds as long as it is p A button to shutdown the Jukebox if it is pressed for more than 3 seconds. Note the different ``type`` here! ```yml - input_devices: - IncreaseVolume: - type: LongPressButton - kwargs: - pin: 3 - hold_time: 3 - actions: - on_press: - alias: shutdown +input_devices: + IncreaseVolume: + type: LongPressButton + kwargs: + pin: 3 + hold_time: 3 + actions: + on_press: + alias: shutdown ``` ### Button: Dual Action @@ -128,74 +128,75 @@ A button to shutdown the Jukebox if it is pressed for more than 3 seconds. Note A button to act differently on short and long presses. Go to previous song on single short press, start playlist from the beginning on press longer than 1 second. -Note: the short press action is executed on button release since we don't not know how much longer somebody is going -to press the button. The long press action is executed as soon as the hold time has been reached. +> [!NOTE] +> the short press action is executed on button release since we don't not know how much longer somebody is going +> to press the button. The long press action is executed as soon as the hold time has been reached. ```yml - input_devices: - PreviousSong: - type: ShortLongPressButton - kwargs: - pin: 13 - hold_time: 1 - actions: - on_short_press: - alias: prev_song - on_long_press: - alias: replay +input_devices: + PreviousSong: + type: ShortLongPressButton + kwargs: + pin: 13 + hold_time: 1 + actions: + on_short_press: + alias: prev_song + on_long_press: + alias: replay ``` ### TwinButton: Six function beast Use two buttons to encode up to six actions depending on single / dual press and also short / long press. Single button short presses skip to prev/next song, while long presses decrease/increase the volume. -Here we also use make use of :attr:`hold_repeat`, to have the volume continue to change for as long as we hold the button -down. Twin press toggle pause or changed the audio output sink from speakers to headset. The attribute :attr:`hold_repeat` +Here we also use make use of attribute `hold_repeat`, to have the volume continue to change for as long as we hold the button +down. Twin press toggle pause or changed the audio output sink from speakers to headset. The attribute attribute `hold_repeat` only applies to single press actions, never to dual press actions. ```yml - input_devices: - SixActionBeast: - type: TwinButton - kwargs: - a: 12 - b: 13 - hold_repeat: true - actions: - on_short_press_a: - alias: prev_song - on_short_press_b: - alias: next_song - on_short_press_ab: - alias: toggle - on_long_press_a: - alias: change_volume - args: -3 - on_long_press_b: - alias: change_volume - args: 3 - on_long_press_ab: - alias: toggle_output +input_devices: + SixActionBeast: + type: TwinButton + kwargs: + a: 12 + b: 13 + hold_repeat: true + actions: + on_short_press_a: + alias: prev_song + on_short_press_b: + alias: next_song + on_short_press_ab: + alias: toggle + on_long_press_a: + alias: change_volume + args: -3 + on_long_press_b: + alias: change_volume + args: 3 + on_long_press_ab: + alias: toggle_output ``` With a TwinButton not all functions need to be assigned. A button that only does prev/next song and causes as shutdown only on dual press with a minimum hold time of 2 seconds looks like this: ```yml - input_devices: - CombinationButton: - type: TwinButton - kwargs: - a: 12 - b: 13 - hold_time: 2 - actions: - on_short_press_a: - alias: prev_song - on_short_press_b: - alias: next_song - on_long_press_ab: - alias: toggle_output +input_devices: + CombinationButton: + type: TwinButton + kwargs: + a: 12 + b: 13 + hold_time: 2 + actions: + on_short_press_a: + alias: prev_song + on_short_press_b: + alias: next_song + on_long_press_ab: + alias: toggle_output ``` ### Rotary Encoder: Volume Control @@ -205,22 +206,22 @@ It has four pins, typically labelled DT, CLK, SW, GND. Connect GND to ground. Co RPi with a 1 kOhm resistor each - these are pins ``a`` in ``b`` in the configuration. If later the rotation direction does not match, simply swap the pins in the configuration file. The pin SW (for switch) is not always present. It is a button when the rotary encoder is pressed from the top. Configure a -regular button entry separately for this button, e.g. `Button: Toggle Playback`_. +regular button entry separately for this button, e.g. [Button Toggle Playback](#button-toggle-playback). ```yml - input_devices: - VolumeRotator: - type: RotaryEncoder - kwargs: - a: 5 - b: 6 - actions: - on_rotate_clockwise: - alias: change_volume - args: 5 - on_rotate_counter_clockwise: - alias: change_volume - args: -5 +input_devices: + VolumeRotator: + type: RotaryEncoder + kwargs: + a: 5 + b: 6 + actions: + on_rotate_clockwise: + alias: change_volume + args: 5 + on_rotate_counter_clockwise: + alias: change_volume + args: -5 ``` ### Rotary Encoder: Previous/Next Song @@ -245,59 +246,64 @@ Configuring output devices contains 2 aspects: activates the device on e.g. RFID card read. There are many predefined connections available. Check them all out in class `components.gpio.gpioz.plugin.connectivity` -**Note:** There are two different types of buzzers: +### Buzzers + +There are two different types of buzzers: -_Active buzzer_ +#### Active buzzer These buzzers make a single-frequency beep sound when a constant voltage is applied. A common module is the KY-012. These buzzers must be mapped to class `components.gpio.gpioz.core.output_devices.Buzzer`. -_Passive buzzer_ +#### Passive buzzer + These buzzers must be used with a PWM signal but then can emit different frequency beeps. This is all handled by us. A common module is the KY-006. These buzzers _must_ be mapped to class `components.gpio.gpioz.core.output_devices.TonalBuzzer` +#### General + For many connection function is does not matter if a class `components.gpio.gpioz.core.output_devices.Buzzer` or a class `components.gpio.gpioz.core.output_devices.TonalBuzzer` is connected. -The connection function takes care of the differences internally - as long as the class matches the physical -hardware device! +The connection function takes care of the differences internally - as long as the class matches the physical hardware device! ### Status LED An LED that lights up when the Jukebox service is operational. ```yml - output_devices: - StatusLED: - type: LED - connect: - - gpio.gpioz.plugin.connectivity.register_status_led_callback - kwargs: - pin: 17 +output_devices: + StatusLED: + type: LED + connect: + - gpio.gpioz.plugin.connectivity.register_status_led_callback + kwargs: + pin: 17 ``` As with the input devices, every output device requires a unique, but freely chosen name - here ``StatusLED``. The parameter ``type`` directly matches the GPIO output device class, in this case -class ``. +class `components.gpio.gpioz.core.output_devices.LED`. The parameters in ``kwargs`` relate to the class initialization parameters. The ``connect`` option is a list of functions to call to connect this device with a function inside the Jukebox. An output device can be used by multiple functions. -.. important:: You cannot set the class initialization parameters :attr:`pin_factory` or :attr:`name` - from inside ``kwargs``. The name is automatically assigned from the unique name of configuration entry. +> [!IMPORTANT] +> You cannot set the class initialization parameters attribute `pin_factory` or attribute `name` +> from inside ``kwargs``. The name is automatically assigned from the unique name of configuration entry. ### Card Read Buzzer Sound a Piezzo Buzzer once when a card swipe has been detected. For unknown cards, sound it 3 times. ```yml - output_devices: - RfidBuzzer: - type: Buzzer - connect: - - gpio.gpioz.plugin.connectivity.register_rfid_callback - kwargs: - pin: 12 +output_devices: + RfidBuzzer: + type: Buzzer + connect: + - gpio.gpioz.plugin.connectivity.register_rfid_callback + kwargs: + pin: 12 ``` ### Card Read + Volume + Status Buzzer @@ -307,15 +313,15 @@ On top sound a short beep when minimum or maximum volume is reached. The only difference is the two additional connection functions. ```yml - output_devices: - RfidBuzzer: - type: Buzzer - connect: - - gpio.gpioz.plugin.connectivity.register_rfid_callback - - gpio.gpioz.plugin.connectivity.register_status_buzzer_callback - - gpio.gpioz.plugin.connectivity.register_volume_buzzer_callback - kwargs: - pin: 12 +output_devices: + RfidBuzzer: + type: Buzzer + connect: + - gpio.gpioz.plugin.connectivity.register_rfid_callback + - gpio.gpioz.plugin.connectivity.register_status_buzzer_callback + - gpio.gpioz.plugin.connectivity.register_volume_buzzer_callback + kwargs: + pin: 12 ``` ### Tonal Status Buzzer @@ -324,14 +330,14 @@ Have an active buzzer play a 4 note melody on startup and a 3 note melody on clo Use the same buzzer to beep on RFID card swipes. ```yml - output_devices: - RfidBuzzer: - type: TonalBuzzer - connect: - - gpio.gpioz.plugin.connectivity.register_status_tonalbuzzer_callback - - gpio.gpioz.plugin.connectivity.register_rfid_callback - kwargs: - pin: 12 +output_devices: + RfidBuzzer: + type: TonalBuzzer + connect: + - gpio.gpioz.plugin.connectivity.register_status_tonalbuzzer_callback + - gpio.gpioz.plugin.connectivity.register_rfid_callback + kwargs: + pin: 12 ``` ### Card Read LED @@ -339,58 +345,55 @@ Use the same buzzer to beep on RFID card swipes. Just like `Card Read Buzzer`, but flash a LED instead of buzzing a sound. The difference is the output device type. ```yml - output_devices: - RfidLED: - type: LED - connect: - - gpio.gpioz.plugin.connectivity.register_rfid_callback - kwargs: - pin: 12 +output_devices: + RfidLED: + type: LED + connect: + - gpio.gpioz.plugin.connectivity.register_rfid_callback + kwargs: + pin: 12 ``` ### Volume LED -Have a LED change it's brightness to reflect the current volume level. It also flashes when minimum or maximum -volume level is reached. +Have a LED change it's brightness to reflect the current volume level. It also flashes when minimum or maximum volume level is reached. ```yml - output_devices: - VolumeLED: - type: PWMLED - connect: gpio.gpioz.plugin.connectivity.register_volume_led_callback - kwargs: - pin: 18 +output_devices: + VolumeLED: + type: PWMLED + connect: gpio.gpioz.plugin.connectivity.register_volume_led_callback + kwargs: + pin: 18 ``` ### Color Volume LED Have an RGBLED change it's color to reflect the current volume level. It also flashes when minimum or maximum -volume level is reached. RGBLED's can be found as modules, e.g. KY-016 or KY-009, or as individual components from any -electronics shop. +volume level is reached. RGBLED's can be found as modules, e.g. KY-016 or KY-009, or as individual components from any electronics shop. ```yml - output_devices: - VolumeLED: - type: RGBLED - connect: gpio.gpioz.plugin.connectivity.register_volume_rgbled_callback - kwargs: - pin: 18 +output_devices: + VolumeLED: + type: RGBLED + connect: gpio.gpioz.plugin.connectivity.register_volume_rgbled_callback + kwargs: + pin: 18 ``` ### Bluetooth audio output LED Indicates the current audio output sink. LED is off when audio sink is primary sink, and -on when audio sink is secondary sink (e.g. a bluetooth headset). When sink toggle fails, LED blinks -3 times. +on when audio sink is secondary sink (e.g. a bluetooth headset). When sink toggle fails, LED blinks 3 times. ```yml - output_devices: - HeadsetConnected: - type: LED - connect: - - gpio.gpioz.plugin.connectivity.register_audio_sink_change_callback - kwargs: - pin: 27 +output_devices: + HeadsetConnected: + type: LED + connect: + - gpio.gpioz.plugin.connectivity.register_audio_sink_change_callback + kwargs: + pin: 27 ``` ## Developer options @@ -405,8 +408,8 @@ configured the [Mock RFID Reader](../developers/rfid/mock_reader.md), the GPIO input and output devices are added to the GUI. Simply change the header in the configuration file to: ```yml - pin_factory: - type: mock.MockFactory +pin_factory: + type: mock.MockFactory ``` ![Mock GPIO](mock_gpio.png) @@ -419,9 +422,9 @@ happen on a RPi board. See the GPIOZero documentation how to set it up. Simply change the header in the configuration file to enable it. Host is the IP address of your RPi board. ```yml - pin_factory: - type: pigpio.PiGPIOFactory - pigpio.PiGPIOFactory: - kwargs: - host: 192.168.178.32 +pin_factory: + type: pigpio.PiGPIOFactory + pigpio.PiGPIOFactory: + kwargs: + host: 192.168.178.32 ``` diff --git a/documentation/builders/rpc-commands.md b/documentation/builders/rpc-commands.md index 374c27d97..181034f4a 100644 --- a/documentation/builders/rpc-commands.md +++ b/documentation/builders/rpc-commands.md @@ -11,26 +11,26 @@ Here is the essence of what you need to know: An RPC command consists of up to three parts - #. the function to execute (e.g. play_folder, change_volume) - #. the positional arguments (optional) - #. the keyword arguments (optional) +1. the function to execute (e.g. play_folder, change_volume) +2. the positional arguments (optional) +3. the keyword arguments (optional) The function specification consists of two (e.g., ``host.shutdown``) or three terms (e.g., ``volume.ctrl.change_volume``). In configuration files, this will look like this: -.. code-block:: yaml - - package: host - plugin: shutdown +```yml +package: host +plugin: shutdown +``` Or like this for a three part function with the argument set to ``5``: -.. code-block:: yaml - - package: volume - plugin: ctrl - method: change_volume - args: [5] +```yml +package: volume +plugin: ctrl +method: change_volume +args: [5] +``` The keyword ``method`` is optional. If needs to be used depends on the function you want to call. @@ -41,63 +41,64 @@ to a pre-defined RPC command, e.g. ``play_card`` maps to ``player.ctrl.play_card Instead of -.. code-block:: yaml - - package: player - plugin: ctrl - method: play_card - args: [path/to/folder] +```yml +package: player +plugin: ctrl +method: play_card +args: [path/to/folder] +``` -you can simply specify instead : +you can simply specify instead: -.. code-block:: yaml +```yml +alias: play_card +args: [path/to/folder] +``` - alias: play_card - args: [path/to/folder] - -Using in alias is optional. But if the keyword is present in the configuration it takes precedence over an explicit -specified RPC command. +Using in alias is optional. But if the keyword is present in the configuration it takes precedence over an explicit specified RPC command. ## Arguments Arguments can be specified in similar fashion to Python function arguments: as positional arguments and / or keyword arguments. Let's check out play_card, which is defined as: -.. py:function:: play_card(...) -> player.ctrl.play_card(folder: str, recursive: bool = False) +```python +play_card(...) -> player.ctrl.play_card(folder: str, recursive: bool = False) :noindex: :param folder: Folder path relative to music library path :param recursive: Add folder recursively +``` This means it takes two arguments: - * folder of type string - * recursive of type bool +* folder of type string +* recursive of type bool In the following examples, we will always use the alias for smaller configuration text. All three examples do exactly the same, but use different ways of specifying the command. -.. code-block:: yaml - - alias: play_card - args: [path/to/folder, True] - -.. code-block:: yaml - - alias: play_card - args: [path/to/folder] - kwargs: - recursive: True - -.. code-block:: yaml - - alias: play_card - kwargs: - folder: path/to/folder - recursive: True - -.. important:: *args* must be a **list** of arguments to be passed! Even if only a single argument is passed. - So, use *args: [value]*. We try catch mis-uses but that might not always work. +```yml +alias: play_card +args: [path/to/folder, True] +``` + +```yml +alias: play_card +args: [path/to/folder] +kwargs: + recursive: True +``` + +```yml +alias: play_card +kwargs: + folder: path/to/folder + recursive: True +``` + +> [!IMPORTANT] *args* must be a **list** of arguments to be passed! Even if only a single argument is passed. +> So, use *args: [value]*. We try catch mis-uses but that might not always work. You will find some more examples the configuration of the [Card Database](card-database.md) diff --git a/documentation/builders/system.md b/documentation/builders/system.md index e981678c0..2f7df8888 100644 --- a/documentation/builders/system.md +++ b/documentation/builders/system.md @@ -10,8 +10,8 @@ The system consists of 4. [Web UI](system.md#web-ui) which is served through an Nginx web server 5. A set of [Configuration Tools](../developers/coreapps.md#configuration-tools) and a set of [Developer Tools](../developers/coreapps.md#developer-tools) -.. note:: The default install puts everything into the users home folder `~/RPi-Jukebox-RFID`. - Another folder might work, but is certainly not tested. +> [!NOTE] The default install puts everything into the users home folder `~/RPi-Jukebox-RFID`. +> Another folder might work, but is certainly not tested. ## Music Player Daemon (MPD) @@ -34,7 +34,7 @@ $ systemctl --user start mpd $ systemctl --user stop mpd ``` -.. important:: Never start or enable the system-wide MPD service with `sudo systemctl start mpd`! +> [!IMPORTANT] Never start or enable the system-wide MPD service with `sudo systemctl start mpd`! To check if MPD is running or has issues, use diff --git a/documentation/builders/troubleshooting.md b/documentation/builders/troubleshooting.md index d68230f81..2f34af083 100644 --- a/documentation/builders/troubleshooting.md +++ b/documentation/builders/troubleshooting.md @@ -58,7 +58,7 @@ shared/logs/errors.log : Only Errors and Warnings For debugging, it is usually very helpful to observe the apps output directly on the console log. -``` bash +```bash # Make sure the Jukebox service is stopped: $ systemctl --user stop jukebox-daemon @@ -77,7 +77,7 @@ It is possible to start the Jukebox with a catch-all debug enabler with a logger Attention: This only emits messages to the console and does not write to the log files! This is more a fallback features: -``` bash +```bash $ cd src/jukebox $ ./run_jukebox.py -vv ``` @@ -94,6 +94,6 @@ gone pear-shaped. Services are restarted automatically when they fail. Things are just not behaving as expected? Time to check the system logs: -``` bash +```bash $ journalctl --user -b -u jukebox-daemon ``` diff --git a/documentation/developers/rfid/basics.md b/documentation/developers/rfid/basics.md index 7d557ed06..27464924b 100644 --- a/documentation/developers/rfid/basics.md +++ b/documentation/developers/rfid/basics.md @@ -16,7 +16,7 @@ behavior can be deactivated for individual cards. ## Reader Types -#### place-capable: +### Place-capable Some readers give a single event signal when the card is placed on the reader. This is sufficient to build a fully-featured Jukebox. @@ -28,7 +28,7 @@ Generally, **not** all [USB-based RFID readers](genericusb.md) are place-capable The known place-capable readers are [RDM6300 Reader](rdm6300.md), [MFRC522 SPI Reader](mfrc522_spi.md) or [PN532 I2C Reader](pn532_i2c.md). -#### Frequency: +### Frequency Readers operate on one of two different frequencies: 125kHz or 13.56 MHz. Make sure to buy compatible cards, RFID stickers or key fobs working with the same frequency as the reader. @@ -44,7 +44,7 @@ settings any time. Some options are not covered by the tool. You may change the file manually. -``` yaml +```yaml rfid: readers: read_00: @@ -60,33 +60,33 @@ rfid: For each reader, there is an entry `read_XX`. -#### module: +### module Indicates the Python package used for this reader. Filled by the RFID configuration tool. -#### config: +### config Filled by the [RFID reader configuration tool](../coreapps.md#RFID-Reader) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. -#### same_id_delay: float \| integer +### same_id_delay: float \| integer Minimum delay in seconds between 2 card detections before triggering a new action. This is to prevent double triggering or bouncing. -#### place_not_swipe: true \| false +### place_not_swipe: true \| false For place-capable RFID readers enable dual action mode: a start action (e.g. playing) on card placement and card removal action (e.g. pause). -#### card_removal_action: Dictionary +### card_removal_action: Dictionary Executes the given function on card removal. Only relevant if place_not_swipe is true. The action is identical for all cards read on that reader. The removal-action can be set to ignored on a card-by-card basis. More on card action configurations in [RPC Commands](../../builders/rpc-commands.md). > [!NOTE] > Developer's note: The reason for a unique removal action for all cards is that card triggering and card removal are happening in two separate threads. Removal needs to be in a time-out thread. Thus, we would need to transport information from one thread to another. This can be done of course but is not implemented (yet). Ignoring card removal is much easier and works for now. -#### log_ignored_cards: true \| false +### log_ignored_cards: true \| false Log all cards that are ignored due to same_id_delay. This is a option for developers. Don't use it unless you need it for debugging as it has the potential to spam your log files. -#### Second Swipe +### Second Swipe Looking for 'Second Swipe' option? That is part of the Player configuration and not part of the RFID configuration, as the 'Second Swipe' action needs to take into account the player state, which can also be altered through the WebUI. diff --git a/documentation/developers/rfid/mfrc522_spi.md b/documentation/developers/rfid/mfrc522_spi.md index 5dc46aab6..8a04f729e 100644 --- a/documentation/developers/rfid/mfrc522_spi.md +++ b/documentation/developers/rfid/mfrc522_spi.md @@ -6,8 +6,7 @@ RC522 RFID reader via SPI connection. ## Installation -Run the [RFID reader configuration tool](../coreapps.md#RFID-Reader) for guided -installation. +Run the [RFID reader configuration tool](../coreapps.md#RFID-Reader) for guided installation. ## Options @@ -18,11 +17,11 @@ are just routed through to spidev. Have a look at the spidev documentation for details if you really want to use a different SPI bus. The default setup makes most sense for almost everyone. -#### spi_bus *(default=0)* +### spi_bus *(default=0)* The SPI Bus ID. The default bus is 0. For other bus IDs, the RPi also needs to re-configured. For that reason we set this to zero. -#### spi_ce *(default=0)* +### spi_ce *(default=0)* SPI chip enable pin. On default SPI bus 0, this can be @@ -31,26 +30,26 @@ SPI chip enable pin. On default SPI bus 0, this can be For other SPI buses refer to RPi documentation. -#### pin_irq +### pin_irq Mandatory IRQ pin. This can be any GPIO pin. -#### pin_rst *(default=0)* +### pin_rst *(default=0)* Reset pin for hardware reset. This is an optional pin. If not used, - hardware reset will only be performed by power-on-reset. This has been tested on works fine. - you **must** tie the reset pin of the MFRC522 board **high**! -#### mode_legacy *(default=false)* +### mode_legacy *(default=false)* 4-byte-only legacy mode: previously the pirc522 library could only read the lower 4 bytes of a card UID. It can now read 4-byte and full 7-byte UIDs. Legacy mode turns back to the old behaviour. This only makes sense, if you already have an large RFID collection and do not want to re-assign every card. -#### antenna_gain *(default=4)* +### antenna_gain *(default=4)* Antenna gain factor of the RFID reader chip on the MFRC522 board. -#### log_all_cards *(default=false)* +### log_all_cards *(default=false)* If true all card read-outs will be logged, even when card is permanently on reader. Only for debugging. diff --git a/documentation/developers/rfid/mock_reader.md b/documentation/developers/rfid/mock_reader.md index 5daee0ab5..4d5a3ea36 100644 --- a/documentation/developers/rfid/mock_reader.md +++ b/documentation/developers/rfid/mock_reader.md @@ -17,10 +17,10 @@ If you [mock the GPIO pins](../../../src/jukebox/components/gpio/gpioz/README.rs > Replacing the TK lib in Anaconda's environment with the system `libtk`. > However, this depends on an exact version match of the `libtk`. > ->``` bash ->cd /path/to/anaconda3/envs/rpi/lib ->mv ./libtk8.6.so ./libtk8.6.so.bak ->ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so libtk8.6.so ->``` +> ``` bash +> cd /path/to/anaconda3/envs/rpi/lib +> mv ./libtk8.6.so ./libtk8.6.so.bak +> ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so libtk8.6.so +> ``` > ->An alternative is to simply use Python's `venv` module to create a virtual environment. This uses your systems Python version and thus will work with your system `libtk`. It might not be the RPi's Python version - but that should not matter in most cases. +> An alternative is to simply use Python's `venv` module to create a virtual environment. This uses your systems Python version and thus will work with your system `libtk`. It might not be the RPi's Python version - but that should not matter in most cases. diff --git a/documentation/developers/rfid/pn532_i2c.md b/documentation/developers/rfid/pn532_i2c.md index 819f051f3..d60cb2e54 100644 --- a/documentation/developers/rfid/pn532_i2c.md +++ b/documentation/developers/rfid/pn532_i2c.md @@ -24,8 +24,8 @@ range and no problem with various types of cards and stickers. You can usually pick up a board at -> - -> - +* +* ## Board Connections diff --git a/documentation/developers/rfid/template_reader.md b/documentation/developers/rfid/template_reader.md index 6a7a8b0c3..5b8458691 100644 --- a/documentation/developers/rfid/template_reader.md +++ b/documentation/developers/rfid/template_reader.md @@ -24,19 +24,19 @@ Your new reader is a python subpackage with these three mandatory files components/rfid/hardware/awesome_reader/ +- awesome_reader.py <-- The actual reader module +- description.py <-- A description module w/o dependencies. Do not change the filename! - +- README.rst <-- The Readme + +- README.md <-- The Readme ``` -The module documentation must go into a separate file, called README.ME. +The module documentation must go into a separate file, called README.md. ## Conventions -- Single reader per directory / subpackage -- reader module directory name and reader module file name must be +- Single reader per directory / subpackage +- reader module directory name and reader module file name must be identical -- Obviously awesome_reader will be replaced with something more +- Obviously awesome_reader will be replaced with something more descriptive. The naming scheme for the subpackage is - - \\_\\_\ - - e.g. generic_usb/generic_usb.py - - e.g. pn532_spi/pn532_spi.py - - ... + - \\_\\_\ + - e.g. generic_usb/generic_usb.py + - e.g. pn532_spi/pn532_spi.py + - ... From c9b6cd76bea87796287f749d4f9c2383f1f87f24 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Mon, 25 Dec 2023 23:28:53 +0100 Subject: [PATCH 064/121] Update Pulseaudio docs for Docker on Mac (#2176) --- docker/jukebox.Dockerfile | 2 +- documentation/developers/docker.md | 45 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index f842a7394..3249edcb8 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -21,7 +21,7 @@ RUN usermod -aG pulse ${USER} # Install all Jukebox dependencies RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ - gcc at wget \ + g++ at wget \ espeak mpc mpg123 git ffmpeg spi-tools netcat \ python3 python3-venv python3-dev python3-mutagen diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 79c2ada2a..d7242750e 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -49,6 +49,9 @@ They can be run individually or in combination. To do that, we use 1. [Install Docker & Compose (Mac)](https://docs.docker.com/docker-for-mac/install/) 2. [Install pulseaudio](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712) (Other references: [[1]](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html), [[2]](https://stackoverflow.com/a/50939994/1062438)) +> [!NOTE] +> In order for Pulseaudio to work properly with Docker on your Mac, you need to start Pulseaudio in a specific way. Otherwise MPD will throw an exception. See [Pulseaudio issues on Mac](#pulseaudio-issue-on-mac) for more info. + ``` bash // Build Images $ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build @@ -169,6 +172,38 @@ details. ### `mpd` container +#### Pulseaudio issue on Mac + +If you notice the following exception while running MPD in Docker, it refers to a incorrect setup of your Mac host Pulseaudio. + +``` +mpd | ALSA lib pulse.c:242:(pulse_connect) PulseAudio: Unable to connect: Connection refused +mpd | exception: Failed to read mixer for 'Global ALSA->Pulse stream': failed to attach to pulse: Connection refused +``` + +To fix the issue, try the following. + +1. Stop your Pulseaudio service + ``` + brew service stop pulseaudio + ``` +2. Start Pulseaudio with this command + ``` + pulseaudio --load=module-native-protocol-tcp --exit-idle-time=-1 --daemon + ``` +3. Check if daemon is working + ``` + pulseaudio --check -v + ``` + +Everything else should have been set up properly as a [prerequisite](#mac) + +* [Source](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712) + + + +#### Other error messages + When starting the `mpd` container, you will see the following errors. You can ignore them, MPD will run. @@ -213,6 +248,16 @@ jukebox | 171:__init__.py - jb.host.lnx - MainThread - E jukebox | 319:server.py - jb.pub.server - host.timer.cputemp - ERROR - Publish command from different thread 'host.timer.cputemp' than publisher was created from 'MainThread'! ``` +#### Pulseaudio and Volume issues + +If you encounter the following error, refer to [Pulseaudio issues on Mac](#pulseaudio-issue-on-mac). + +``` +jukebox | 21.12.2023 08:50:09 - 629:plugs.py - jb.plugin - MainThread - ERROR - Ignoring failed package load finalizer: 'volume.finalize()' +jukebox | 21.12.2023 08:50:09 - 630:plugs.py - jb.plugin - MainThread - ERROR - Reason: NameError: name 'pulse_control' is not defined +``` + + ## Appendix ### Individual Docker Image From 3a904b5f97d107806a8fe31dc21256577b3996bf Mon Sep 17 00:00:00 2001 From: s-martin Date: Thu, 28 Dec 2023 13:35:00 +0100 Subject: [PATCH 065/121] Activate codeql analysis (#2182) * Activate codeql analysis * Update codeql version * Update checkout version * Rename workflow file * Update name of workflow * Setup Python deps manually * Install packages * Fix packages --- ...ql-analysis.yml => codeql-analysis_v3.yml} | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) rename .github/workflows/{codeql-analysis.yml => codeql-analysis_v3.yml} (72%) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis_v3.yml similarity index 72% rename from .github/workflows/codeql-analysis.yml rename to .github/workflows/codeql-analysis_v3.yml index df6b2837a..1c1c68ba3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis_v3.yml @@ -9,14 +9,14 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: "CodeQL v3" on: push: - branches: [ develop ] + branches: [ future3/develop ] pull_request: # The branches below must be a subset of the branches above - branches: [ develop ] + branches: [ future3/develop ] schedule: - cron: '16 18 * * 0' @@ -35,22 +35,37 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install dependencies + run: | + # Install necessary packages + sudo apt-get install libasound2-dev pulseaudio + python -m pip install --upgrade pip + pip install -r requirements.txt + # Set the `CODEQL-PYTHON` environment variable to the Python executable + # that includes the dependencies + echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main + setup-python-dependencies: false # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +79,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From c713610ff87fdaa434ff751dfdea118584b3e47f Mon Sep 17 00:00:00 2001 From: Vito Zanotelli Date: Tue, 2 Jan 2024 21:51:14 +0100 Subject: [PATCH 066/121] Reorder 'USER' command (#2199) This makes sure the 'venv' is initalized using the correct user, preventing permission errors Closes issue #2198 Co-authored-by: pre-commit fix Vito Zanotelli --- docker/jukebox.Dockerfile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index 3249edcb8..e7a7a45b1 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -25,13 +25,18 @@ RUN apt-get update && apt-get install -qq -y \ espeak mpc mpg123 git ffmpeg spi-tools netcat \ python3 python3-venv python3-dev python3-mutagen +# Copy in the source code using the correct permissions +COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ + +# Switch to the `$USER` (typically `pi`) +USER ${USER} +WORKDIR ${HOME} + +# Initialize venv ENV VIRTUAL_ENV=${INSTALLATION_PATH}/.venv RUN python3 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" -USER ${USER} -WORKDIR ${HOME} -COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt From c4ee966d92614caa2e5bca552d52333dd3ddaac9 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Wed, 3 Jan 2024 23:37:49 +0100 Subject: [PATCH 067/121] add workflow files to paths to trigger run (#2202) --- .github/workflows/test_docker_debian_v3.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test_docker_debian_v3.yml b/.github/workflows/test_docker_debian_v3.yml index f333d15eb..06718afba 100644 --- a/.github/workflows/test_docker_debian_v3.yml +++ b/.github/workflows/test_docker_debian_v3.yml @@ -8,6 +8,7 @@ on: branches: - 'future3/**' paths: + - '.github/workflows/test_docker_debian*.yml' - 'installation/**' - 'ci/**' - 'resources/**' @@ -20,6 +21,7 @@ on: - future3/develop - future3/main paths: + - '.github/workflows/test_docker_debian*.yml' - 'installation/**' - 'ci/**' - 'resources/**' From 03e64ff8680cd214f61fd56a2b6049c82b1b2d36 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:35:04 +0100 Subject: [PATCH 068/121] New card actions: play, pause, prev, next, toggle, repeat, shuffle (#2179) * Adding additional player controls to assign to cards * streamline some RPC command namings across jukebox and webapp * Fix typos * Fix Repeat toggles * Fix toggle_shuffle * Fix typo in command * Fix another typo * Fix wrong parameter for shuffle * Update German translation for single-repeat * Simplify Shuffle option * Simplify Repeat option * Streamline Player functions with new set_ methods in jukebox * Abstract OptionsSelector * Refactor some code * Fix indentation * Rename set_ methods * Undo some doc changes --- documentation/developers/docker.md | 15 ++++- documentation/developers/status.md | 7 +- src/jukebox/components/playermpd/__init__.py | 55 +++++++++++++-- src/jukebox/components/rpc_command_alias.py | 18 +++++ src/webapp/public/locales/de/translation.json | 44 +++++++++--- src/webapp/public/locales/en/translation.json | 45 ++++++++++--- src/webapp/src/commands/index.js | 13 +++- .../Cards/controls/actions/audio/index.js | 34 +++++++++- .../actions/audio/slider-change-volume.js | 4 +- .../controls/actions/synchronisation/index.js | 11 ++- .../rfidcards/change-on-rfid-scan-options.js | 67 ------------------- .../Cards/controls/options-selector.js | 61 +++++++++++++++++ src/webapp/src/components/Player/controls.js | 34 +++++----- src/webapp/src/components/Player/cover.js | 6 +- src/webapp/src/components/Player/index.js | 2 +- src/webapp/src/components/Player/seekbar.js | 2 +- src/webapp/src/config.js | 9 ++- src/webapp/src/i18n.js | 1 + 18 files changed, 297 insertions(+), 131 deletions(-) delete mode 100644 src/webapp/src/components/Cards/controls/actions/synchronisation/rfidcards/change-on-rfid-scan-options.js create mode 100644 src/webapp/src/components/Cards/controls/options-selector.js diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index d7242750e..80651ce84 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -47,7 +47,20 @@ They can be run individually or in combination. To do that, we use ### Mac 1. [Install Docker & Compose (Mac)](https://docs.docker.com/docker-for-mac/install/) -2. [Install pulseaudio](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712) (Other references: [[1]](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html), [[2]](https://stackoverflow.com/a/50939994/1062438)) +2. Install pulseaudio + 1. Use Homebrew to install + ``` + $ brew install pulseaudio + ``` + 2. Enable pulseaudio network capabilities. In an editor, open `/opt/homebrew/Cellar/pulseaudio/16.1/etc/pulse/default.pa` (you might need to adapt this path to your own system settings). Uncomment the following line. + ``` + load-module module-native-protocol-tcp + ``` + 3. Restart the pulseaudio service + ``` + $ brew services restart pulseaudio + ``` + 4. If you have trouble with your audio, try these resources to troubleshoot: [[1]](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712), [[2]](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html), [[3]](https://stackoverflow.com/a/50939994/1062438) > [!NOTE] > In order for Pulseaudio to work properly with Docker on your Mac, you need to start Pulseaudio in a specific way. Otherwise MPD will throw an exception. See [Pulseaudio issues on Mac](#pulseaudio-issue-on-mac) for more info. diff --git a/documentation/developers/status.md b/documentation/developers/status.md index 2fb92897f..48c2b6c3b 100644 --- a/documentation/developers/status.md +++ b/documentation/developers/status.md @@ -107,10 +107,9 @@ Topics marked _in progress_ are already in the process of implementation by comm - [ ] Folder configuration (_in progress_) - [ ] [Reference](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/MANUAL#manage-playout-behaviour) - [ ] Resume: Save and restore position (how interact with shuffle?) - - [ ] Single: Enable mpc single - - [ ] Shuffle: Enable mpc random (not shuffle) - - Rename to random, as this is mpc random - - [ ] Loop: Loop playlist + - [ ] Repeat Playlist + - [ ] Repeat Song + - [ ] Shuffle ### MPD Player diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index e854ee5ee..c77c21051 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -335,11 +335,6 @@ def seek(self, new_time): with self.mpd_lock: self.mpd_client.seekcur(new_time) - @plugs.tag - def shuffle(self, random): - # As long as we don't work with waiting lists (aka playlist), this implementation is ok! - self.mpd_retry_with_mutex(self.mpd_client.random, 1 if random else 0) - @plugs.tag def rewind(self): """ @@ -363,7 +358,6 @@ def replay(self): @plugs.tag def toggle(self): """Toggle pause state, i.e. do a pause / resume depending on current state""" - logger.debug("Toggle") with self.mpd_lock: self.mpd_client.pause() @@ -378,8 +372,27 @@ def replay_if_stopped(self): if self.mpd_status['state'] == 'stop': self.play_folder(self.music_player_status['player_status']['last_played_folder']) + # Shuffle + def _shuffle(self, random): + # As long as we don't work with waiting lists (aka playlist), this implementation is ok! + self.mpd_retry_with_mutex(self.mpd_client.random, 1 if random else 0) + @plugs.tag - def repeatmode(self, mode): + def shuffle(self, option='toggle'): + if option == 'toggle': + if self.mpd_status['random'] == '0': + self._shuffle(1) + else: + self._shuffle(0) + elif option == 'enable': + self._shuffle(1) + elif option == 'disable': + self._shuffle(0) + else: + logger.error(f"'{option}' does not exist for 'shuffle'") + + # Repeat + def _repeatmode(self, mode): if mode == 'repeat': repeat = 1 single = 0 @@ -394,6 +407,34 @@ def repeatmode(self, mode): self.mpd_client.repeat(repeat) self.mpd_client.single(single) + @plugs.tag + def repeat(self, option='toggle'): + if option == 'toggle': + if self.mpd_status['repeat'] == '0': + self._repeatmode('repeat') + elif self.mpd_status['repeat'] == '1' and self.mpd_status['single'] == '0': + self._repeatmode('single') + else: + self._repeatmode(None) + elif option == 'toggle_repeat': + if self.mpd_status['repeat'] == '0': + self._repeatmode('repeat') + else: + self._repeatmode(None) + elif option == 'toggle_repeat_single': + if self.mpd_status['single'] == '0': + self._repeatmode('single') + else: + self._repeatmode(None) + elif option == 'enable_repeat': + self._repeatmode('repeat') + elif option == 'enable_repeat_single': + self._repeatmode('single') + elif option == 'disable': + self._repeatmode(None) + else: + logger.error(f"'{option}' does not exist for 'repeat'") + @plugs.tag def get_current_song(self, param): return self.mpd_status diff --git a/src/jukebox/components/rpc_command_alias.py b/src/jukebox/components/rpc_command_alias.py index bb484891a..e56727ff4 100644 --- a/src/jukebox/components/rpc_command_alias.py +++ b/src/jukebox/components/rpc_command_alias.py @@ -36,6 +36,12 @@ 'package': 'player', 'plugin': 'ctrl', 'method': 'play_folder'}, + 'play': { + 'package': 'player', + 'plugin': 'ctrl', + 'method': 'play', + 'note': 'Play the currently selected song', + 'ignore_card_removal_action': True}, 'pause': { 'package': 'player', 'plugin': 'ctrl', @@ -57,6 +63,18 @@ 'plugin': 'ctrl', 'method': 'toggle', 'ignore_card_removal_action': True}, + 'shuffle': { + 'package': 'player', + 'plugin': 'ctrl', + 'method': 'shuffle', + 'note': 'Shuffle', + 'ignore_card_removal_action': True}, + 'repeat': { + 'package': 'player', + 'plugin': 'ctrl', + 'method': 'repeat', + 'note': 'Repeat', + 'ignore_card_removal_action': True}, # VOLUME 'set_volume': { diff --git a/src/webapp/public/locales/de/translation.json b/src/webapp/public/locales/de/translation.json index 85d39b879..f3bd89782 100644 --- a/src/webapp/public/locales/de/translation.json +++ b/src/webapp/public/locales/de/translation.json @@ -23,7 +23,14 @@ "timer_fade_volume": "Fade volume", "toggle_output": "Audio-Ausgang umschalten", "sync_rfidcards_all": "Alle Audiodateien und Karteneinträge synchronisieren", - "sync_rfidcards_change_on_rfid_scan": "Aktivierung ändern für 'on RFID scan' " + "sync_rfidcards_change_on_rfid_scan": "Aktivierung ändern für 'on RFID scan'", + "next_song": "Nächster Song", + "pause": "Pause", + "play": "Abspielen", + "prev_song": "Vorheriger Song", + "shuffle": "Zufallswiedergabe", + "repeat": "Wiedergabe wiederholen", + "toggle": "Abspielen/Pause umschalten" } }, "controls-selector": { @@ -56,8 +63,25 @@ "no-music-selected": "Es ist keine Musik ausgewählt.", "loading-song-error": "Während des Ladens des Songs ist ein Fehler aufgetreten." }, - "volume": { - "title": "Lautstärke Stufen" + "audio": { + "repeat": { + "description": "Wähle den zu setzenden Status.", + "label-toggle": "Umschalten - Schaltet durch 1) Wiedergabeliste Wiederholen, 2) Song Wiederholen, 3) Wiederholen Deaktiveren", + "label-toggle-repeat": "Wiedergabeliste Wiederholen umschalten", + "label-toggle-repeat-single": "Song Wiederholen umschalten", + "label-enable-repeat": "Wiedergabeliste Wiederholen aktivieren", + "label-enable-repeat-single": "Song Wiederholen aktivieren", + "label-disable": "Wiederholen deaktivieren" + }, + "shuffle": { + "description": "Wähle den zu setzenden Status.", + "label-toggle": "Umschalten", + "label-enable": "Aktivieren", + "label-disable": "Deaktivieren" + }, + "volume": { + "title": "Lautstärke Stufen" + } }, "timers": { "description": "Wähle die Anzahl der Minuten nachdem die Aktion ausgeführt werden soll." @@ -138,17 +162,17 @@ "player": { "controls": { "shuffle": { - "activate": "Shuffle aktivieren", - "deactivate": "Shuffle deaktivieren" + "enable": "Zufallswiedergabe aktivieren", + "disable": "Zufallswiedergabe deaktivieren" }, - "skip": "Zurück", + "prev_song": "Zurück", "play": "Abspielen", "pause": "Pause", - "next": "Weiter", + "next_song": "Weiter", "repeat": { - "activate": "Wiederholen aktivieren", - "activate-single": "1 Wiederholen aktivieren", - "deactivate": "Wiederholen deaktivieren" + "enable": "Wiedergabeliste Wiederholen aktivieren", + "enable-single": "Song Wiederholen aktivieren", + "disable": "Wiederholen deaktivieren" } }, "cover": { diff --git a/src/webapp/public/locales/en/translation.json b/src/webapp/public/locales/en/translation.json index d03941eb6..348d3771d 100644 --- a/src/webapp/public/locales/en/translation.json +++ b/src/webapp/public/locales/en/translation.json @@ -23,7 +23,14 @@ "timer_fade_volume": "Fade volume", "toggle_output": "Toggle audio output", "sync_rfidcards_all": "Sync all audiofiles and card entries", - "sync_rfidcards_change_on_rfid_scan": "Change activation of 'on RFID scan'" + "sync_rfidcards_change_on_rfid_scan": "Change activation of 'on RFID scan'", + "next_song": "Next song", + "pause": "Pause", + "play": "Play", + "prev_song": "Previous song", + "shuffle": "Shuffle", + "repeat": "Repeat", + "toggle": "Toggle Play/Pause" } }, "controls-selector": { @@ -56,8 +63,25 @@ "no-music-selected": "No music selected", "loading-song-error": "An error occurred while loading song." }, - "volume": { - "title": "Volume steps" + "audio": { + "repeat": { + "description": "Choose the state to set.", + "label-toggle": "Toggle - Loops through 1) Repeat playlist, 2) Repeat song, 3) Disable repeat", + "label-toggle-repeat": "Toggle Repeat Playlist", + "label-toggle-repeat-single": "Toggle Repeat Song", + "label-enable-repeat": "Enable Repeat Playlist", + "label-enable-repeat-single": "Enable Repeat Song", + "label-disable": "Disable" + }, + "shuffle": { + "description": "Choose the state to set.", + "label-toggle": "Toggle", + "label-enable": "Enable", + "label-disable": "Disable" + }, + "volume": { + "title": "Volume steps" + } }, "timers": { "description": "Choose the amount of minutes you want the action to be performed." @@ -138,17 +162,17 @@ "player": { "controls": { "shuffle": { - "activate": "Activate shuffle", - "deactivate": "Deactivate shuffle" + "enable": "Enable shuffle", + "disable": "Disable shuffle" }, - "skip": "Skip previous track", + "prev_song": "Skip to previous track", "play": "Play", "pause": "Pause", - "next": "Skip next track", + "next_song": "Skip to next track", "repeat": { - "activate": "Activate repeat", - "activate-single": "Activate single repeat", - "deactivate": "Deactivate repeat" + "enable": "Enable Playlist repeat", + "enable-single": "Enable Song repeat", + "disable": "Disable repeat" } }, "cover": { @@ -207,6 +231,7 @@ }, "secondswipe": { "title": "Second Swipe", + "description": "Second action after the same card swiped again", "restart": "Restart playlist", "toggle": "Toggle pause / play", "skip": "Skip to next track", diff --git a/src/webapp/src/commands/index.js b/src/webapp/src/commands/index.js index 2352e46b7..8c844d8da 100644 --- a/src/webapp/src/commands/index.js +++ b/src/webapp/src/commands/index.js @@ -82,25 +82,32 @@ const commands = { plugin: 'ctrl', method: 'pause', }, - previous: { + prev_song: { _package: 'player', plugin: 'ctrl', method: 'prev', }, - next: { + next_song: { _package: 'player', plugin: 'ctrl', method: 'next', }, + toggle: { + _package: 'player', + plugin: 'ctrl', + method: 'toggle', + }, shuffle: { _package: 'player', plugin: 'ctrl', method: 'shuffle', + argKeys: ['option'], }, repeat: { _package: 'player', plugin: 'ctrl', - method: 'repeatmode', + method: 'repeat', + argKeys: ['option'], }, seek: { _package: 'player', diff --git a/src/webapp/src/components/Cards/controls/actions/audio/index.js b/src/webapp/src/components/Cards/controls/actions/audio/index.js index 1d3ddc465..aba96cdc2 100644 --- a/src/webapp/src/components/Cards/controls/actions/audio/index.js +++ b/src/webapp/src/components/Cards/controls/actions/audio/index.js @@ -2,10 +2,11 @@ import React from 'react'; import CommandSelector from '../../command-selector'; import SliderChangeVolume from './slider-change-volume'; +import OptionsSelector from '../../options-selector'; import { getActionAndCommand } from '../../../utils'; -const SelectVolume = ({ +const SelectAudioVolume = ({ actionData, handleActionDataChange, }) => { @@ -23,8 +24,37 @@ const SelectVolume = ({ handleActionDataChange={handleActionDataChange} /> } + {command === 'shuffle' && + + } + {command === 'repeat' && + + } ); }; -export default SelectVolume; \ No newline at end of file +export default SelectAudioVolume; \ No newline at end of file diff --git a/src/webapp/src/components/Cards/controls/actions/audio/slider-change-volume.js b/src/webapp/src/components/Cards/controls/actions/audio/slider-change-volume.js index 2e70b489a..64f5edada 100644 --- a/src/webapp/src/components/Cards/controls/actions/audio/slider-change-volume.js +++ b/src/webapp/src/components/Cards/controls/actions/audio/slider-change-volume.js @@ -37,12 +37,12 @@ const SliderChangeVolume = ({ - {t('cards.controls.actions.volume.title')} + {t('cards.controls.actions.audio.volume.title')} {command === 'sync_rfidcards_change_on_rfid_scan' && - } diff --git a/src/webapp/src/components/Cards/controls/actions/synchronisation/rfidcards/change-on-rfid-scan-options.js b/src/webapp/src/components/Cards/controls/actions/synchronisation/rfidcards/change-on-rfid-scan-options.js deleted file mode 100644 index 6d2b2fde6..000000000 --- a/src/webapp/src/components/Cards/controls/actions/synchronisation/rfidcards/change-on-rfid-scan-options.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -import { - Grid, - FormControl, - FormControlLabel, - Radio, - RadioGroup, - Typography, -} from '@mui/material'; - -import { - getActionAndCommand, - getArgsValues, -} from '../../../../utils'; - - -const ChangeOnRfidScan = ({ - actionData, - handleActionDataChange, -}) => { - const { t } = useTranslation(); - - const { action, command } = getActionAndCommand(actionData); - const [option] = getArgsValues(actionData); - - const onChange = (event, option) => { - handleActionDataChange(action, command, { option }) - }; - - return ( - - - - {t('cards.controls.actions.synchronisation.rfidcards.description')} - - - - } - label={t('cards.controls.actions.synchronisation.rfidcards.label-toggle')} - value="toggle" - /> - } - label={t('cards.controls.actions.synchronisation.rfidcards.label-enable')} - value="enable" - /> - } - label={t('cards.controls.actions.synchronisation.rfidcards.label-disable')} - value="disable" - /> - - - - - ); -}; - -export default ChangeOnRfidScan; diff --git a/src/webapp/src/components/Cards/controls/options-selector.js b/src/webapp/src/components/Cards/controls/options-selector.js new file mode 100644 index 000000000..7a2fd1b22 --- /dev/null +++ b/src/webapp/src/components/Cards/controls/options-selector.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { + Grid, + FormControl, + FormControlLabel, + Radio, + RadioGroup, + Typography, +} from '@mui/material'; + +import { + getActionAndCommand, + getArgsValues, +} from '../utils.js'; + +const OptionsSelector = ({ + actionType, + actionData, + handleActionDataChange, + optionLabel, + options, +}) => { + const { t } = useTranslation(); + const { action, command } = getActionAndCommand(actionData); + const [option] = getArgsValues(actionData); + + const onChange = (event, option) => { + handleActionDataChange(action, command, { option }) + }; + + return ( + + + + {t(optionLabel)} + + + + {options.map(({ labelKey, value }) => ( + } + label={t(labelKey)} + value={value} + /> + ))} + + + + + ); +}; + +export default OptionsSelector; diff --git a/src/webapp/src/components/Player/controls.js b/src/webapp/src/components/Player/controls.js index dc433cef8..48a587a32 100644 --- a/src/webapp/src/components/Player/controls.js +++ b/src/webapp/src/components/Player/controls.js @@ -32,15 +32,11 @@ const Controls = () => { } = state; const toggleShuffle = () => { - request('shuffle', { random: !isShuffle }); + request('shuffle', { option: 'toggle' }); } const toggleRepeat = () => { - let mode = null; - if (!isRepeat && !isSingle) mode = 'repeat'; - if (isRepeat && !isSingle) mode = 'single'; - - request('repeat', { mode }); + request('repeat', { option: 'toggle' }); } useEffect(() => { @@ -58,14 +54,14 @@ const Controls = () => { const labelShuffle = () => ( isShuffle - ? t('player.controls.shuffle.deactivate') - : t('player.controls.shuffle.activate') + ? t('player.controls.shuffle.disable') + : t('player.controls.shuffle.enable') ); const labelRepeat = () => { - if (!isRepeat) return t('player.controls.repeat.activate'); - if (isRepeat && !isSingle) return t('player.controls.repeat.activate-single'); - if (isRepeat && isSingle) return t('player.controls.repeat.deactivate'); + if (!isRepeat) return t('player.controls.repeat.enable'); + if (isRepeat && !isSingle) return t('player.controls.repeat.enable-single'); + if (isRepeat && isSingle) return t('player.controls.repeat.disable'); }; return ( @@ -89,14 +85,14 @@ const Controls = () => { - {/* Skip previous track */} + {/* Skip to previous song */} request('previous')} + onClick={e => request('prev_song')} size="large" sx={iconStyles} - title={t('player.controls.skip')} + title={t('player.controls.prev_song')} > @@ -127,14 +123,14 @@ const Controls = () => { } - {/* Skip next track */} + {/* Skip to next song */} request('next')} + onClick={e => request('next_song')} size="large" sx={iconStyles} - title={t('player.controls.next')} + title={t('player.controls.next_song')} > diff --git a/src/webapp/src/components/Player/cover.js b/src/webapp/src/components/Player/cover.js index d20133acb..a744b7b5f 100644 --- a/src/webapp/src/components/Player/cover.js +++ b/src/webapp/src/components/Player/cover.js @@ -33,7 +33,11 @@ const Cover = ({ coverImage }) => { {t('player.cover.title')}} {!coverImage && { if (result) { setCoverImage(`/cover-cache/${result}`); setBackgroundImage([ - 'linear-gradient(to bottom, rgba(18, 18, 18, 0.7), rgba(18, 18, 18, 1))', + 'linear-gradient(to bottom, rgba(18, 18, 18, 0.5), rgba(18, 18, 18, 1))', `url(/cover-cache/${result})` ].join(',')); }; diff --git a/src/webapp/src/components/Player/seekbar.js b/src/webapp/src/components/Player/seekbar.js index aab3682ee..b138bf827 100644 --- a/src/webapp/src/components/Player/seekbar.js +++ b/src/webapp/src/components/Player/seekbar.js @@ -69,7 +69,7 @@ const SeekBar = () => { direction="row" justifyContent="space-between" sx={ { - marginTop: '-20px', + marginTop: '-10px', }} > diff --git a/src/webapp/src/config.js b/src/webapp/src/config.js index 35b5a980e..482db205e 100644 --- a/src/webapp/src/config.js +++ b/src/webapp/src/config.js @@ -42,7 +42,14 @@ const JUKEBOX_ACTIONS_MAP = { audio: { commands: { change_volume: {}, - toggle_output: {} + toggle_output: {}, + play: {}, + pause: {}, + toggle: {}, + next_song: {}, + prev_song: {}, + shuffle: {}, + repeat: {}, }, }, diff --git a/src/webapp/src/i18n.js b/src/webapp/src/i18n.js index b2a752fdd..d374abae3 100644 --- a/src/webapp/src/i18n.js +++ b/src/webapp/src/i18n.js @@ -18,6 +18,7 @@ i18n // for all options read: https://www.i18next.com/overview/configuration-options .init({ debug: true, + // lng: 'en', fallbackLng: 'en', interpolation: { escapeValue: false, // not needed for react as it escapes by default From 6e9e4f7ae1db3a83ba9e147e320f1aaa6499de70 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Mon, 15 Jan 2024 07:35:36 +0100 Subject: [PATCH 069/121] Add swap file adjustment for webapp build (#2204) * adjust swapfile if memory is too low * add option for webapp build * update ci tests * Update run_install_webapp_local.sh * update ENABLE_WEBAPP_BUILD handling * updated logging * add build webapp test workflow * fix path * fix actions * fix: add shell * fix trigger path * adjust node mem calculation leave enough memory for the system. increase swap with min size * use shell script for builds * add env CI * use new action for release build * adjusted min sizes * remove obsolete code * update workflow name * move update dependencies entirely to rebuild script. update docs * move existing build folder to backup. update docs * increase swap with lower step size. minor fixes * refactor vars and logging. added verbose param. * update logging * fix indentation * update logging. moved NODEMEM check * refactor webapp build during installation removed unneccessay vars (only use ENABLE_WEBAPP_PROD_DOWNLOAD). trimmed webapp build option. updated messages. harmonized var access and queries * Update docs * Update messages * harmonize webapp wording * fix flake8 * fix flake8 * update node installation. update node version for armv6. use recommended setup from nodesource. removed update version on new installation run. * update docs * ci add platform linux/armv6 (deactivated) * reverted to https://deb.nodesource.com/ installation * update docs * align fin message * Update docs. Remove full URL references to branch * ignore stderr if node is not installed before * ignore stderr if node is not installed before * bugfix. removed additional char * add npm project config better network handling especially for armv6l devices * use performance optimized install command update warning for armv6l devices * Update docs Update installation steps. split for version. add tip from installation- Add webapp doc for developers. update docs and link to new webapp doc. eliminate duplications. merged developer-issues into new webapp doc * also raise fetch-retry-mintimeout * update docs harmonize wording for webapp. add build output. change ARMv6 installation note. * add note for webserver restart * update installation docs. add bullet list * change npm config values * show "slow hardware message" at installation start * update armv6l warning for webapp rebuild * make script return value more clear * Make local webapp build an optional step due to unpredictable npm connection errors. Show webapp fin message only if webapp build fails * use prebuilt webapp bundle also for develop * update docs * Update webapp.md * Update customize_options.sh (typos) * Update installation.md * Update system.md * Update installation.md * Update webapp.md * Update customize_options.sh * change npm config values * update docs * update welcome to match changes in wiki * update rebuild message * Add doc links * update ignorefiles for build.bak * Update doc and link for constributors * Update CONTRIBUTING.md --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --- .dockerignore | 1 + .githooks/post-merge | 6 +- .github/actions/build-webapp/action.yml | 25 +++ .../bundle_webapp_and_release_v3.yml | 28 +-- .github/workflows/test_build_webapp_v3.yml | 33 ++++ .../test_docker_debian_codename_sub_v3.yml | 9 +- .github/workflows/test_docker_debian_v3.yml | 16 ++ CONTRIBUTING.md | 37 ++-- ci/installation/run_install_common.sh | 2 +- ci/installation/run_install_faststartup.sh | 2 +- ci/installation/run_install_libzmq_local.sh | 2 +- .../run_install_webapp_download.sh | 4 +- ci/installation/run_install_webapp_local.sh | 4 +- documentation/builders/autohotspot.md | 8 +- documentation/builders/concepts.md | 2 +- documentation/builders/installation.md | 38 +++- documentation/builders/system.md | 6 +- documentation/builders/update.md | 2 +- documentation/developers/README.md | 1 + documentation/developers/developer-issues.md | 86 -------- .../developers/development-environment.md | 32 +-- documentation/developers/docker.md | 4 +- documentation/developers/status.md | 2 +- documentation/developers/webapp.md | 149 ++++++++++++++ installation/includes/00_constants.sh | 3 + installation/includes/01_default_config.sh | 4 +- installation/includes/02_helpers.sh | 11 ++ installation/includes/03_welcome.sh | 14 +- installation/includes/05_finish.sh | 2 +- installation/routines/customize_options.sh | 46 ++--- installation/routines/install.sh | 1 + installation/routines/setup_jukebox_core.sh | 13 -- installation/routines/setup_jukebox_webapp.sh | 143 +++++++++----- resources/default-settings/gpio.example.yaml | 2 +- resources/html/404.html | 2 +- resources/html/runbuildui.html | 8 +- src/jukebox/components/playermpd/__init__.py | 9 +- src/webapp/.gitignore | 3 + src/webapp/.npmrc | 3 + src/webapp/public/index.html | 2 +- src/webapp/run_rebuild.sh | 187 ++++++++++++------ 41 files changed, 585 insertions(+), 367 deletions(-) create mode 100644 .github/actions/build-webapp/action.yml create mode 100644 .github/workflows/test_build_webapp_v3.yml delete mode 100644 documentation/developers/developer-issues.md create mode 100644 documentation/developers/webapp.md create mode 100644 src/webapp/.npmrc diff --git a/.dockerignore b/.dockerignore index a587de477..da30b5a97 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,3 +14,4 @@ shared src/webapp/node_modules src/webapp/npm-debug.log src/webapp/build +src/webapp/build.bak diff --git a/.githooks/post-merge b/.githooks/post-merge index 3b838c87c..6fc6e4f54 100755 --- a/.githooks/post-merge +++ b/.githooks/post-merge @@ -5,7 +5,7 @@ # TO ACTIVATE: cp .githooks/post-merge .git/hooks/. # # Checks: -# - Changes to web app +# - Changes to Web App # - Changes to web dependency # - Changes to python requirements # @@ -20,7 +20,7 @@ warn_npm_dependency() { echo "************************************************************" echo "ATTENTION: npm dependencies have changed since last pull!" echo "" - echo "To update dependencies and rebuilt WebApp run:" + echo "To update dependencies and rebuilt Web App run:" echo "$ cd src/webapp && ./run_rebuild.sh -u" echo "************************************************************" echo -e "\n" @@ -31,7 +31,7 @@ warn_webapp() { echo "************************************************************" echo "ATTENTION: Web App sources have changed since last pull!" echo "" - echo "To rebuilt the WebApp run:" + echo "To rebuilt the Web App run:" echo "$ cd src/webapp && ./run_rebuild.sh" echo "************************************************************" echo -e "\n" diff --git a/.github/actions/build-webapp/action.yml b/.github/actions/build-webapp/action.yml new file mode 100644 index 000000000..9f192105f --- /dev/null +++ b/.github/actions/build-webapp/action.yml @@ -0,0 +1,25 @@ +name: Build Web App +description: 'Build Web App with Node' +inputs: + webapp-root-path: + description: 'root path of the Web App sources' + required: false + default: './src/webapp' +outputs: + webapp-root-path: + description: 'used root path of the Web App sources' + value: ${{ inputs.webapp-root-path }} + +runs: + using: "composite" + steps: + - name: Setup Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + - name: run build + working-directory: ${{ inputs.webapp-root-path }} + shell: bash + env: + CI: false + run: ./run_rebuild.sh -u \ No newline at end of file diff --git a/.github/workflows/bundle_webapp_and_release_v3.yml b/.github/workflows/bundle_webapp_and_release_v3.yml index 548f2b47a..13bffe472 100644 --- a/.github/workflows/bundle_webapp_and_release_v3.yml +++ b/.github/workflows/bundle_webapp_and_release_v3.yml @@ -1,4 +1,4 @@ -name: Bundle Webapp and Release +name: Bundle Web App and Release on: push: @@ -18,7 +18,7 @@ jobs: check_abort: ${{ steps.vars.outputs.check_abort }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set Output vars id: vars @@ -72,9 +72,6 @@ jobs: if: ${{ needs.check.outputs.check_abort == 'false' }} runs-on: ubuntu-latest - env: - WEBAPP_ROOT_PATH: ./src/webapp - outputs: tag_name: ${{ needs.check.outputs.tag_name }} release_type: ${{ needs.check.outputs.release_type }} @@ -83,7 +80,7 @@ jobs: webapp_bundle_name_latest: ${{ steps.vars.outputs.webapp_bundle_name_latest }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set Output vars id: vars @@ -94,21 +91,12 @@ jobs: echo "webapp_bundle_name=webapp-build-${COMMIT_SHA:0:10}.tar.gz" >> $GITHUB_OUTPUT echo "webapp_bundle_name_latest=webapp-build-latest.tar.gz" >> $GITHUB_OUTPUT - - name: Setup Node.js 20.x - uses: actions/setup-node@v3 - with: - node-version: 20.x - - name: npm install - working-directory: ${{ env.WEBAPP_ROOT_PATH }} - run: npm install - - name: npm build - working-directory: ${{ env.WEBAPP_ROOT_PATH }} - env: - CI: false - run: npm run build + - name: Build Web App + id: build-webapp + uses: ./.github/actions/build-webapp - name: Create Bundle - working-directory: ${{ env.WEBAPP_ROOT_PATH }} + working-directory: ${{ steps.build-webapp.outputs.webapp-root-path }} run: | tar -czvf ${{ steps.vars.outputs.webapp_bundle_name }} build @@ -116,7 +104,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: ${{ steps.vars.outputs.webapp_bundle_name }} - path: ${{ env.WEBAPP_ROOT_PATH }}/${{ steps.vars.outputs.webapp_bundle_name }} + path: ${{ steps.build-webapp.outputs.webapp-root-path }}/${{ steps.vars.outputs.webapp_bundle_name }} retention-days: 5 release: diff --git a/.github/workflows/test_build_webapp_v3.yml b/.github/workflows/test_build_webapp_v3.yml new file mode 100644 index 000000000..426d01f04 --- /dev/null +++ b/.github/workflows/test_build_webapp_v3.yml @@ -0,0 +1,33 @@ +name: Test Build Web App v3 + +on: + schedule: + # run at 18:00 every sunday + - cron: '0 18 * * 0' + push: + branches: + - 'future3/**' + paths: + - '.github/workflows/test_build_webapp_v3.yml' + - '.github/actions/build-webapp/**' + - 'src/webapp/**' + pull_request: + # The branches below must be a subset of the branches above + branches: + - future3/develop + - future3/main + paths: + - '.github/workflows/test_build_webapp_v3.yml' + - '.github/actions/build-webapp/**' + - 'src/webapp/**' + +jobs: + + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build Web App + uses: ./.github/actions/build-webapp \ No newline at end of file diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml index dde60cb2a..a9ec217dc 100644 --- a/.github/workflows/test_docker_debian_codename_sub_v3.yml +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -1,4 +1,4 @@ -name: Subworkflow Test Install Scripts Debian V3 +name: Subworkflow Test Install Scripts Debian v3 on: workflow_call: @@ -46,6 +46,7 @@ jobs: cache_key: ${{ steps.vars.outputs.cache_key }} image_file_name: ${{ steps.vars.outputs.image_file_name }} image_tag_name: ${{ steps.vars.outputs.image_tag_name }} + docker_run_options: ${{ steps.vars.outputs.docker_run_options }} # create local docker registry to use locally build images services: @@ -83,6 +84,7 @@ jobs: id: vars env: LOCAL_REGISTRY_PORT: ${{ inputs.local_registry_port }} + PLATFORM: ${{ inputs.platform }} run: | echo "image_tag_name=${{ steps.pre-vars.outputs.image_tag_name }}" >> $GITHUB_OUTPUT echo "image_tag_name_local_base=localhost:${{ env.LOCAL_REGISTRY_PORT }}/${{ steps.pre-vars.outputs.image_tag_name }}-base" >> $GITHUB_OUTPUT @@ -90,6 +92,9 @@ jobs: echo "image_file_path=./${{ steps.pre-vars.outputs.image_file_name }}" >> $GITHUB_OUTPUT echo "cache_scope=${{ steps.pre-vars.outputs.cache_scope }}" >> $GITHUB_OUTPUT echo "cache_key=${{ steps.pre-vars.outputs.cache_scope }}-${{ github.sha }}#${{ github.run_attempt }}" >> $GITHUB_OUTPUT + if [ "${{ env.PLATFORM }}" == "linux/arm/v6" ] ; then + echo "docker_run_options=-e QEMU_CPU=arm1176" >> $GITHUB_OUTPUT + fi # Build base image for debian version name. Layers will be cached and image pushes to local registry - name: Build Image - Base @@ -167,7 +172,7 @@ jobs: uses: tj-actions/docker-run@v2 with: image: ${{ needs.build.outputs.image_tag_name }} - options: --platform ${{ inputs.platform }} --user ${{ env.TEST_USER_NAME }} --init + options: ${{ needs.build.outputs.docker_run_options }} --platform ${{ inputs.platform }} --user ${{ env.TEST_USER_NAME }} --init name: ${{ matrix.test_script }} args: | ./${{ matrix.test_script }} diff --git a/.github/workflows/test_docker_debian_v3.yml b/.github/workflows/test_docker_debian_v3.yml index 06718afba..673745db9 100644 --- a/.github/workflows/test_docker_debian_v3.yml +++ b/.github/workflows/test_docker_debian_v3.yml @@ -45,9 +45,25 @@ jobs: debian_codename: 'bookworm' platform: linux/arm/v7 + # # can be activate on test branches, currently failing + # run_bookworm_armv6: + # name: 'bookworm armv6' + # uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml + # with: + # debian_codename: 'bookworm' + # platform: linux/arm/v6 + run_bullseye_armv7: name: 'bullseye armv7' uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml with: debian_codename: 'bullseye' platform: linux/arm/v7 + + # # can be activate on test branches, currently failing + # run_bullseye_armv6: + # name: 'bullseye armv6' + # uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml + # with: + # debian_codename: 'bullseye' + # platform: linux/arm/v6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbf12f84d..f4ac9cd16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,12 +45,17 @@ as local, temporary scratch areas. Contributors have played a bigger role over time to keep Phoniebox on the edge of innovation :) -We want to keep it as easy as possible to contribute changes that get things working in your environment. -There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. +Our goal is to make it simple for you to contribute changes that improve functionality in your specific environment. +To achieve this, we have a set of guidelines that we kindly request contributors to adhere to. +These guidelines help us maintain a streamlined process and stay on top of incoming contributions. -Development for Version 3 is done on the git branch `future3/develop`. How to move to that branch, see below. +To report bug fixes and improvements, please follow the steps outlined below: +1. For bug fixes and minor improvements, simply open a new issue or pull request (PR). +2. If you intend to port a feature from Version 2.x to future3 or wish to implement a new feature, we recommend reaching out to us beforehand. + - In such cases, please create an issue outlining your plans and intentions. + - We will ensure that there are no ongoing efforts on the same topic. -For bug fixes and improvements just open an issue or PR as described below. If you plan to port a feature from Version 2.X or implement a new feature, it is advisable to contact us first. In this case, also open an issue describing what you are planning to do. We will just check that nobody else is already on the subject. We are looking forward to your work. Check the current [feature list](https://rpi-jukebox-rfid.readthedocs.io/en/latest/featurelist.html) for available features and work in progress. +We eagerly await your contributions! You can review the current [feature list](documentation/developers/status.md) to check for available features and ongoing work. ## Getting Started @@ -60,31 +65,21 @@ For bug fixes and improvements just open an issue or PR as described below. If y Version 2 will continue to live for quite a while. * Clearly describe the issue including steps to reproduce when it is a bug * Make sure you fill in the earliest version that you know has the issue -* By default this will get you to the `future3/main` branch. You will move to the `future3/develop` branch, do this: - -~~~bash -cd ~/RPi-Jukebox-RFID -git checkout future3/develop -git fetch origin -git reset --hard origin/future3/develop -git pull -~~~ The preferred way of code contributions are [pull requests (follow this link for a small howto)](https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github). -And, ideally pull requests use the "running code" on the `future3/develop` branch of your Phoniebox. +And ideally pull requests use the "running code" of your Phoniebox. Alternatively, feel free to post tweaks, suggestions and snippets in the ["issues" section](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues). ## Making Changes +* Create a fork of this repository * Create a topic branch from where you want to base your work. - * This is usually the master branch or the develop branch. - * Only target release branches if you are certain your fix must be on that + * This is usually the `future3/develop` branch. + * Only target the `future3/main` branch if you are certain your fix must be on that branch. - * To quickly create a topic branch based on master, run `git checkout -b - fix/master/my_contribution master`. Please avoid working directly on the - `master` branch. * Make commits of logical and atomic units. * Check for unnecessary whitespace with `git diff --check` before committing. +* See also the [documentation for developers](documentation/developers/README.md) ## Making Trivial Changes @@ -168,8 +163,8 @@ The original contributor will be notified of the revert. ## Guidelines -* Phoniebox runs on Raspian **Buster**. Therefore, all Python code should work at least with **Python 3.7**. -* For GPIO all code should work with **RPi.GPIO**. gpiozero is currently not intended to use. +* Phoniebox runs on Raspberry Pi OS. +* Minimum python version is currently **Python 3.9**. ## Additional Resources diff --git a/ci/installation/run_install_common.sh b/ci/installation/run_install_common.sh index 3d4c59778..b8c641580 100644 --- a/ci/installation/run_install_common.sh +++ b/ci/installation/run_install_common.sh @@ -24,7 +24,7 @@ export ENABLE_WEBAPP_PROD_DOWNLOAD=true # n - setup rfid reader # y - setup samba # y - setup webapp -# - - install node (forced WebApp Download) +# - - build webapp (skipped due to forced webapp Download) # n - setup kiosk mode # n - reboot diff --git a/ci/installation/run_install_faststartup.sh b/ci/installation/run_install_faststartup.sh index 249d78ffc..134aeca71 100644 --- a/ci/installation/run_install_faststartup.sh +++ b/ci/installation/run_install_faststartup.sh @@ -22,7 +22,7 @@ LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" # n - setup rfid reader # n - setup samba # n - setup webapp -# - - install node (only with webapp = y) +# - - build webapp (only with webapp = y) # - - setup kiosk mode (only with webapp = y) # n - reboot diff --git a/ci/installation/run_install_libzmq_local.sh b/ci/installation/run_install_libzmq_local.sh index aa3726b2f..335cb24a1 100644 --- a/ci/installation/run_install_libzmq_local.sh +++ b/ci/installation/run_install_libzmq_local.sh @@ -23,7 +23,7 @@ export BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=true # n - setup rfid reader # n - setup samba # n - setup webapp -# - - install node (only with webapp = y) +# - - build webapp (only with webapp = y) # - - setup kiosk mode (only with webapp = y) # n - reboot diff --git a/ci/installation/run_install_webapp_download.sh b/ci/installation/run_install_webapp_download.sh index 698e057b9..ded27ec54 100644 --- a/ci/installation/run_install_webapp_download.sh +++ b/ci/installation/run_install_webapp_download.sh @@ -4,7 +4,7 @@ # Used e.g. for tests on Docker # Objective: -# Test for the WebApp (download) and dependent features path. +# Test for the Web App (download) and dependent features path. SOURCE="${BASH_SOURCE[0]}" SCRIPT_DIR="$(dirname "$SOURCE")" @@ -22,7 +22,7 @@ LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" # n - setup rfid reader # n - setup samba # y - setup webapp -# n - install node +# n - build webapp # y - setup kiosk mode # n - reboot diff --git a/ci/installation/run_install_webapp_local.sh b/ci/installation/run_install_webapp_local.sh index 917f985af..7af16df3a 100644 --- a/ci/installation/run_install_webapp_local.sh +++ b/ci/installation/run_install_webapp_local.sh @@ -4,7 +4,7 @@ # Used e.g. for tests on Docker # Objective: -# Test for the WebApp (build locally) and dependent features path. +# Test for the Web App (build locally) and dependent features path. SOURCE="${BASH_SOURCE[0]}" SCRIPT_DIR="$(dirname "$SOURCE")" @@ -23,7 +23,7 @@ export ENABLE_WEBAPP_PROD_DOWNLOAD=false # n - setup rfid reader # n - setup samba # y - setup webapp -# y - install node +# y - build webapp # y - setup kiosk mode # n - reboot diff --git a/documentation/builders/autohotspot.md b/documentation/builders/autohotspot.md index 69e6f4d6a..ecf996f81 100644 --- a/documentation/builders/autohotspot.md +++ b/documentation/builders/autohotspot.md @@ -2,7 +2,7 @@ The Auto-Hotspot function allows the Jukebox to switch between its connection between a known WiFi and an automatically generated hotspot -so that you can still access via SSH or Webapp. +so that you can still access via SSH or Web App. > [!IMPORTANT] > Please configure the WiFi connection to your home access point before enabling these feature! @@ -17,10 +17,10 @@ hotspot named `Phoniebox_Hotspot`. You will be able to connect to this hotspot using the given password in the installation or the default password: `PlayItLoud!` -### Webapp +### Web App After connecting to the `Phoniebox_Hotspot` you are able to connect to -the webapp accessing the website [10.0.0.5](http://10.0.0.5/). +the Web App accessing the website [10.0.0.5](http://10.0.0.5/). ### ssh @@ -69,7 +69,7 @@ ieee80211d=1 ## Disabling automatism -Auto-Hotspot can be enabled or disabled using the Webapp. +Auto-Hotspot can be enabled or disabled using the Web App. > [!IMPORTANT] > Disabling or enabling will keep the last state. diff --git a/documentation/builders/concepts.md b/documentation/builders/concepts.md index 37c13db89..0a4305cfe 100644 --- a/documentation/builders/concepts.md +++ b/documentation/builders/concepts.md @@ -12,7 +12,7 @@ The core app is centered around a plugin concept. This serves three purposes: ## Remote Procedure Call Server (RPC) -The Remote Procedure Call (RPC) server allows remotely triggering actions (e.g., from the Webapp) within the Jukebox core application. Only Python functions registered by the plugin interface can be called. This simplifies external APIs and lets us focus on the relevant user functions. +The Remote Procedure Call (RPC) server allows remotely triggering actions (e.g., from the Web App) within the Jukebox core application. Only Python functions registered by the plugin interface can be called. This simplifies external APIs and lets us focus on the relevant user functions. Why should you care? Because we use the same protocol when triggering actions from other inputs like a card swipe, a GPIO button press, etc. How that works is described in [RPC Commands](rpc-commands.md). diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index 945368480..7ce0e59c0 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -3,7 +3,7 @@ ## Install Raspberry Pi OS Lite > [!IMPORTANT] -> Currently, the installation does only work on Raspberry Pi's with ARMv7 and ARMv8 architecture, so 2, 3 and 4! Pi 1 and Zero's are currently unstable and will require a bit more work! Pi 4 and 5 are an excess ;-) +> All Raspberry Pi models are supported. For sufficient performance, **we recommend Pi 2, 3 or Zero 2** (`ARMv7` models). Because Pi 1 or Zero 1 (`ARMv6` models) have limited resources, they are slower (during installation and start up procedure) and might require a bit more work! Pi 4 and 5 are an excess ;-) Before you can install the Phoniebox software, you need to prepare your Raspberry Pi. @@ -79,30 +79,48 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to ## Install Phoniebox software -Run the following command in your SSH terminal and follow the instructions +Choose a version, run the corresponding install command in your SSH terminal and follow the instructions. +* [Stable Release](#stable-release) +* [Pre-Release](#pre-release) +* [Development](#development) + +After a successful installation, [configure your Phoniebox](configuration.md). + +> [!TIP] +> Depending on your hardware, this installation might last around 60 minutes (usually it's faster, 20-30 min). It updates OS packages, installs Phoniebox dependencies and applies settings. Be patient and don't let your computer go to sleep. It might disconnect your SSH connection causing the interruption of the installation process. Consider starting the installation in a terminal multiplexer like 'screen' or 'tmux' to avoid this. + +### Stable Release +This will install the latest **stable release** from the *future3/main* branch. ```bash cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh) ``` -This will get the latest **stable release** from the branch *future3/main*. +### Pre-Release +This will install the latest **pre-release** from the *future3/develop* branch. -To install directly from a specific branch and/or a different repository -specify the variables like this: +```bash +cd; GIT_BRANCH='future3/develop' bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) +``` + +### Development +You can also install a specific branch and/or a fork repository. Update the variables to refer to your desired location. (The URL must not necessarily be updated, unless you have actually updated the file being downloaded.) + +> [!IMPORTANT] +> A fork repository must be named '*RPi-Jukebox-RFID*' like the official repository ```bash cd; GIT_USER='MiczFlor' GIT_BRANCH='future3/develop' bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) ``` -This will switch directly to the specified feature branch during installation. - > [!NOTE] -> For all branches *except* the current Release future3/main, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Developer steps to install](../developers/development-environment.md#steps-to-install) +> The Installation of the official repository's release branches ([Stable Release](#stable-release) and [Pre-Release](#pre-release)) will deploy a pre-build bundle of the Web App. +> If you install another branch or from a fork repository, the Web App needs to be built locally. This is part of the installation process. See the the developers [Web App](../developers/webapp.md) documentation for further details. -If you suspect an error you can monitor the installation-process with +### Logs +To follow the installation closely, use this command in another terminal. ```bash cd; tail -f INSTALL-.log ``` -After successful installation, continue with [configuring your Phoniebox](configuration.md). diff --git a/documentation/builders/system.md b/documentation/builders/system.md index 2f7df8888..f6eeb7ba1 100644 --- a/documentation/builders/system.md +++ b/documentation/builders/system.md @@ -7,7 +7,7 @@ The system consists of 1. [Music Player Daemon (MPD)](system.md#music-player-daemon-mpd) which we use for all music playback (local, stream, podcast, ...) 2. [PulseAudio](system.md#pulseaudio) for flexible audio output support 3. [Jukebox Core Service](system.md#jukebox-core-service) for controlling MPD and PulseAudio and providing all the features -4. [Web UI](system.md#web-ui) which is served through an Nginx web server +4. [Web App](system.md#web-app-ui) as User Interface (UI) for a web browser 5. A set of [Configuration Tools](../developers/coreapps.md#configuration-tools) and a set of [Developer Tools](../developers/coreapps.md#developer-tools) > [!NOTE] The default install puts everything into the users home folder `~/RPi-Jukebox-RFID`. @@ -96,9 +96,9 @@ The `systemd` service file is located at the default location for user services: Starting and stopping the service can be useful for debugging or configuration checks. -## Web UI +## Web App (UI) -The Web UI is served using nginx. Nginx runs as a system service. The home directory is localed at +The [Web App](../developers/webapp.md) is served using nginx. Nginx runs as a system service. The home directory is located at ```text ./src/webapp/build diff --git a/documentation/builders/update.md b/documentation/builders/update.md index f6dab2c91..94baf0a93 100644 --- a/documentation/builders/update.md +++ b/documentation/builders/update.md @@ -29,7 +29,7 @@ $ git pull $ diff shared/settings/jukebox.yaml resources/default-settings/jukebox.default.yaml $ cd src/webapp -$ ./run_rebuild.sh +$ ./run_rebuild.sh -u ``` ## Migration Path from Version 2 diff --git a/documentation/developers/README.md b/documentation/developers/README.md index 64fee44c1..cfa389713 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -8,6 +8,7 @@ ## Reference * [Jukebox Apps](./coreapps.md) +* [Web App](./webapp.md) * [RFID Readers](./rfid) * [Feature Status](./status.md) * [Known Issues](./known-issues.md) diff --git a/documentation/developers/developer-issues.md b/documentation/developers/developer-issues.md deleted file mode 100644 index bcb4d491a..000000000 --- a/documentation/developers/developer-issues.md +++ /dev/null @@ -1,86 +0,0 @@ -# Developer Issues - -## Building the Webapp on the PI - -### JavaScript heap out of memory - -While (re-) building the Web App, you get the following output: - -``` {.bash emphasize-lines="12"} -pi@MusicPi:~/RPi-Jukebox-RFID/src/webapp $ npm run build - -> webapp@0.1.0 build -> react-scripts build - -Creating an optimized production build... - -[...] - -<--- JS stacktrace ---> - -FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory -``` - -#### Reason - -Not enough memory for Node - -#### Solution - -Prior to building set the node memory environment variable. - -1. Make sure the value is less than the total available space on the - system, or you may run into the next issue. (Not always though!) - Check memory availability with `free -mt`. -2. We also experience trouble, when the space is set too small a - value. 512 always works, 256 sometimes does, sometimes does not. - If your free memory is small, consider increasing the swap size of - your system! - -``` bash -export NODE_OPTIONS=--max-old-space-size=512 -npm run build -``` - -Alternatively, use the provided script, which sets the variable for you -(provided your swap size is large enough): - -``` bash -$ cd src/webapp -$ ./run_rebuild.sh -``` - -#### Changing Swap Size - -This will set the swapsize to 1024 MB (and will deactivate swapfactor). Change accordingly if you have a SD Card with small capacity. - -```bash -sudo dphys-swapfile swapoff -sudo sed -i "s|.*CONF_SWAPSIZE=.*|CONF_SWAPSIZE=1024|g" /etc/dphys-swapfile -sudo sed -i "s|^\s*CONF_SWAPFACTOR=|#CONF_SWAPFACTOR=|g" /etc/dphys-swapfile -sudo dphys-swapfile setup -sudo dphys-swapfile swapon -``` - -### Process exited too early // kill -9 - -``` {.bash emphasize-lines="8,9"} -pi@MusicPi:~/RPi-Jukebox-RFID/src/webapp $ npm run build - -> webapp@0.1.0 build -> react-scripts build - -... - -The build failed because the process exited too early. -This probably means the system ran out of memory or someone called 'kill -9' on the process. -``` - -#### Reason - -Node tried to allocate more memory than available on the system. - -#### Solution - -Adjust the node memory variable as described in [JavaScript heap out of memory](#javascript-heap-out-of-memory). But make sure to allocate less memory than the available memory. If that is not sufficient, increase the swap file size of your -system and try again. diff --git a/documentation/developers/development-environment.md b/documentation/developers/development-environment.md index 249f6263c..d2d995919 100644 --- a/documentation/developers/development-environment.md +++ b/documentation/developers/development-environment.md @@ -1,6 +1,6 @@ # Development Environment -You have 3 development options. Each option has its pros and cons. To interact with GPIO or other hardware, it's required to develop directly on a Raspberry Pi. For general development of Python code (Jukebox) or JavaScript (Webapp), we recommend Docker. Developing on your local machine (Linux, Mac, Windows) works as well and requires all dependencies to be installed locally. +You have 3 development options. Each option has its pros and cons. To interact with GPIO or other hardware, it's required to develop directly on a Raspberry Pi. For general development of Python code (Jukebox) or JavaScript (Web App), we recommend Docker. Developing on your local machine (Linux, Mac, Windows) works as well and requires all dependencies to be installed locally. - [Development Environment](#development-environment) - [Develop in Docker](#develop-in-docker) @@ -15,35 +15,15 @@ There is a complete [Docker setup](./docker.md). ## Develop on Raspberry Pi -The full setup is running on the RPi and you access files via SSH. Pretty easy to set up as you simply do a normal install and switch to the `future3/develop` branch. +The full setup is running on the RPi and you access files via SSH. ### Steps to install -We recommend to use at least a Pi 3 or Pi Zero 2 for development. This hardware won\'t be needed in production, but it can be slow while developing. +We recommend to use at least a Pi 3 or Pi Zero 2 for development. While this hardware won\'t be needed in production, it comes in helpful while developing. -1. Install the latest Pi OS on a SD card. -2. Boot up your Raspberry Pi. -3. [Install](../builders/installation.md) the Jukebox software as if you were building a Phoniebox. You can install from your own fork and feature branch you wish which can be changed later as well. The original repository will be set as `upstream`. -4. Once the installation has successfully ran, reboot your Pi. -5. Due to some resource constraints, the Webapp does not build the latest changes and instead consumes the latest official release. To change that, you need to install NodeJS and build the Webapp locally. -6. Install NodeJS using the existing installer - - ``` bash - cd ~/RPi-Jukebox-RFID/installation/routines; \ - source setup_jukebox_webapp.sh; \ - _jukebox_webapp_install_node - ``` - -7. To free up RAM, reboot your Pi. -8. Build the Webapp using the existing build command. If the build fails, you might have forgotten to reboot. - - ``` bash - cd ~/RPi-Jukebox-RFID/src/webapp; \ - ./run_rebuild.sh -u - ``` - -9. The Webapp should now be updated. -10. To continuously update Webapp, pull the latest changes from your repository and rerun the command above. +1. Follow the [installation preperation](../builders/installation.md#install-raspberry-pi-os-lite) steps +1. [Install](../builders/installation.md#development) your feature/fork branch of the Jukebox software. The official repository will be set as `upstream`. +1. If neccessary [build the Web App](./webapp.md) locally ## Develop on local machine diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 80651ce84..3718373db 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -30,7 +30,7 @@ need to adapt some of those commands to your needs. $ cp ./resources/default-settings/jukebox.default.yaml ./shared/settings/jukebox.yaml ``` - * Override/Merge the values from the following [Override file](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/docker/config/jukebox.overrides.yaml) in your `jukebox.yaml`. + * Override/Merge the values from the following [Override file](../../docker/config/jukebox.overrides.yaml) in your `jukebox.yaml`. * **\[Currently required\]** Update all relative paths (`../..`) in to `/home/pi/RPi-Jukebox-RFID`. 4. Change directory into the `./shared/audiofolders` @@ -159,7 +159,7 @@ Read these threads for details: [thread 1](https://unix.stackexchange.com/questi The Dockerfile is defined to start all Phoniebox related services. -Open in your browser to see the web application. +Open in your browser to see the Web App. While the `webapp` container does not require a reload while working on it (hot-reload is enabled), you will have to restart your `jukebox` diff --git a/documentation/developers/status.md b/documentation/developers/status.md index 48c2b6c3b..0a40f8125 100644 --- a/documentation/developers/status.md +++ b/documentation/developers/status.md @@ -6,7 +6,7 @@ There are a few things that are specifically not integrated yet: playing streams In the following is the currently implemented feature list in more detail. It also shows some of the shortcomings. However, the list is _not complete in terms of planned features_, but probably _reflects more of where work is currently being put into_. -**For new contributors:** If you want to port a feature from version 2.X or implement a new feature, contact us. Open an issue or join us in the chat room. You may pick topics marked as open below, but also any other topic missing in the list below. As mentioned, that list is not complete in terms of open features. Check the [Contribution guide](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/CONTRIBUTING.md). +**For new contributors:** If you want to port a feature from version 2.X or implement a new feature, contact us. Open an issue or join us in the chat room. You may pick topics marked as open below, but also any other topic missing in the list below. As mentioned, that list is not complete in terms of open features. Check the [Contribution guide](../../CONTRIBUTING.md). Topics marked _in progress_ are already in the process of implementation by community members. diff --git a/documentation/developers/webapp.md b/documentation/developers/webapp.md new file mode 100644 index 000000000..2e8504337 --- /dev/null +++ b/documentation/developers/webapp.md @@ -0,0 +1,149 @@ +# Web App + +The Web App sources are located in `src/webapp`. A pre-build bundle of the Web App is deployed when installing from an official release branch. If you install from a feature branch or a fork repository, the Web App needs to be built locally. This requires Node to be installed and is part of the installation process. + +## Install node manually + +If you installed from an official release branch, Node might not be installed. To install Node for local development, follow the [official setup](https://deb.nodesource.com/). + +``` bash +NODE_MAJOR=20 +sudo apt-get -y update && sudo apt-get -y install ca-certificates curl gnupg +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg +echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list +sudo apt-get -y update && sudo apt-get -y install nodejs +``` + +## Develop the Web App + +The Web App is a React application based on [Create React App](https://create-react-app.dev/). To start a development server, run the following command: + +``` +cd ~/RPi-Jukebox-RFID/src/webapp +npm install # Just the first time or when dependencies change +npm start +``` + +## Build the Web App + +To build your Web App after its source code has changed (e.g. through a local change or through a pull from the repository), it needs to be rebuilt manually. +Use the provided script to rebuild whenever required. The artifacts can be found in the folder `build`. + +```bash +cd ~/RPi-Jukebox-RFID/src/webapp; \ +./run_rebuild.sh -u +``` + +After a successfull build you might need to restart the web server. + +``` +sudo systemctl restart nginx.service +``` + +## Known Issues while building + +### JavaScript heap out of memory + +While (re-) building the Web App, you get the following output: + +``` {.bash emphasize-lines="12"} +> webapp@0.1.0 build +> react-scripts build + +Creating an optimized production build... + +[...] + +<--- JS stacktrace ---> + +FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory +``` + +#### Reason + +Not enough memory for Node + +#### Solution + +Use the [provided script](#build-the-web-app) to rebuild the Web App. It sets the needed node options and also checks and adjusts the swap size if there is not enough memory available. + +If you need to run the commands manually, make sure to have enough memory available (min. 512 MB). The following commands might help. + +Set the swapsize to 512 MB (and deactivate swapfactor). Adapt accordingly if you have a SD Card with small capacity. +```bash +sudo dphys-swapfile swapoff +sudo sed -i "s|.*CONF_SWAPSIZE=.*|CONF_SWAPSIZE=512|g" /etc/dphys-swapfile +sudo sed -i "s|^\s*CONF_SWAPFACTOR=|#CONF_SWAPFACTOR=|g" /etc/dphys-swapfile +sudo dphys-swapfile setup +sudo dphys-swapfile swapon +``` + +Set Node's maximum amount of memory. Memory must be available. +``` bash +export NODE_OPTIONS=--max-old-space-size=512 +npm run build +``` + +### Process exited too early // kill -9 + +``` {.bash emphasize-lines="8,9"} +> webapp@0.1.0 build +> react-scripts build + +[...] + +The build failed because the process exited too early. +This probably means the system ran out of memory or someone called 'kill -9' on the process. +``` + +#### Reason + +Node tried to allocate more memory than available on the system. + +#### Solution + +See [JavaScript heap out of memory](#javascript-heap-out-of-memory) + + +### Client network socket disconnected + +``` {.bash emphasize-lines="8,9"} +[...] + +npm ERR! code ECONNRESET +npm ERR! network Client network socket disconnected before secure TLS connection was established +npm ERR! network This is a problem related to network connectivity. +npm ERR! network In most cases you are behind a proxy or have bad network settings. +npm ERR! network +npm ERR! network If you are behind a proxy, please make sure that the +npm ERR! network 'proxy' config is set properly. See: 'npm help config' +``` + +#### Reason + +The network connection is too slow or has issues. +This tends to happen on `armv6l` devices where building takes significantly more time due to limited resources. + +#### Solution + +Try to use an ethernet connection. A reboot and/or running the script multiple times might also help ([Build produces EOF errors](#build-produces-eof-errors) might occur). + +If the error still persists, try to raise the timeout for npm package resolution. + +1. Open the npm config file in an editor +1. Increase the `fetch-retry-*` values by '30000' (30 seconds) and save +1. Retry the build + +### Build produces EOF errors + +#### Reason + +A previous run failed during installation and left a package corrupted. + +#### Solution + +Remove the mode packages and rerun again the script. +``` {.bash emphasize-lines="8,9"} +rm -rf node_modules +``` diff --git a/installation/includes/00_constants.sh b/installation/includes/00_constants.sh index 380e1de2e..89299989c 100644 --- a/installation/includes/00_constants.sh +++ b/installation/includes/00_constants.sh @@ -16,4 +16,7 @@ GIT_BRANCH_DEVELOP=${GIT_BRANCH_DEVELOP:-future3/develop} # This message will be displayed at the end of the installation process # Functions wanting to have something important printed at the end should APPEND to this variable +# example: +# local tmp_fin_message="A Message" +# FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" FIN_MESSAGE="" diff --git a/installation/includes/01_default_config.sh b/installation/includes/01_default_config.sh index fa1bafb61..ec7b67b66 100644 --- a/installation/includes/01_default_config.sh +++ b/installation/includes/01_default_config.sh @@ -26,8 +26,6 @@ GIT_USE_SSH=${GIT_USE_SSH:-"true"} # For non-production builds, the Wep App must be build locally # Valid values # - release-only: download in release branch only -# - true: force download even in non-release branch, +# - true: force download even in non-release branch # - false: never download ENABLE_WEBAPP_PROD_DOWNLOAD=${ENABLE_WEBAPP_PROD_DOWNLOAD:-"release-only"} -# Install Node during setup for Web App building. This is only needed for development builds -ENABLE_INSTALL_NODE=${ENABLE_INSTALL_NODE:-"false"} diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 239a18ccf..e9ca7640e 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -2,6 +2,17 @@ ### Helpers +show_slow_hardware_message() { + if [[ $(uname -m) == "armv6l" ]]; then + print_c "-------------------------------------------------------------------- +| Your hardware is a little slower so this will take a while. | +| Go watch a movie but don't let your computer go to sleep for the | +| SSH connection to remain intact. | +-------------------------------------------------------------------- +" + fi +} + # $1->start, $2->end calc_runtime_and_print() { runtime=$(($2-$1)) diff --git a/installation/includes/03_welcome.sh b/installation/includes/03_welcome.sh index 5b3ee84be..62c4910c8 100644 --- a/installation/includes/03_welcome.sh +++ b/installation/includes/03_welcome.sh @@ -16,16 +16,16 @@ You are turning your Raspberry Pi into a Phoniebox. Good choice! Depending on your hardware, this installation might last -around 60 minutes (usually it's faster). It updates OS -packages, installs Phoniebox dependencies and registers -settings. Be patient and don't let your computer go to -sleep. It might disconnect your SSH connection causing -the interruption of the installation process. +around 60 minutes (usually it's faster, 20-30 min). It +updates OS packages, installs Phoniebox dependencies and +applies settings. Be patient and don't let your computer +go to sleep. It might disconnect your SSH connection +causing the interruption of the installation process. Consider starting the installation in a terminal multiplexer like 'screen' or 'tmux' to avoid this. -By the way, you can follow the installation details here -in a separate SSH session: +To follow the installation closely, use this command +in another terminal. cd; tail -f ${INSTALLATION_LOGFILE} Let's set up your Phoniebox. diff --git a/installation/includes/05_finish.sh b/installation/includes/05_finish.sh index 22ba6ae80..c48fc31d2 100644 --- a/installation/includes/05_finish.sh +++ b/installation/includes/05_finish.sh @@ -11,7 +11,7 @@ ${FIN_MESSAGE} In order to start, you need to reboot your Raspberry Pi. Your SSH connection will disconnect. -After the reboot, you can access the WebApp in your browser at +After the reboot, you can access the Web App in your browser at http://${local_hostname}.local or http://${CURRENT_IP_ADDRESS} Don't forget to upload files. " diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index 7409a6e07..3e94b07dc 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -189,12 +189,12 @@ Do you want to install Samba? [Y/n]" _option_webapp() { # ENABLE_WEBAPP clear_c - print_c "------------------------ WEBAPP ------------------------- + print_c "------------------------ WEB APP ------------------------ This is only required if you want to use a graphical interface to manage your Phoniebox! -Would you like to install the web application? [Y/n]" +Would you like to install the Web App? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -213,7 +213,7 @@ _option_kiosk_mode() { print_c "----------------------- KIOSK MODE ---------------------- If you have a screen attached to your RPi, -this will launch the web application right after boot. +this will launch the Web App right after boot. It will only install the necessary xserver dependencies and not the entire RPi desktop environment. @@ -282,48 +282,38 @@ Disable Pi's on-chip audio (headphone / jack output)? [y/N]" _option_webapp_devel_build() { # Let's detect if we are on the official release branch - if [[ "$GIT_BRANCH" != "${GIT_BRANCH_RELEASE}" || "$GIT_USER" != "$GIT_UPSTREAM_USER" || "$CI_RUNNING" == "true" ]]; then - ENABLE_INSTALL_NODE=true + if [[ "$GIT_BRANCH" != "${GIT_BRANCH_RELEASE}" && "$GIT_BRANCH" != "${GIT_BRANCH_DEVELOP}" ]] || [[ "$GIT_USER" != "$GIT_UPSTREAM_USER" ]] || [[ "$CI_RUNNING" == "true" ]] ; then # Unless ENABLE_WEBAPP_PROD_DOWNLOAD is forced to true by user override, do not download a potentially stale build if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == "release-only" ]]; then ENABLE_WEBAPP_PROD_DOWNLOAD=false fi - if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == false ]]; then + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" != true && "$ENABLE_WEBAPP_PROD_DOWNLOAD" != "release-only" ]]; then clear_c - print_c "--------------------- WEBAPP NODE --------------------- + print_c "--------------------- WEB APP BUILD --------------------- -You are installing from an unofficial branch. -Therefore a prebuilt web app is not available and -you will have to build it locally. +You are installing from a non-release branch +and/or an unofficial repository. +Therefore a pre-build Web App is not available +and it needs to be built locally. This requires Node to be installed. -You can choose to decline the Node installation and -the lastest prebuilt version from the main repository -will be installed. This can lead to incompatibilities. +If you decline, the lastest pre-build version +from the official repository will be installed. +This can lead to incompatibilities. -Do you want to install Node? [Y/n]" +Do you want to build the Web App? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) - ENABLE_INSTALL_NODE=false - ENABLE_WEBAPP_PROD_DOWNLOAD=true - ;; + ENABLE_WEBAPP_PROD_DOWNLOAD=true + ;; *) - # This message will be displayed at the end of the installation process - local tmp_fin_message="ATTENTION: You need to build the web app locally with - $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u - This must be done after reboot, due to memory restrictions. - Read the documentation regarding local Web App builds!" - FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" - ;; + ;; esac fi fi - log "ENABLE_INSTALL_NODE=${ENABLE_INSTALL_NODE}" - if [ "$ENABLE_INSTALL_NODE" != true ]; then - log "ENABLE_WEBAPP_PROD_DOWNLOAD=${ENABLE_WEBAPP_PROD_DOWNLOAD}" - fi + log "ENABLE_WEBAPP_PROD_DOWNLOAD=${ENABLE_WEBAPP_PROD_DOWNLOAD}" } _run_customize_options() { diff --git a/installation/routines/install.sh b/installation/routines/install.sh index 62d602f17..f1f2a5f80 100644 --- a/installation/routines/install.sh +++ b/installation/routines/install.sh @@ -2,6 +2,7 @@ install() { clear_c customize_options clear_c + show_slow_hardware_message set_raspi_config set_ssh_qos update_raspi_os diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index 1c524abb0..d9c06b937 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -8,14 +8,6 @@ JUKEBOX_ZMQ_VERSION="4.3.5" JUKEBOX_PULSE_CONFIG="${HOME_PATH}"/.config/pulse/default.pa JUKEBOX_SERVICE_NAME="${SYSTEMD_USR_PATH}/jukebox-daemon.service" -_show_slow_hardware_message() { - print_c " -------------------------------------------------------------------- - | Your hardware is a little slower so this step will take a while. | - | Go watch a movie but don't let your computer go to sleep for the | - | SSH connection to remain intact. | - --------------------------------------------------------------------" -} - # Functions _jukebox_core_install_os_dependencies() { print_lc " Install Jukebox OS dependencies" @@ -86,11 +78,6 @@ _jukebox_core_build_and_install_pyzmq() { print_lc " Install pyzmq with libzmq-drafts to support WebSockets" if ! pip list | grep -F pyzmq >> /dev/null; then - - if [[ $(uname -m) == "armv6l" ]]; then - _show_slow_hardware_message - fi - mkdir -p "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error if [ "$BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE" = true ] ; then _jukebox_core_build_libzmq_with_drafts diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 2884f18cb..7fcbce7ff 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -3,58 +3,87 @@ # Constants WEBAPP_NGINX_SITE_DEFAULT_CONF="/etc/nginx/sites-available/default" -# For ARMv7+ +# Node major version used. +# If changed also update in .github\actions\build-webapp\action.yml NODE_MAJOR=20 -# For ARMv6 -# To update version, follow these links -# https://github.com/sdesalas/node-pi-zero -# https://github.com/nodejs/unofficial-builds/ -NODE_SOURCE_EXPERIMENTAL="https://raw.githubusercontent.com/sdesalas/node-pi-zero/master/install-node-v16.3.0.sh" +# Node version for ARMv6 (unofficial builds) +NODE_ARMv6_VERSION=v20.10.0 -_jukebox_webapp_install_node() { - sudo apt-get -y update +OPTIONAL_WEBAPP_BUILD_FAILED=false - if which node > /dev/null; then - print_lc " Found existing NodeJS. Hence, updating NodeJS" - sudo npm cache clean -f - sudo npm install --silent -g n - sudo n --quiet latest - sudo npm update --silent -g - else +_jukebox_webapp_install_node() { print_lc " Install NodeJS" - # Zero and older versions of Pi with ARMv6 only - # support experimental NodeJS - if [[ $(uname -m) == "armv6l" ]]; then - wget -O - ${NODE_SOURCE_EXPERIMENTAL} | sudo bash - sudo apt-get -qq -y install nodejs - sudo npm install --silent -g npm + local node_version_installed=$(node -v 2>/dev/null) + local arch=$(uname -m) + if [[ "$arch" == "armv6l" ]]; then + if [ "$node_version_installed" == "$NODE_ARMv6_VERSION" ]; then + print_lc " Skipping. NodeJS already installed" + else + # For ARMv6 unofficial build + # https://github.com/nodejs/unofficial-builds/ + local node_tmp_dir="${HOME_PATH}/node" + local node_install_dir=/usr/local/lib/nodejs + local node_filename="node-${NODE_ARMv6_VERSION}-linux-${arch}" + local node_tar_filename="${node_filename}.tar.gz" + node_download_url="https://unofficial-builds.nodejs.org/download/release/${NODE_ARMv6_VERSION}/${node_tar_filename}" + + mkdir -p "${node_tmp_dir}" && cd "${node_tmp_dir}" || exit_on_error + download_from_url ${node_download_url} ${node_tar_filename} + tar -xzf ${node_tar_filename} + rm -rf ${node_tar_filename} + + # see https://github.com/nodejs/help/wiki/Installation + # Remove existing symlinks + sudo unlink /usr/bin/node 2>/dev/null + sudo unlink /usr/bin/npm 2>/dev/null + sudo unlink /usr/bin/npx 2>/dev/null + + # Clear existing nodejs and copy new files + sudo rm -rf "${node_install_dir}" + sudo mv "${node_filename}" "${node_install_dir}" + + sudo ln -s "${node_install_dir}/bin/node" /usr/bin/node + sudo ln -s "${node_install_dir}/bin/npm" /usr/bin/npm + sudo ln -s "${node_install_dir}/bin/npx" /usr/bin/npx + + cd "${HOME_PATH}" || exit_on_error + rm -rf "${node_tmp_dir}" + fi else - # install NodeJS and npm as recommended in - # https://github.com/nodesource/distributions - sudo apt-get install -y ca-certificates curl gnupg - sudo mkdir -p /etc/apt/keyrings - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg - echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list - sudo apt-get update - sudo apt-get install -y nodejs + if [[ "$node_version_installed" == "v${NODE_MAJOR}."* ]]; then + print_lc " Skipping. NodeJS already installed" + else + sudo apt-get -y remove nodejs + # install NodeJS as recommended in + # https://deb.nodesource.com/ + sudo apt-get -y update && sudo apt-get -y install ca-certificates curl gnupg + sudo mkdir -p /etc/apt/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list + sudo apt-get -y update && sudo apt-get -y install nodejs + fi fi - fi } -# TODO: Avoid building the app locally -# Instead implement a Github Action that prebuilds on commititung a git tag _jukebox_webapp_build() { - print_lc " Building web application" - cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error - npm ci --prefer-offline --no-audit --production - rm -rf build - # The build wrapper script checks available memory on system and sets Node options accordingly - ./run_rebuild.sh + print_lc " Building Web App" + cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error + if ! ./run_rebuild.sh -u ; then + print_lc " Web App build failed! + Follow instructions shown at the end of installation!" + OPTIONAL_WEBAPP_BUILD_FAILED=true + # This message will be displayed at the end of the installation process + local tmp_fin_message="ATTENTION: The build of the Web App failed during installation. + Please run the build manually with the following command + $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u + Read the documentation regarding local Web App builds!" + FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" + fi } _jukebox_webapp_download() { - print_lc " Downloading web application" + print_lc " Downloading Web App" local jukebox_version=$(python "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") local git_head_hash=$(git -C "${INSTALLATION_PATH}" rev-parse --verify --quiet HEAD) local git_head_hash_short=${git_head_hash:0:10} @@ -67,11 +96,11 @@ _jukebox_webapp_download() { if validate_url ${download_url_commit} ; then log " DOWNLOAD_URL ${download_url_commit}" download_from_url ${download_url_commit} ${tar_filename} - elif [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true ]] && validate_url ${download_url_latest} ; then + elif [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == true ]] && validate_url ${download_url_latest} ; then log " DOWNLOAD_URL ${download_url_latest}" download_from_url ${download_url_latest} ${tar_filename} else - exit_on_error "No prebuild webapp bundle found!" + exit_on_error "No prebuild Web App bundle found!" fi tar -xzf ${tar_filename} rm -f ${tar_filename} @@ -80,7 +109,7 @@ _jukebox_webapp_download() { _jukebox_webapp_register_as_system_service_with_nginx() { print_lc " Install and configure nginx" - sudo apt-get -qq -y update + sudo apt-get -y update sudo apt-get -y purge apache2 sudo apt-get -y install nginx @@ -97,11 +126,22 @@ _jukebox_webapp_register_as_system_service_with_nginx() { _jukebox_webapp_check() { print_verify_installation - if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == true || "$ENABLE_WEBAPP_PROD_DOWNLOAD" == "release-only" ]] ; then verify_dirs_exists "${INSTALLATION_PATH}/src/webapp/build" - fi - if [[ $ENABLE_INSTALL_NODE == true ]] ; then - verify_apt_packages nodejs + else + local arch=$(uname -m) + if [[ "$arch" == "armv6l" ]]; then + local node_version_installed=$(node -v 2>/dev/null) + log " Verify 'node' is installed" + test ! "${node_version_installed}" == "${NODE_ARMv6_VERSION}" && exit_on_error "ERROR: 'node' not in expected version: '${node_version_installed}' instead of '${NODE_ARMv6_VERSION}'!" + log " CHECK" + else + verify_apt_packages nodejs + fi + + if [[ "$OPTIONAL_WEBAPP_BUILD_FAILED" == false ]]; then + verify_dirs_exists "${INSTALLATION_PATH}/src/webapp/build" + fi fi verify_apt_packages nginx @@ -111,14 +151,11 @@ _jukebox_webapp_check() { } _run_setup_jukebox_webapp() { - if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == true || "$ENABLE_WEBAPP_PROD_DOWNLOAD" == "release-only" ]] ; then _jukebox_webapp_download - fi - if [[ $ENABLE_INSTALL_NODE == true ]] ; then + else _jukebox_webapp_install_node - # Local Web App build during installation does not work at the moment - # Needs to be done after reboot! There will be a message at the end of the installation process - # _jukebox_webapp_build + _jukebox_webapp_build fi _jukebox_webapp_register_as_system_service_with_nginx _jukebox_webapp_check @@ -126,6 +163,6 @@ _run_setup_jukebox_webapp() { setup_jukebox_webapp() { if [ "$ENABLE_WEBAPP" == true ] ; then - run_with_log_frame _run_setup_jukebox_webapp "Install web application" + run_with_log_frame _run_setup_jukebox_webapp "Install Web App" fi } diff --git a/resources/default-settings/gpio.example.yaml b/resources/default-settings/gpio.example.yaml index f19b83c87..59793210f 100644 --- a/resources/default-settings/gpio.example.yaml +++ b/resources/default-settings/gpio.example.yaml @@ -1,6 +1,6 @@ # Provides a few selected examples of GPIO configuration # Check out the documentation for many more configuration recipes -# https://rpi-jukebox-rfid.readthedocs.io/en/latest/index.html +# documentation/builders/gpio.md pin_factory: type: rpigpio.RPiGPIOFactory output_devices: diff --git a/resources/html/404.html b/resources/html/404.html index 1a60d2415..a79decc6c 100755 --- a/resources/html/404.html +++ b/resources/html/404.html @@ -15,7 +15,7 @@

Ups! Requested file not found.

Why not try again from the top-level of

diff --git a/resources/html/runbuildui.html b/resources/html/runbuildui.html index 04f92c704..6ade7bb0a 100755 --- a/resources/html/runbuildui.html +++ b/resources/html/runbuildui.html @@ -12,11 +12,11 @@

Ups! Looks like your Web UI has not been build!

No reason to panic. Please run through the following steps:

    -
  • cd ~/RPi-Jukebox-RFID/src/webapp
  • -
  • ./run_rebuild.sh -u
  • -
  • Reload this page
  • +
  • cd ~/RPi-Jukebox-RFID/src/webapp

  • +
  • ./run_rebuild.sh -u

  • +
  • Reload this page

-

In case of trouble when building the Web UI, consult the documentation! +

In case of trouble when building the Web UI, consult the official documentation! diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index c77c21051..a2dbc914a 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -56,8 +56,9 @@ """ # noqa: E501 # Warum ist "Second Swipe" im Player und nicht im RFID Reader? # Second swipe ist abhängig vom Player State - nicht vom RFID state. -# Beispiel: RFID triggered Folder1, Webapp triggered Folder2, RFID Folder1: Dann muss das 2. Mal Folder1 auch als "first swipe" -# gewertet werden. Wenn der RFID das basierend auf IDs macht, kann der nicht unterscheiden und glaubt es ist 2. Swipe. +# Beispiel: RFID triggered Folder1, Web App triggered Folder2, RFID Folder1: +# Dann muss das 2. Mal Folder1 auch als "first swipe" gewertet werden. +# Wenn der RFID das basierend auf IDs macht, kann der nicht unterscheiden und glaubt es ist 2. Swipe. # Beispiel 2: Jemand hat RFID Reader (oder 1x RFID und 1x Barcode Scanner oder so) angeschlossen. Liest zuerst Karte mit # Reader 1 und dann mit Reader 2: Reader 2 weiß nicht, was bei Reader 1 passiert ist und denkt es ist 1. swipe. # Beispiel 3: RFID trigered Folder1, Playlist läuft durch und hat schon gestoppt, dann wird die Karte wieder vorgehalten. @@ -68,7 +69,7 @@ # # In der aktuellen Implementierung weiß der Player (der second "swipe" dekodiert) überhaupt nichts vom RFID. # Im Prinzip gibt es zwei "Play" Funktionen: (1) play always from start und (2) play with toggle action. -# Die Webapp ruft immer (1) auf und die RFID immer (2). Jetzt kann man sogar für einige Karten sagen +# Die Web App ruft immer (1) auf und die RFID immer (2). Jetzt kann man sogar für einige Karten sagen # immer (1) - also kein Second Swipe und für andere (2). # Sollte der Reader das Swcond swipe dekodieren, muss aber der Reader den Status des Player kennen. # Das ist allerdings ein Problem. In Version 2 ist das nicht aufgefallen, @@ -76,7 +77,7 @@ # # Beispiel: Second swipe bei anderen Funktionen, hier: WiFi on/off. # Was die Karte Action tut ist ein Toggle. Der Toggle hängt vom Wifi State ab, den der RFID Kartenleser nicht kennt. -# Den kann der Leser auch nicht tracken. Der State kann ja auch über die WebApp oder Kommandozeile geändert werden. +# Den kann der Leser auch nicht tracken. Der State kann ja auch über die Web App oder Kommandozeile geändert werden. # Toggle (und 2nd Swipe generell) ist immer vom Status des Zielsystems abhängig und kann damit nur vom Zielsystem geändert # werden. Bei Wifi also braucht man 3 Funktionen: on / off / toggle. Toggle ist dann first swipe / second swipe diff --git a/src/webapp/.gitignore b/src/webapp/.gitignore index 4d29575de..b32ff75cd 100644 --- a/src/webapp/.gitignore +++ b/src/webapp/.gitignore @@ -11,6 +11,9 @@ # production /build +# development +/build.bak + # misc .DS_Store .env.local diff --git a/src/webapp/.npmrc b/src/webapp/.npmrc new file mode 100644 index 000000000..f05c1e85f --- /dev/null +++ b/src/webapp/.npmrc @@ -0,0 +1,3 @@ +fetch-retries=10 +fetch-retry-mintimeout=20000 +fetch-retry-maxtimeout=120000 diff --git a/src/webapp/public/index.html b/src/webapp/public/index.html index 71ea85188..30c055a5e 100644 --- a/src/webapp/public/index.html +++ b/src/webapp/public/index.html @@ -7,7 +7,7 @@ diff --git a/src/webapp/run_rebuild.sh b/src/webapp/run_rebuild.sh index 870813d78..e8e4d06c0 100755 --- a/src/webapp/run_rebuild.sh +++ b/src/webapp/run_rebuild.sh @@ -1,30 +1,35 @@ #!/usr/bin/env bash usage() { - echo -e "\nRebuild the Web App\n" - echo "${BASH_SOURCE[0]} [-u] [-m SIZE]" - echo " -u : Update NPM dependencies before rebuild (only necessary if package.json changed)" - echo " -m SIZE : Set Node memory limit in MB (if omitted limit is deduced automatically)" - echo -e "\n\n" + echo -e "\nRebuild the Web App\n" + echo "${BASH_SOURCE[0]} [-u] [-m SIZE]" + echo " -u : Update NPM dependencies before rebuild (only necessary on first build or if package.json changed" + echo " -m SIZE : Set Node memory limit in MB (if omitted limit is deduced automatically and swap might be adjusted)" + echo " -v : Increase verbosity" + echo -e "\n\n" } UPDATE_DEPENDENCIES=false +VERBOSE=false -while getopts ":uhm:" opt; do - case ${opt} in - u) - UPDATE_DEPENDENCIES=true - ;; - m) - NODEMEM="${OPTARG}" - ;; - h) - usage - ;; - \?) - usage - ;; - esac +while getopts ":uhvm:" opt; do + case ${opt} in + u) + UPDATE_DEPENDENCIES=true + ;; + m) + NODEMEM="${OPTARG}" + ;; + v) + VERBOSE=true + ;; + h) + usage + ;; + \?) + usage + ;; + esac done # Change working directory to location of script @@ -32,44 +37,87 @@ SOURCE=${BASH_SOURCE[0]} SCRIPT_DIR="$(dirname "$SOURCE")" cd "$SCRIPT_DIR" || exit 1 -# Need to check free space and limit Node memory usage -# for PIs with little memory -MemTotal=$(grep MemTotal /proc/meminfo | awk '{print $2}') -MemFree=$(grep MemFree /proc/meminfo | awk '{print $2}') -SwapFree=$(grep SwapFree /proc/meminfo | awk '{print $2}') -TotalFree=$((SwapFree + MemFree)) - -MemTotal=$((MemTotal / 1024)) -MemFree=$((MemFree / 1024)) -SwapFree=$((SwapFree / 1024)) -TotalFree=$((TotalFree / 1024)) - -echo "Total phys memory: ${MemTotal} MB" -echo "Free phys memory : ${MemFree} MB" -echo "Free swap memory : ${SwapFree} MB" -echo "Free total memory: ${TotalFree} MB" - - -if [[ -z $NODEMEM ]]; then - # Keep a buffer of minimum 20 MB - if [[ $TotalFree -gt 1044 ]]; then - NODEMEM=1024 - elif [[ $TotalFree -gt 532 ]]; then - NODEMEM=512 - elif [[ $TotalFree -gt 276 ]]; then - NODEMEM=256 - else - echo "ERROR: Not enough memory available on system. Please increase swap size to give at least 276 MByte free memory." - echo "Current free memory = $TotalFree MB" - echo "Hint: if only a little memory is missing, stopping spocon, mpd, and jukebox-daemon might give you enough space" - exit 1 - fi -fi +change_swap() { + local new_swap_size="$1" + sudo dphys-swapfile swapoff || return 1 + sudo sed -i "s|.*CONF_SWAPSIZE=.*|CONF_SWAPSIZE=${new_swap_size}|g" /etc/dphys-swapfile || return 1 + sudo sed -i "s|^\s*CONF_SWAPFACTOR=|#CONF_SWAPFACTOR=|g" /etc/dphys-swapfile || return 1 + sudo dphys-swapfile setup 1&>/dev/null || return 1 + sudo dphys-swapfile swapon || return 1 +} -if [[ $NODEMEM -gt $TotalFree ]]; then - echo "ERROR: Requested node memory setting is larger than available free memory: $NODEMEM MB > $TotalFree MB" - exit 1 -fi +# Need to check free space and limit Node memory usage for PIs with little memory. +# Adjust swap if needed to have minimum memory available +calc_nodemem() { + echo "calculate usable memory" + # keep a buffer for the kernel etc. + local mem_buffer=256 + + local mem_total=$(grep MemTotal /proc/meminfo | awk '{print $2}') + local mem_free=$(grep MemFree /proc/meminfo | awk '{print $2}') + local swap_total=$(grep SwapTotal /proc/meminfo | awk '{print $2}') + local swap_free=$(grep SwapFree /proc/meminfo | awk '{print $2}') + local total_free=$((swap_free + mem_free)) + + mem_total=$((mem_total / 1024)) + mem_free=$((mem_free / 1024)) + swap_total=$((swap_total / 1024)) + swap_free=$((swap_free / 1024)) + total_free=$((total_free / 1024)) + + local free_to_use=$((total_free - mem_buffer)) + + if [ "$VERBOSE" == true ]; then + echo " Total phys memory : ${mem_total} MB" + echo " Free phys memory : ${mem_free} MB" + echo " Total swap memory : ${swap_total} MB" + echo " Free swap memory : ${swap_free} MB" + echo " Free total memory : ${total_free} MB" + echo " Keep as buffer : ${mem_buffer} MB" + echo -e " Free usable memory: ${free_to_use} MB\n" + fi + + if [[ -z $NODEMEM ]]; then + # mininum memory used for node + local mem_min=512 + if [[ $free_to_use -gt $mem_min ]]; then + NODEMEM=$free_to_use + else + echo " WARN: Not enough memory left on system for node (usable ${free_to_use} MB, min. ${mem_min} MB)." + echo " Trying to adjust swap size ..." + + local add_swap_size=$((mem_min / 2)) + local new_swap_size=$((swap_total + add_swap_size)) + + # keep a buffer on the filesystem + local filesystem_needed=$((add_swap_size + 512)) + local filesystem_free=$(df -BM -P / | tail -n 1 | awk '{print $4}') + filesystem_free=${filesystem_free//M} + + if [ "$VERBOSE" == true ]; then + echo " New swap size = $new_swap_size MB" + echo " Additional filesystem space needed = $filesystem_needed MB" + echo " Current free filesystem space = $filesystem_free MB" + fi + + if [ "${filesystem_free}" -lt "${filesystem_needed}" ]; then + echo " ERROR: Not enough space available on filesystem for swap (free ${filesystem_free} MB, min. ${filesystem_needed} MB). Abort!" + exit 1 + elif ! change_swap $new_swap_size ; then + echo " ERROR: failed to change swap size. Abort!" + exit 1 + fi + + calc_nodemem || return 1 + fi + + elif [[ $NODEMEM -gt $free_to_use ]]; then + echo " ERROR: Requested node memory setting is larger than usable free memory: ${NODEMEM} MB > ${free_to_use} MB (free ${total_free} MB - buffer ${mem_buffer} MB). Abort!" + exit 1 + fi +} + +calc_nodemem export NODE_OPTIONS=--max-old-space-size=${NODEMEM} @@ -77,16 +125,27 @@ echo "Setting Node Options:" env | grep NODE if [[ $(uname -m) == armv6l ]]; then - echo " You are running on a hardware with less resources. Building - the webapp might fail. If so, try to install the stable - release installation instead." + echo " +----------------------------------------------------------- +| You are running a hardware with limited resources. | +| Building the Web App takes significantly more time. | +| In case it fails, check the documentation | +| to trouble shoot. | +----------------------------------------------------------- +" fi -# In rare cases you will need to update the npm dependencies -# This is the case when the file package.json changed if [[ $UPDATE_DEPENDENCIES == true ]]; then - npm install + npm install --prefer-offline --no-audit fi +build_output_folder="build" # Rebuild Web App -npm run build +rm -rf "${build_output_folder}.bak" +if [ -d "${build_output_folder}" ]; then + mv -f "${build_output_folder}" "${build_output_folder}.bak" +fi +if ! npm run build ; then + echo "ERROR: rebuild of Web App failed!" + exit 1 +fi From 03e619789baaad3e3b0876503605f1e295be0d9e Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:24:29 +0100 Subject: [PATCH 070/121] [New component] HiFiBerry Sound Card & OnOff SHIM (#2169) * Add components folder to installation and docs. First component is hifiberry sound card * Allow for all hifiberry boards * Disabling HDMI Audio * Finalize HiFiBerry doc * Refer to Pi Pinout for convenience * Enable ALSA config as an option as well * Allow script to be run multiple times * Add OnOff SHIM as component * Make script more robust based on PR comments * Update hifiberry soundcard options * Automate soundcard detection for asound.conf * use /boot/config based on debian version * Reorganize a few things * some bugfixes * Final fixes * Optimize case * Update docs * Uninstall option * fix: Remove option was not reachable * fix: enable sudo * fix: make I/O fail silently * feat: Introduce 1-line installation * feat: outsource onboard_sound as its own option * refactor: remove alsa config * fix: update case function * fix: adding some thens * fix: some iterations did not work * fix: adding another sudo * refactor: get_key_by_item_number for associated arrays * refactor: remove last bits of alsa * fix: final touches * fix: add missing removal only option * fix: outsource example_usage for 1-line install * fix: condition for 1-line installation * fix: another fix for if conditions * refactor: move 1-line installation down * gs * another fix * fix: write array check differently * refactor: final touches * feat: enable silent mode for check_existing_hifiberry * fix: reintroduce sudo check * fix: documentation * fix: final touches again * fix: remove is_sudo again * fix: Remove last sudo occurrences * fix: bullet proof * Make bash files executable * fix: Update documentation * fix: 1-line path does not worj * Update documentation/builders/components/soundcards/hifiberry.md Co-authored-by: s-martin * Update documentation/builders/components/soundcards/hifiberry.md Co-authored-by: s-martin * Update documentation/builders/components/soundcards/hifiberry.md Co-authored-by: s-martin * fix: Update regex for commented code * Adding OnOff Shim resource --- documentation/builders/README.md | 6 + .../builders/components/power/onoff-shim.md | 36 ++++++ .../components/soundcards/hifiberry.md | 49 ++++++++ documentation/developers/docker.md | 2 +- installation/components/setup_hifiberry.sh | 119 ++++++++++++++++++ installation/includes/02_helpers.sh | 70 +++++++++-- installation/options/onboard_sound.sh | 27 ++++ 7 files changed, 296 insertions(+), 13 deletions(-) create mode 100644 documentation/builders/components/power/onoff-shim.md create mode 100644 documentation/builders/components/soundcards/hifiberry.md create mode 100755 installation/components/setup_hifiberry.sh create mode 100755 installation/options/onboard_sound.sh diff --git a/documentation/builders/README.md b/documentation/builders/README.md index f9fef397e..8ea5e0648 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -14,6 +14,12 @@ * [Card Database](./card-database.md) * [Troubleshooting](./troubleshooting.md) +## Components +* [Power](./components/power/) + * [OnOff SHIM for safe power on/off](./components/power/onoff-shim.md) +* [Soundcards](./components/soundcards/) + * [HiFiBerry Boards](./components/soundcards/hifiberry.md) + ## Advanced * [Bluetooth (and audio buttons)](./bluetooth-audio-buttons.md) diff --git a/documentation/builders/components/power/onoff-shim.md b/documentation/builders/components/power/onoff-shim.md new file mode 100644 index 000000000..b83ea6140 --- /dev/null +++ b/documentation/builders/components/power/onoff-shim.md @@ -0,0 +1,36 @@ +# OnOff SHIM by Pimorino + +The OnOff SHIM from Pimorino allows you to savely start and shutdown your Raspberry Pi through a button. While you can switch of your Phoniebox via an RFID Card (through an RPC command), it is difficult to switch it on again without cutting the physical power supply. + +## Installation + +To install the software, open a terminal and type the following command to run the one-line-installer. A reboot will be required once the installation is finished. + +> [!NOTE] +> The installation will ask you a few questions. You can safely answer with the default response. + +``` +curl https://get.pimoroni.com/onoffshim | bash +``` + +* [Source](https://shop.pimoroni.com/products/onoff-shim?variant=41102600138) + +## How to manually wire OnOff SHIM + +The OnOff SHIM comes with a 12-PIN header which needs soldering. If you want to spare some GPIO pins for other purposes, you can individually wire the OnOff SHIM with the Raspberry Pi. Below you can find a table of Pins to be connected. + +| Board pin name | Board pin | Physical RPi pin | RPi pin name | +|----------------|-----------|------------------|--------------| +| 3.3V | 1 | 1, 17 | 3V3 power | +| 5V | 2 | 2 | 5V power | +| 5V | 4 | 4 | 5V power | +| GND | 6 | 6, 9, 20, 25 | Ground | +| GPLCLK0 | 7 | 7 | GPIO4 | +| GPIO17 | 11 | 11 | GPIO17 | + +* More information can be found here: https://pinout.xyz/pinout/onoff_shim + +## Assembly options + +![](https://cdn.review-images.pimoroni.com/upload-b6276a310ccfbeae93a2d13ec19ab83b-1617096824.jpg?width=640) + diff --git a/documentation/builders/components/soundcards/hifiberry.md b/documentation/builders/components/soundcards/hifiberry.md new file mode 100644 index 000000000..1f19fa96d --- /dev/null +++ b/documentation/builders/components/soundcards/hifiberry.md @@ -0,0 +1,49 @@ +# HiFiBerry + +The installation script works for the most common set of HiFiBerry boards but also other "DAC" related sound cards like `I2S PCM5102A DAC`. + +## Automatic setup + +Run the following command to install any HiFiBerry board. Make sure you reboot your device afterwards. + +```bash +cd ~/RPi-Jukebox-RFID/installation/components +./setup_hifiberry.sh +``` + +If you know you HifiBerry Board identifier, you can run the script as a 1-liner as well + +```bash +./setup_hifiberry.sh enable hifiberry-dac +``` + +If you like to disable your HiFiberry Sound card and enable onboard sound, run the following command + + +```bash +./setup_hifiberry.sh disable +``` + +## Additional information + +If you like to understand what's happening under the hood, feel free to check the [install script](../../../../installation/components/setup_hifiberry.sh). + +The setup is based on [HiFiBerry's instructions](https://www.hifiberry.com/docs/software/configuring-linux-3-18-x/). + +## How to manually wire your HiFiBerry board + +Most HiFiBerry boards come with 40-pin header that you can directly attach to your Pi. This idles many GPIO pins that may be required for other inputs to be attached (like GPIO buttons or RFID). You can also connect your HiFiBerry board separately. The following table show cases the pins required. + +* [Raspberry Pi Pinout](https://github.com/raspberrypi/documentation/blob/develop/documentation/asciidoc/computers/os/using-gpio.adoc) + +| Board pin name | Board pin | Physical RPi pin | RPi pin name | +|----------------|-----------|------------------|--------------| +| 3.3V | 1 | 1, 17 | 3V3 power | +| 5V | 2 | 2, 4 | 5V power | +| GND | 6 | 6, 9, 20, 25 | Ground | +| PCM_CLK | 12 | 12 | GPIO18 | +| PCM_FS | 36 | 36 | GPIO19 | +| PCM_DIN | 38 | 38 | GPIO20 | +| PCM_DOUT | 40 | 40 | GPIO21 | + +You can find more information about manually wiring [here](https://forum-raspberrypi.de/forum/thread/44967-kein-ton-ueber-hifiberry-miniamp-am-rpi-4/?postID=401305#post401305). diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 3718373db..827178aca 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -15,7 +15,7 @@ need to adapt some of those commands to your needs. ## Prerequisites 1. Install required software: Docker, Compose and pulseaudio - * Check installations guide for [Mac](#mac), [Windows](#windows) or [Linux](#linux) + * Check installation guide for [Mac](#mac), [Windows](#windows) or [Linux](#linux) 2. Pull the Jukebox repository: diff --git a/installation/components/setup_hifiberry.sh b/installation/components/setup_hifiberry.sh new file mode 100755 index 000000000..dc2da8d6e --- /dev/null +++ b/installation/components/setup_hifiberry.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +# This script follows the official HiFiBerry documentation +# https://www.hifiberry.com/docs/software/configuring-linux-3-18-x/ + +source ../includes/02_helpers.sh + +script_name=$(basename "$0") +boot_config_path=$(get_boot_config_path) + +declare -A hifiberry_map=( + ["hifiberry-dac"]="DAC (HiFiBerry MiniAmp, I2S PCM5102A DAC)" + ["hifiberry-dacplus"]="HiFiBerry DAC+ Standard/Pro/Amp2" + ["hifiberry-dacplushd"]="HiFiBerry DAC2 HD" + ["hifiberry-dacplusadc"]="HiFiBerry DAC+ ADC" + ["hifiberry-dacplusadcpro"]="HiFiBerry DAC+ ADC Pro" + ["hifiberry-digi"]="HiFiBerry Digi+" + ["hifiberry-digi-pro"]="HiFiBerry Digi+ Pro" + ["hifiberry-amp"]="HiFiBerry Amp+ (not Amp2)" + ["hifiberry-amp3"]="HiFiBerry Amp3" +) + +example_usage() { + for key in "${!hifiberry_map[@]}"; do + description="${hifiberry_map[$key]}" + echo "$key) $description" + done + echo "Example usage: ./${script_name} enable hifiberry-dac" +} + +enable_hifiberry() { + echo "Enabling HiFiBerry board..." + grep -qxF "^dtoverlay=$1" "$boot_config_path" || echo "dtoverlay=$1" | sudo tee -a "$boot_config_path" > /dev/null + ./../options/onboard_sound.sh disable +} + +disable_hifiberry() { + echo "Removing existing HiFiBerry configuration..." + sudo sed -i '/^dtoverlay=hifiberry-/d' "$boot_config_path" + ./../options/onboard_sound.sh enable +} + +check_existing_hifiberry() { + existing_config=$(grep '^dtoverlay=hifiberry-' "$boot_config_path") + if [ ! -z "$existing_config" ]; then + if [ "$1" = "silent" ]; then + disable_hifiberry + return 0 + fi + + echo "Existing HiFiBerry configuration detected: $existing_config" + read -p "Do you want to proceed with a new configuration? This will remove the existing one. (Y/n): " choice + case $choice in + [nN][oO]|[nN]) + echo "Exiting without making changes."; + exit;; + *) + disable_hifiberry; + return 0;; + esac + fi +} + +# 1-line installation +if [ $# -ge 1 ]; then + if [[ "$1" != "enable" && "$1" != "disable" ]] || [[ "$1" == "enable" && -z "$2" ]]; then + echo "Error: Invalid arguments provided. +Usage: ./${script_name} +where can be 'enable' or 'disable'. + +The following board options exist:" + example_usage + exit 1 + fi + + if [ "$1" == "enable" ]; then + if [[ -v hifiberry_map["$2"] ]]; then + check_existing_hifiberry "silent" + enable_hifiberry "$2" + exit 1 + fi + + echo "'$2' is not a valid option. You can choose from:" + example_usage + exit 1 + fi + + disable_hifiberry + exit 1 +fi + +# Guided installation +board_count=${#hifiberry_map[@]} +counter=1 + +echo "Select your HiFiBerry board:" +for key in "${!hifiberry_map[@]}"; do + description="${hifiberry_map[$key]}" + echo "$counter) $description" + ((counter++)) +done +echo "0) Remove existing HiFiBerry configuration" + +read -p "Enter your choice (0-$board_count): " choice +case $choice in + [0]) + disable_hifiberry; + ;; + [1-$board_count]) + selected_board=$(get_key_by_item_number hifiberry_map "$choice") + check_existing_hifiberry + enable_hifiberry "$selected_board"; + ;; + *) + echo "Invalid selection. Exiting."; + exit 1;; +esac + +echo "Configuration complete. Please restart your device." diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index e9ca7640e..f2b60313b 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -13,6 +13,21 @@ show_slow_hardware_message() { fi } +# Get key by item number of associated array +get_key_by_item_number() { + local -n array="$1" + local item_number="$2" + local count=0 + + for key in "${!array[@]}"; do + ((count++)) + if [ "$count" -eq "$item_number" ]; then + echo "$key" + return + fi + done +} + # $1->start, $2->end calc_runtime_and_print() { runtime=$(($2-$1)) @@ -46,18 +61,49 @@ run_with_log_frame() { } get_architecture() { - local arch="" - if [ "$(uname -m)" = "armv7l" ]; then - arch="armv7" - elif [ "$(uname -m)" = "armv6l" ]; then - arch="armv6" - elif [ "$(uname -m)" = "aarch64" ]; then - arch="arm64" - else - arch="$(uname -m)" - fi - - echo $arch + local arch="" + if [ "$(uname -m)" = "armv7l" ]; then + arch="armv7" + elif [ "$(uname -m)" = "armv6l" ]; then + arch="armv6" + elif [ "$(uname -m)" = "aarch64" ]; then + arch="arm64" + else + arch="$(uname -m)" + fi + + echo $arch +} + +is_raspian() { + if [[ $( . /etc/os-release; printf '%s\n' "$ID"; ) == *"raspbian"* ]]; then + echo true + else + echo false + fi +} + +get_debian_version_number() { + source /etc/os-release + echo "$VERSION_ID" +} + +get_boot_config_path() { + if [ "$(is_raspian)" = true ]; then + local debian_version_number=$(get_debian_version_number) + + # Bullseye and lower + if [ "$debian_version_number" -le 11 ]; then + echo "/boot/config.txt" + # Bookworm and higher + elif [ "$debian_version_number" -ge 12 ]; then + echo "/boot/firmware/config.txt" + else + echo "unknown" + fi + else + echo "unknown" + fi } validate_url() { diff --git a/installation/options/onboard_sound.sh b/installation/options/onboard_sound.sh new file mode 100755 index 000000000..475a9161d --- /dev/null +++ b/installation/options/onboard_sound.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +source ../includes/02_helpers.sh +script_name=$(basename "$0") +boot_config_path=$(get_boot_config_path) + +if [ -z "$1" ] || { [ "$1" != "enable" ] && [ "$1" != "disable" ]; }; then + echo "Error: Invalid or no argument provided. +Usage: ./${script_name} + where can be 'enable' or 'disable'" + exit 1 +fi + +arg="$1" + +if [ "$arg" = "enable" ]; then + echo "Enabling Onboard Sound..." + sudo sed -i "s/^\(dtparam=\([^,]*,\)*\)audio=\(off\|false\|no\|0\)\(.*\)/\1audio=on\4/g" "$boot_config_path" + sudo sed -i '/^dtoverlay=vc4-fkms-v3d/{s/,audio=off//g;}' "$boot_config_path" + sudo sed -i '/^dtoverlay=vc4-kms-v3d/{s/,noaudio//g;}' "$boot_config_path" +elif [ "$arg" = "disable" ]; then + echo "Disabling Onboard Sound..." + sudo sed -i "s/^\(dtparam=\([^,]*,\)*\)audio=\(on\|true\|yes\|1\)\(.*\)/\1audio=off\4/g" "$boot_config_path" + sudo sed -i '/^dtoverlay=vc4-fkms-v3d/{s/,audio=off//g;s/$/,audio=off/g;}' "$boot_config_path" + sudo sed -i '/^dtoverlay=vc4-kms-v3d/{s/,noaudio//g;s/$/,noaudio/g;}' "$boot_config_path" +fi + +# TODO Test From 4f015dde6b348dadf0ebf5dae2de89ba9cf18046 Mon Sep 17 00:00:00 2001 From: s-martin Date: Tue, 16 Jan 2024 09:49:02 +0100 Subject: [PATCH 071/121] Create markdown docs from docstring in py files (#2181) * adding pydoc-markdown yml * add lazydocs * run lazydocs and pydoc-markdown parallel for testing * fix output path * add created docs to source control * fix the path to source code * change filter * revert last commit * use only pydoc-markdown * remove the test files from version control * rename script and add to pre-commit hook * change py file to test pre commit hook * modify py file again * test markdown formatting * updated docstring * use sphinx renderer * update markdown * convert links and formatting to markdown * make link to plugin docs * fix comment * fix wrong docstring * add more fixes to doc * update docstring-md * rename to README.md so github picks it up directly * fix formatting * improve docs * improve docs * fix docs * updte docstream md * fix formatting for md * fix formatting to md and check crossref * fix links * fix formatting for notes * fix links * revert wrong fix * fixed indentation * add generated docstring * Check for docstring in action * Try without request changes * try request changes * remove docstring check from action --- .githooks/pre-commit | 14 +- documentation/developers/README.md | 2 + documentation/developers/docstring/README.md | 5914 +++++++++++++++++ documentation/developers/known-issues.md | 2 + pydoc-markdown.yml | 13 + requirements.txt | 3 + run_docgeneration.sh | 15 + .../batt_mon_i2c_ads1015/__init__.py | 53 +- .../bluetooth_audio_buttons/__init__.py | 6 +- .../controls/common/evdev_listener.py | 6 +- .../components/gpio/gpioz/core/converter.py | 2 - .../gpio/gpioz/core/input_devices.py | 37 +- .../components/gpio/gpioz/core/mock.py | 3 +- .../gpio/gpioz/core/output_devices.py | 3 +- .../components/gpio/gpioz/plugin/__init__.py | 12 +- .../gpio/gpioz/plugin/connectivity.py | 36 +- .../components/hostif/linux/__init__.py | 3 +- src/jukebox/components/jingle/__init__.py | 11 +- src/jukebox/components/playermpd/__init__.py | 5 +- .../playermpd/playcontentcallback.py | 4 +- .../template_new_reader.py | 11 +- .../components/rfid/reader/__init__.py | 6 +- src/jukebox/components/rpc_command_alias.py | 2 +- src/jukebox/components/volume/__init__.py | 83 +- src/jukebox/jukebox/cfghandler.py | 5 +- src/jukebox/jukebox/playlistgenerator.py | 2 - src/jukebox/jukebox/plugs.py | 84 +- src/jukebox/jukebox/publishing/server.py | 100 +- src/jukebox/jukebox/rpc/server.py | 13 +- src/jukebox/jukebox/utils.py | 7 +- src/jukebox/misc/loggingext.py | 56 +- src/jukebox/run_configure_audio.py | 2 +- src/jukebox/run_jukebox.py | 4 +- src/jukebox/run_register_rfid_reader.py | 7 +- src/jukebox/run_rpc_tool.py | 2 +- 35 files changed, 6241 insertions(+), 287 deletions(-) create mode 100644 documentation/developers/docstring/README.md create mode 100644 pydoc-markdown.yml create mode 100755 run_docgeneration.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 95901bcda..b1b0c0348 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -7,7 +7,7 @@ # Checks # - flake8 on staged python files # Note: This only checks the modified files -# - docs build of if any python file or any doc file is staged +# - docs build of if any python file is staged # Note: This builds the entire documentation if a changed file goes into the documentation # # If there are problem with this script, commit may still be done with @@ -28,6 +28,18 @@ fi code=$(( flake8_code )) +doc_code=0 +if [[ -n $PY_FILES ]]; then + echo -e "\n**************************************************************" + echo -e "Modified Python source files. Generation markdown docs from docstring ... \n" + echo -e "**************************************************************\n" + ./run_docgeneration.sh -c + doc_code=$? + echo "pydoc_markdown return code: $doc_code" +fi + +code=$(( flake8_code + doc_code )) + if [[ code -gt 0 ]]; then echo -e "\n**************************************************************" echo -e "ERROR(s) during pre-commit checks. Aborting commit!" diff --git a/documentation/developers/README.md b/documentation/developers/README.md index cfa389713..f6ec65dc4 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -10,6 +10,8 @@ * [Jukebox Apps](./coreapps.md) * [Web App](./webapp.md) * [RFID Readers](./rfid) +* [Docstring API Docs (from py files)](./docstring/README.md) +* [Plugin Reference](./docstring/README.md#jukeboxplugs) * [Feature Status](./status.md) * [Known Issues](./known-issues.md) diff --git a/documentation/developers/docstring/README.md b/documentation/developers/docstring/README.md new file mode 100644 index 000000000..dde85b224 --- /dev/null +++ b/documentation/developers/docstring/README.md @@ -0,0 +1,5914 @@ +# None + +## Table of Contents + +* [run\_jukebox](#run_jukebox) +* [\_\_init\_\_](#__init__) +* [run\_register\_rfid\_reader](#run_register_rfid_reader) +* [run\_rpc\_tool](#run_rpc_tool) + * [get\_common\_beginning](#run_rpc_tool.get_common_beginning) + * [runcmd](#run_rpc_tool.runcmd) +* [run\_configure\_audio](#run_configure_audio) +* [run\_publicity\_sniffer](#run_publicity_sniffer) +* [misc](#misc) + * [recursive\_chmod](#misc.recursive_chmod) + * [flatten](#misc.flatten) + * [getattr\_hierarchical](#misc.getattr_hierarchical) +* [misc.inputminus](#misc.inputminus) + * [input\_int](#misc.inputminus.input_int) + * [input\_yesno](#misc.inputminus.input_yesno) +* [misc.loggingext](#misc.loggingext) + * [ColorFilter](#misc.loggingext.ColorFilter) + * [\_\_init\_\_](#misc.loggingext.ColorFilter.__init__) + * [PubStream](#misc.loggingext.PubStream) + * [PubStreamHandler](#misc.loggingext.PubStreamHandler) +* [misc.simplecolors](#misc.simplecolors) + * [Colors](#misc.simplecolors.Colors) + * [resolve](#misc.simplecolors.resolve) + * [print](#misc.simplecolors.print) +* [components](#components) +* [components.playermpd.playcontentcallback](#components.playermpd.playcontentcallback) + * [PlayContentCallbacks](#components.playermpd.playcontentcallback.PlayContentCallbacks) + * [register](#components.playermpd.playcontentcallback.PlayContentCallbacks.register) + * [run\_callbacks](#components.playermpd.playcontentcallback.PlayContentCallbacks.run_callbacks) +* [components.playermpd](#components.playermpd) + * [PlayerMPD](#components.playermpd.PlayerMPD) + * [mpd\_retry\_with\_mutex](#components.playermpd.PlayerMPD.mpd_retry_with_mutex) + * [pause](#components.playermpd.PlayerMPD.pause) + * [next](#components.playermpd.PlayerMPD.next) + * [rewind](#components.playermpd.PlayerMPD.rewind) + * [replay](#components.playermpd.PlayerMPD.replay) + * [toggle](#components.playermpd.PlayerMPD.toggle) + * [replay\_if\_stopped](#components.playermpd.PlayerMPD.replay_if_stopped) + * [play\_card](#components.playermpd.PlayerMPD.play_card) + * [get\_single\_coverart](#components.playermpd.PlayerMPD.get_single_coverart) + * [get\_folder\_content](#components.playermpd.PlayerMPD.get_folder_content) + * [play\_folder](#components.playermpd.PlayerMPD.play_folder) + * [play\_album](#components.playermpd.PlayerMPD.play_album) + * [get\_volume](#components.playermpd.PlayerMPD.get_volume) + * [set\_volume](#components.playermpd.PlayerMPD.set_volume) + * [play\_card\_callbacks](#components.playermpd.play_card_callbacks) +* [components.playermpd.coverart\_cache\_manager](#components.playermpd.coverart_cache_manager) +* [components.rpc\_command\_alias](#components.rpc_command_alias) +* [components.synchronisation.rfidcards](#components.synchronisation.rfidcards) + * [SyncRfidcards](#components.synchronisation.rfidcards.SyncRfidcards) + * [sync\_change\_on\_rfid\_scan](#components.synchronisation.rfidcards.SyncRfidcards.sync_change_on_rfid_scan) + * [sync\_all](#components.synchronisation.rfidcards.SyncRfidcards.sync_all) + * [sync\_card\_database](#components.synchronisation.rfidcards.SyncRfidcards.sync_card_database) + * [sync\_folder](#components.synchronisation.rfidcards.SyncRfidcards.sync_folder) +* [components.synchronisation](#components.synchronisation) +* [components.synchronisation.syncutils](#components.synchronisation.syncutils) +* [components.volume](#components.volume) + * [PulseMonitor](#components.volume.PulseMonitor) + * [SoundCardConnectCallbacks](#components.volume.PulseMonitor.SoundCardConnectCallbacks) + * [toggle\_on\_connect](#components.volume.PulseMonitor.toggle_on_connect) + * [toggle\_on\_connect](#components.volume.PulseMonitor.toggle_on_connect) + * [stop](#components.volume.PulseMonitor.stop) + * [run](#components.volume.PulseMonitor.run) + * [PulseVolumeControl](#components.volume.PulseVolumeControl) + * [OutputChangeCallbackHandler](#components.volume.PulseVolumeControl.OutputChangeCallbackHandler) + * [OutputVolumeCallbackHandler](#components.volume.PulseVolumeControl.OutputVolumeCallbackHandler) + * [toggle\_output](#components.volume.PulseVolumeControl.toggle_output) + * [get\_outputs](#components.volume.PulseVolumeControl.get_outputs) + * [publish\_volume](#components.volume.PulseVolumeControl.publish_volume) + * [publish\_outputs](#components.volume.PulseVolumeControl.publish_outputs) + * [set\_volume](#components.volume.PulseVolumeControl.set_volume) + * [get\_volume](#components.volume.PulseVolumeControl.get_volume) + * [change\_volume](#components.volume.PulseVolumeControl.change_volume) + * [get\_mute](#components.volume.PulseVolumeControl.get_mute) + * [mute](#components.volume.PulseVolumeControl.mute) + * [set\_output](#components.volume.PulseVolumeControl.set_output) + * [set\_soft\_max\_volume](#components.volume.PulseVolumeControl.set_soft_max_volume) + * [get\_soft\_max\_volume](#components.volume.PulseVolumeControl.get_soft_max_volume) + * [card\_list](#components.volume.PulseVolumeControl.card_list) +* [components.rfid](#components.rfid) +* [components.rfid.reader](#components.rfid.reader) + * [RfidCardDetectCallbacks](#components.rfid.reader.RfidCardDetectCallbacks) + * [register](#components.rfid.reader.RfidCardDetectCallbacks.register) + * [run\_callbacks](#components.rfid.reader.RfidCardDetectCallbacks.run_callbacks) + * [rfid\_card\_detect\_callbacks](#components.rfid.reader.rfid_card_detect_callbacks) + * [CardRemovalTimerClass](#components.rfid.reader.CardRemovalTimerClass) + * [\_\_init\_\_](#components.rfid.reader.CardRemovalTimerClass.__init__) +* [components.rfid.configure](#components.rfid.configure) + * [reader\_install\_dependencies](#components.rfid.configure.reader_install_dependencies) + * [reader\_load\_module](#components.rfid.configure.reader_load_module) + * [query\_user\_for\_reader](#components.rfid.configure.query_user_for_reader) + * [write\_config](#components.rfid.configure.write_config) +* [components.rfid.hardware.fake\_reader\_gui.fake\_reader\_gui](#components.rfid.hardware.fake_reader_gui.fake_reader_gui) +* [components.rfid.hardware.fake\_reader\_gui.description](#components.rfid.hardware.fake_reader_gui.description) +* [components.rfid.hardware.fake\_reader\_gui.gpioz\_gui\_addon](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon) + * [create\_inputs](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.create_inputs) + * [set\_state](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.set_state) + * [que\_set\_state](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.que_set_state) + * [fix\_state](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.fix_state) + * [pbox\_set\_state](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.pbox_set_state) + * [que\_set\_pbox](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.que_set_pbox) + * [create\_outputs](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.create_outputs) +* [components.rfid.hardware.generic\_usb.description](#components.rfid.hardware.generic_usb.description) +* [components.rfid.hardware.generic\_usb.generic\_usb](#components.rfid.hardware.generic_usb.generic_usb) +* [components.rfid.hardware.rc522\_spi.description](#components.rfid.hardware.rc522_spi.description) +* [components.rfid.hardware.rc522\_spi.rc522\_spi](#components.rfid.hardware.rc522_spi.rc522_spi) +* [components.rfid.hardware.pn532\_i2c\_py532.description](#components.rfid.hardware.pn532_i2c_py532.description) +* [components.rfid.hardware.pn532\_i2c\_py532.pn532\_i2c\_py532](#components.rfid.hardware.pn532_i2c_py532.pn532_i2c_py532) +* [components.rfid.hardware.rdm6300\_serial.rdm6300\_serial](#components.rfid.hardware.rdm6300_serial.rdm6300_serial) + * [decode](#components.rfid.hardware.rdm6300_serial.rdm6300_serial.decode) +* [components.rfid.hardware.rdm6300\_serial.description](#components.rfid.hardware.rdm6300_serial.description) +* [components.rfid.hardware.template\_new\_reader.description](#components.rfid.hardware.template_new_reader.description) +* [components.rfid.hardware.template\_new\_reader.template\_new\_reader](#components.rfid.hardware.template_new_reader.template_new_reader) + * [query\_customization](#components.rfid.hardware.template_new_reader.template_new_reader.query_customization) + * [ReaderClass](#components.rfid.hardware.template_new_reader.template_new_reader.ReaderClass) + * [\_\_init\_\_](#components.rfid.hardware.template_new_reader.template_new_reader.ReaderClass.__init__) + * [cleanup](#components.rfid.hardware.template_new_reader.template_new_reader.ReaderClass.cleanup) + * [stop](#components.rfid.hardware.template_new_reader.template_new_reader.ReaderClass.stop) + * [read\_card](#components.rfid.hardware.template_new_reader.template_new_reader.ReaderClass.read_card) +* [components.rfid.readerbase](#components.rfid.readerbase) + * [ReaderBaseClass](#components.rfid.readerbase.ReaderBaseClass) +* [components.rfid.cards](#components.rfid.cards) + * [list\_cards](#components.rfid.cards.list_cards) + * [delete\_card](#components.rfid.cards.delete_card) + * [register\_card](#components.rfid.cards.register_card) + * [register\_card\_custom](#components.rfid.cards.register_card_custom) + * [save\_card\_database](#components.rfid.cards.save_card_database) +* [components.rfid.cardutils](#components.rfid.cardutils) + * [decode\_card\_command](#components.rfid.cardutils.decode_card_command) + * [card\_command\_to\_str](#components.rfid.cardutils.card_command_to_str) + * [card\_to\_str](#components.rfid.cardutils.card_to_str) +* [components.publishing](#components.publishing) + * [republish](#components.publishing.republish) +* [components.player](#components.player) + * [MusicLibPath](#components.player.MusicLibPath) + * [get\_music\_library\_path](#components.player.get_music_library_path) +* [components.jingle](#components.jingle) + * [JingleFactory](#components.jingle.JingleFactory) + * [list](#components.jingle.JingleFactory.list) + * [play](#components.jingle.play) + * [play\_startup](#components.jingle.play_startup) + * [play\_shutdown](#components.jingle.play_shutdown) +* [components.jingle.alsawave](#components.jingle.alsawave) + * [AlsaWave](#components.jingle.alsawave.AlsaWave) + * [play](#components.jingle.alsawave.AlsaWave.play) + * [AlsaWaveBuilder](#components.jingle.alsawave.AlsaWaveBuilder) + * [\_\_init\_\_](#components.jingle.alsawave.AlsaWaveBuilder.__init__) +* [components.jingle.jinglemp3](#components.jingle.jinglemp3) + * [JingleMp3Play](#components.jingle.jinglemp3.JingleMp3Play) + * [play](#components.jingle.jinglemp3.JingleMp3Play.play) + * [JingleMp3PlayBuilder](#components.jingle.jinglemp3.JingleMp3PlayBuilder) + * [\_\_init\_\_](#components.jingle.jinglemp3.JingleMp3PlayBuilder.__init__) +* [components.hostif.linux](#components.hostif.linux) + * [shutdown](#components.hostif.linux.shutdown) + * [reboot](#components.hostif.linux.reboot) + * [jukebox\_is\_service](#components.hostif.linux.jukebox_is_service) + * [is\_any\_jukebox\_service\_active](#components.hostif.linux.is_any_jukebox_service_active) + * [restart\_service](#components.hostif.linux.restart_service) + * [get\_disk\_usage](#components.hostif.linux.get_disk_usage) + * [get\_cpu\_temperature](#components.hostif.linux.get_cpu_temperature) + * [get\_ip\_address](#components.hostif.linux.get_ip_address) + * [wlan\_disable\_power\_down](#components.hostif.linux.wlan_disable_power_down) + * [get\_autohotspot\_status](#components.hostif.linux.get_autohotspot_status) + * [stop\_autohotspot](#components.hostif.linux.stop_autohotspot) + * [start\_autohotspot](#components.hostif.linux.start_autohotspot) +* [components.misc](#components.misc) + * [rpc\_cmd\_help](#components.misc.rpc_cmd_help) + * [get\_all\_loaded\_packages](#components.misc.get_all_loaded_packages) + * [get\_all\_failed\_packages](#components.misc.get_all_failed_packages) + * [get\_start\_time](#components.misc.get_start_time) + * [get\_log](#components.misc.get_log) + * [get\_log\_debug](#components.misc.get_log_debug) + * [get\_log\_error](#components.misc.get_log_error) + * [get\_git\_state](#components.misc.get_git_state) + * [empty\_rpc\_call](#components.misc.empty_rpc_call) +* [components.controls](#components.controls) +* [components.controls.bluetooth\_audio\_buttons](#components.controls.bluetooth_audio_buttons) +* [components.controls.common.evdev\_listener](#components.controls.common.evdev_listener) + * [find\_device](#components.controls.common.evdev_listener.find_device) + * [EvDevKeyListener](#components.controls.common.evdev_listener.EvDevKeyListener) + * [\_\_init\_\_](#components.controls.common.evdev_listener.EvDevKeyListener.__init__) + * [run](#components.controls.common.evdev_listener.EvDevKeyListener.run) + * [start](#components.controls.common.evdev_listener.EvDevKeyListener.start) +* [components.battery\_monitor](#components.battery_monitor) +* [components.battery\_monitor.BatteryMonitorBase](#components.battery_monitor.BatteryMonitorBase) + * [pt1\_frac](#components.battery_monitor.BatteryMonitorBase.pt1_frac) + * [BattmonBase](#components.battery_monitor.BatteryMonitorBase.BattmonBase) +* [components.battery\_monitor.batt\_mon\_simulator](#components.battery_monitor.batt_mon_simulator) + * [battmon\_simulator](#components.battery_monitor.batt_mon_simulator.battmon_simulator) +* [components.battery\_monitor.batt\_mon\_i2c\_ads1015](#components.battery_monitor.batt_mon_i2c_ads1015) + * [battmon\_ads1015](#components.battery_monitor.batt_mon_i2c_ads1015.battmon_ads1015) +* [components.gpio.gpioz.plugin](#components.gpio.gpioz.plugin) + * [output\_devices](#components.gpio.gpioz.plugin.output_devices) + * [input\_devices](#components.gpio.gpioz.plugin.input_devices) + * [factory](#components.gpio.gpioz.plugin.factory) + * [IS\_ENABLED](#components.gpio.gpioz.plugin.IS_ENABLED) + * [IS\_MOCKED](#components.gpio.gpioz.plugin.IS_MOCKED) + * [CONFIG\_FILE](#components.gpio.gpioz.plugin.CONFIG_FILE) + * [ServiceIsRunningCallbacks](#components.gpio.gpioz.plugin.ServiceIsRunningCallbacks) + * [register](#components.gpio.gpioz.plugin.ServiceIsRunningCallbacks.register) + * [run\_callbacks](#components.gpio.gpioz.plugin.ServiceIsRunningCallbacks.run_callbacks) + * [service\_is\_running\_callbacks](#components.gpio.gpioz.plugin.service_is_running_callbacks) + * [build\_output\_device](#components.gpio.gpioz.plugin.build_output_device) + * [build\_input\_device](#components.gpio.gpioz.plugin.build_input_device) + * [get\_output](#components.gpio.gpioz.plugin.get_output) + * [on](#components.gpio.gpioz.plugin.on) + * [off](#components.gpio.gpioz.plugin.off) + * [set\_value](#components.gpio.gpioz.plugin.set_value) + * [flash](#components.gpio.gpioz.plugin.flash) +* [components.gpio.gpioz.plugin.connectivity](#components.gpio.gpioz.plugin.connectivity) + * [BUZZ\_TONE](#components.gpio.gpioz.plugin.connectivity.BUZZ_TONE) + * [register\_rfid\_callback](#components.gpio.gpioz.plugin.connectivity.register_rfid_callback) + * [register\_status\_led\_callback](#components.gpio.gpioz.plugin.connectivity.register_status_led_callback) + * [register\_status\_buzzer\_callback](#components.gpio.gpioz.plugin.connectivity.register_status_buzzer_callback) + * [register\_status\_tonalbuzzer\_callback](#components.gpio.gpioz.plugin.connectivity.register_status_tonalbuzzer_callback) + * [register\_audio\_sink\_change\_callback](#components.gpio.gpioz.plugin.connectivity.register_audio_sink_change_callback) + * [register\_volume\_led\_callback](#components.gpio.gpioz.plugin.connectivity.register_volume_led_callback) + * [register\_volume\_buzzer\_callback](#components.gpio.gpioz.plugin.connectivity.register_volume_buzzer_callback) + * [register\_volume\_rgbled\_callback](#components.gpio.gpioz.plugin.connectivity.register_volume_rgbled_callback) +* [components.gpio.gpioz.core.converter](#components.gpio.gpioz.core.converter) + * [ColorProperty](#components.gpio.gpioz.core.converter.ColorProperty) + * [VolumeToRGB](#components.gpio.gpioz.core.converter.VolumeToRGB) + * [\_\_call\_\_](#components.gpio.gpioz.core.converter.VolumeToRGB.__call__) + * [luminize](#components.gpio.gpioz.core.converter.VolumeToRGB.luminize) +* [components.gpio.gpioz.core.mock](#components.gpio.gpioz.core.mock) + * [patch\_mock\_outputs\_with\_callback](#components.gpio.gpioz.core.mock.patch_mock_outputs_with_callback) +* [components.gpio.gpioz.core.input\_devices](#components.gpio.gpioz.core.input_devices) + * [NameMixin](#components.gpio.gpioz.core.input_devices.NameMixin) + * [set\_rpc\_actions](#components.gpio.gpioz.core.input_devices.NameMixin.set_rpc_actions) + * [EventProperty](#components.gpio.gpioz.core.input_devices.EventProperty) + * [ButtonBase](#components.gpio.gpioz.core.input_devices.ButtonBase) + * [value](#components.gpio.gpioz.core.input_devices.ButtonBase.value) + * [pin](#components.gpio.gpioz.core.input_devices.ButtonBase.pin) + * [pull\_up](#components.gpio.gpioz.core.input_devices.ButtonBase.pull_up) + * [close](#components.gpio.gpioz.core.input_devices.ButtonBase.close) + * [Button](#components.gpio.gpioz.core.input_devices.Button) + * [on\_press](#components.gpio.gpioz.core.input_devices.Button.on_press) + * [LongPressButton](#components.gpio.gpioz.core.input_devices.LongPressButton) + * [on\_press](#components.gpio.gpioz.core.input_devices.LongPressButton.on_press) + * [ShortLongPressButton](#components.gpio.gpioz.core.input_devices.ShortLongPressButton) + * [RotaryEncoder](#components.gpio.gpioz.core.input_devices.RotaryEncoder) + * [pin\_a](#components.gpio.gpioz.core.input_devices.RotaryEncoder.pin_a) + * [pin\_b](#components.gpio.gpioz.core.input_devices.RotaryEncoder.pin_b) + * [on\_rotate\_clockwise](#components.gpio.gpioz.core.input_devices.RotaryEncoder.on_rotate_clockwise) + * [on\_rotate\_counter\_clockwise](#components.gpio.gpioz.core.input_devices.RotaryEncoder.on_rotate_counter_clockwise) + * [close](#components.gpio.gpioz.core.input_devices.RotaryEncoder.close) + * [TwinButton](#components.gpio.gpioz.core.input_devices.TwinButton) + * [StateVar](#components.gpio.gpioz.core.input_devices.TwinButton.StateVar) + * [close](#components.gpio.gpioz.core.input_devices.TwinButton.close) + * [value](#components.gpio.gpioz.core.input_devices.TwinButton.value) + * [is\_active](#components.gpio.gpioz.core.input_devices.TwinButton.is_active) +* [components.gpio.gpioz.core.output\_devices](#components.gpio.gpioz.core.output_devices) + * [LED](#components.gpio.gpioz.core.output_devices.LED) + * [flash](#components.gpio.gpioz.core.output_devices.LED.flash) + * [Buzzer](#components.gpio.gpioz.core.output_devices.Buzzer) + * [flash](#components.gpio.gpioz.core.output_devices.Buzzer.flash) + * [PWMLED](#components.gpio.gpioz.core.output_devices.PWMLED) + * [flash](#components.gpio.gpioz.core.output_devices.PWMLED.flash) + * [RGBLED](#components.gpio.gpioz.core.output_devices.RGBLED) + * [flash](#components.gpio.gpioz.core.output_devices.RGBLED.flash) + * [TonalBuzzer](#components.gpio.gpioz.core.output_devices.TonalBuzzer) + * [flash](#components.gpio.gpioz.core.output_devices.TonalBuzzer.flash) + * [melody](#components.gpio.gpioz.core.output_devices.TonalBuzzer.melody) +* [components.timers](#components.timers) +* [jukebox](#jukebox) +* [jukebox.callingback](#jukebox.callingback) + * [CallbackHandler](#jukebox.callingback.CallbackHandler) + * [register](#jukebox.callingback.CallbackHandler.register) + * [run\_callbacks](#jukebox.callingback.CallbackHandler.run_callbacks) + * [has\_callbacks](#jukebox.callingback.CallbackHandler.has_callbacks) +* [jukebox.version](#jukebox.version) + * [version](#jukebox.version.version) + * [version\_info](#jukebox.version.version_info) +* [jukebox.cfghandler](#jukebox.cfghandler) + * [ConfigHandler](#jukebox.cfghandler.ConfigHandler) + * [loaded\_from](#jukebox.cfghandler.ConfigHandler.loaded_from) + * [get](#jukebox.cfghandler.ConfigHandler.get) + * [setdefault](#jukebox.cfghandler.ConfigHandler.setdefault) + * [getn](#jukebox.cfghandler.ConfigHandler.getn) + * [setn](#jukebox.cfghandler.ConfigHandler.setn) + * [setndefault](#jukebox.cfghandler.ConfigHandler.setndefault) + * [config\_dict](#jukebox.cfghandler.ConfigHandler.config_dict) + * [is\_modified](#jukebox.cfghandler.ConfigHandler.is_modified) + * [clear\_modified](#jukebox.cfghandler.ConfigHandler.clear_modified) + * [save](#jukebox.cfghandler.ConfigHandler.save) + * [load](#jukebox.cfghandler.ConfigHandler.load) + * [get\_handler](#jukebox.cfghandler.get_handler) + * [load\_yaml](#jukebox.cfghandler.load_yaml) + * [write\_yaml](#jukebox.cfghandler.write_yaml) +* [jukebox.playlistgenerator](#jukebox.playlistgenerator) + * [TYPE\_DECODE](#jukebox.playlistgenerator.TYPE_DECODE) + * [PlaylistCollector](#jukebox.playlistgenerator.PlaylistCollector) + * [\_\_init\_\_](#jukebox.playlistgenerator.PlaylistCollector.__init__) + * [set\_exclusion\_endings](#jukebox.playlistgenerator.PlaylistCollector.set_exclusion_endings) + * [get\_directory\_content](#jukebox.playlistgenerator.PlaylistCollector.get_directory_content) + * [parse](#jukebox.playlistgenerator.PlaylistCollector.parse) +* [jukebox.NvManager](#jukebox.NvManager) +* [jukebox.publishing](#jukebox.publishing) + * [get\_publisher](#jukebox.publishing.get_publisher) +* [jukebox.publishing.subscriber](#jukebox.publishing.subscriber) +* [jukebox.publishing.server](#jukebox.publishing.server) + * [PublishServer](#jukebox.publishing.server.PublishServer) + * [run](#jukebox.publishing.server.PublishServer.run) + * [handle\_message](#jukebox.publishing.server.PublishServer.handle_message) + * [handle\_subscription](#jukebox.publishing.server.PublishServer.handle_subscription) + * [Publisher](#jukebox.publishing.server.Publisher) + * [\_\_init\_\_](#jukebox.publishing.server.Publisher.__init__) + * [send](#jukebox.publishing.server.Publisher.send) + * [revoke](#jukebox.publishing.server.Publisher.revoke) + * [resend](#jukebox.publishing.server.Publisher.resend) + * [close\_server](#jukebox.publishing.server.Publisher.close_server) +* [jukebox.daemon](#jukebox.daemon) + * [log\_active\_threads](#jukebox.daemon.log_active_threads) + * [JukeBox](#jukebox.daemon.JukeBox) + * [signal\_handler](#jukebox.daemon.JukeBox.signal_handler) +* [jukebox.plugs](#jukebox.plugs) + * [PluginPackageClass](#jukebox.plugs.PluginPackageClass) + * [register](#jukebox.plugs.register) + * [register](#jukebox.plugs.register) + * [register](#jukebox.plugs.register) + * [register](#jukebox.plugs.register) + * [register](#jukebox.plugs.register) + * [register](#jukebox.plugs.register) + * [tag](#jukebox.plugs.tag) + * [initialize](#jukebox.plugs.initialize) + * [finalize](#jukebox.plugs.finalize) + * [atexit](#jukebox.plugs.atexit) + * [load](#jukebox.plugs.load) + * [load\_all\_named](#jukebox.plugs.load_all_named) + * [load\_all\_unnamed](#jukebox.plugs.load_all_unnamed) + * [load\_all\_finalize](#jukebox.plugs.load_all_finalize) + * [close\_down](#jukebox.plugs.close_down) + * [call](#jukebox.plugs.call) + * [call\_ignore\_errors](#jukebox.plugs.call_ignore_errors) + * [exists](#jukebox.plugs.exists) + * [get](#jukebox.plugs.get) + * [loaded\_as](#jukebox.plugs.loaded_as) + * [delete](#jukebox.plugs.delete) + * [dump\_plugins](#jukebox.plugs.dump_plugins) + * [summarize](#jukebox.plugs.summarize) + * [generate\_help\_rst](#jukebox.plugs.generate_help_rst) + * [get\_all\_loaded\_packages](#jukebox.plugs.get_all_loaded_packages) + * [get\_all\_failed\_packages](#jukebox.plugs.get_all_failed_packages) +* [jukebox.speaking\_text](#jukebox.speaking_text) +* [jukebox.multitimer](#jukebox.multitimer) + * [MultiTimer](#jukebox.multitimer.MultiTimer) + * [cancel](#jukebox.multitimer.MultiTimer.cancel) + * [GenericTimerClass](#jukebox.multitimer.GenericTimerClass) + * [\_\_init\_\_](#jukebox.multitimer.GenericTimerClass.__init__) + * [start](#jukebox.multitimer.GenericTimerClass.start) + * [cancel](#jukebox.multitimer.GenericTimerClass.cancel) + * [toggle](#jukebox.multitimer.GenericTimerClass.toggle) + * [trigger](#jukebox.multitimer.GenericTimerClass.trigger) + * [is\_alive](#jukebox.multitimer.GenericTimerClass.is_alive) + * [get\_timeout](#jukebox.multitimer.GenericTimerClass.get_timeout) + * [set\_timeout](#jukebox.multitimer.GenericTimerClass.set_timeout) + * [publish](#jukebox.multitimer.GenericTimerClass.publish) + * [get\_state](#jukebox.multitimer.GenericTimerClass.get_state) + * [GenericEndlessTimerClass](#jukebox.multitimer.GenericEndlessTimerClass) + * [GenericMultiTimerClass](#jukebox.multitimer.GenericMultiTimerClass) + * [\_\_init\_\_](#jukebox.multitimer.GenericMultiTimerClass.__init__) + * [start](#jukebox.multitimer.GenericMultiTimerClass.start) +* [jukebox.utils](#jukebox.utils) + * [decode\_rpc\_call](#jukebox.utils.decode_rpc_call) + * [decode\_rpc\_command](#jukebox.utils.decode_rpc_command) + * [decode\_and\_call\_rpc\_command](#jukebox.utils.decode_and_call_rpc_command) + * [bind\_rpc\_command](#jukebox.utils.bind_rpc_command) + * [rpc\_call\_to\_str](#jukebox.utils.rpc_call_to_str) + * [generate\_cmd\_alias\_rst](#jukebox.utils.generate_cmd_alias_rst) + * [generate\_cmd\_alias\_reference](#jukebox.utils.generate_cmd_alias_reference) + * [get\_git\_state](#jukebox.utils.get_git_state) +* [jukebox.rpc](#jukebox.rpc) +* [jukebox.rpc.client](#jukebox.rpc.client) +* [jukebox.rpc.server](#jukebox.rpc.server) + * [RpcServer](#jukebox.rpc.server.RpcServer) + * [\_\_init\_\_](#jukebox.rpc.server.RpcServer.__init__) + * [run](#jukebox.rpc.server.RpcServer.run) + + + +# run\_jukebox + +This is the main app and starts the Jukebox Core. + +Usually this runs as a service, which is started automatically after boot-up. At times, it may be necessary to restart +the service. +For example after a configuration change. Not all configuration changes can be applied on-the-fly. +See [Jukebox Configuration](../../builders/configuration.md#jukebox-configuration). + +For debugging, it is usually desirable to run the Jukebox directly from the console rather than +as service. This gives direct logging info in the console and allows changing command line parameters. +See [Troubleshooting](../../builders/troubleshooting.md). + + + + +# \_\_init\_\_ + + + +# run\_register\_rfid\_reader + +Setup tool to configure the RFID Readers. + +Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change +the settings. For more information see [RFID Readers](../rfid/README.md). + +> [!NOTE] +> This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). +> Any manual modifications to the settings will have to be re-applied + + + + +# run\_rpc\_tool + +Command Line Interface to the Jukebox RPC Server + +A command line tool for sending RPC commands to the running jukebox app. +This uses the same interface as the WebUI. Can be used for additional control +or for debugging. + +The tool features auto-completion and command history. + +The list of available commands is fetched from the running Jukebox service. + +.. todo: + - kwargs support + + + + +#### get\_common\_beginning + +```python +def get_common_beginning(strings) +``` + +Return the strings that are common to the beginning of each string in the strings list. + + + + +#### runcmd + +```python +def runcmd(cmd) +``` + +Just run a command. + +Right now duplicates more or less main() +:todo remove duplication of code + + + + +# run\_configure\_audio + +Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. + +Will also setup equalizer and mono down mixer in the pulseaudio config file. + +Run this once after installation. Can be re-run at any time to change the settings. +For more information see [Audio Configuration](../../builders/audio.md#audio-configuration). + + + + +# run\_publicity\_sniffer + +A command line tool that monitors all messages being sent out from the + +Jukebox via the publishing interface. Received messages are printed in the console. +Mainly used for debugging. + + + + +# misc + + + +#### recursive\_chmod + +```python +def recursive_chmod(path, mode_files, mode_dirs) +``` + +Recursively change folder and file permissions + +mode_files/mode dirs can be given in octal notation e.g. 0o777 +flags from the stats module. + +Reference: https://docs.python.org/3/library/os.html#os.chmod + + + + +#### flatten + +```python +def flatten(iterable) +``` + +Flatten all levels of hierarchy in nested iterables + + + + +#### getattr\_hierarchical + +```python +def getattr_hierarchical(obj: Any, name: str) -> Any +``` + +Like the builtin getattr, but descends though the hierarchy levels + + + + +# misc.inputminus + +Zero 3rd-party dependency module for user prompting + +Yes, there are modules out there to do the same and they have more features. +However, this is low-complexity and has zero dependencies + + + + +#### input\_int + +```python +def input_int(prompt, + blank=None, + min=None, + max=None, + prompt_color=None, + prompt_hint=False) -> int +``` + +Request an integer input from user + +**Arguments**: + +- `prompt`: The prompt to display +- `blank`: Value to return when user just hits enter. Leave at None, if blank is invalid +- `min`: Minimum valid integer value (None disables this check) +- `max`: Maximum valid integer value (None disables this check) +- `prompt_color`: Color of the prompt. Color will be reset at end of prompt +- `prompt_hint`: Append a 'hint' with [min...max, default=xx] to end of prompt + +**Returns**: + +integer value read from user input + + + +#### input\_yesno + +```python +def input_yesno(prompt, + blank=None, + prompt_color=None, + prompt_hint=False) -> bool +``` + +Request a yes / no choice from user + +Accepts multiple input for true/false and is case insensitive + +**Arguments**: + +- `prompt`: The prompt to display +- `blank`: Value to return when user just hits enter. Leave at None, if blank is invalid +- `prompt_color`: Color of the prompt. Color will be reset at end of prompt +- `prompt_hint`: Append a 'hint' with [y/n] to end of prompt. Default choice will be capitalized + +**Returns**: + +boolean value read from user input + + + +# misc.loggingext + +## Logger + +We use a hierarchical Logger structure based on pythons logging module. It can be finely configured with a yaml file. + +The top-level logger is called 'jb' (to make it short). In any module you may simple create a child-logger at any hierarchy +level below 'jb'. It will inherit settings from it's parent logger unless otherwise configured in the yaml file. +Hierarchy separator is the '.'. If the logger already exits, getLogger will return a reference to the same, else it will be +created on the spot. + +Example: How to get logger and log away at your heart's content: + + >>> import logging + >>> logger = logging.getLogger('jb.awesome_module') + >>> logger.info('Started general awesomeness aura') + +Example: YAML snippet, setting WARNING as default level everywhere and DEBUG for jb.awesome_module: + + loggers: + jb: + level: WARNING + handlers: [console, debug_file_handler, error_file_handler] + propagate: no + jb.awesome_module: + level: DEBUG + + +> [!NOTE] +> The name (and hierarchy path) of the logger can be arbitrary and must not necessarily match the module name (still makes +> sense). +> There can be multiple loggers per module, e.g. for special classes, to further control the amount of log output + + + + +## ColorFilter Objects + +```python +class ColorFilter(logging.Filter) +``` + +This filter adds colors to the logger + +It adds all colors from simplecolors by using the color name as new keyword, +i.e. use %(colorname)c or {colorname} in the formatter string + +It also adds the keyword {levelnameColored} which is an auto-colored drop-in replacement +for the levelname depending on severity. + +Don't forget to {reset} the color settings at the end of the string. + + + + +#### \_\_init\_\_ + +```python +def __init__(enable=True, color_levelname=True) +``` + +**Arguments**: + +- `enable`: Enable the coloring +- `color_levelname`: Enable auto-coloring when using the levelname keyword + + + +## PubStream Objects + +```python +class PubStream() +``` + +Stream handler wrapper around the publisher for logging.StreamHandler + +Allows logging to send all log information (based on logging configuration) +to the Publisher. + +> [!CAUTION] +> This can lead to recursions! +> Recursions come up when +> * Publish.send / PublishServer.send also emits logs, which cause a another send, which emits a log, +> which causes a send, ..... +> * Publisher initialization emits logs, which need a Publisher instance to send logs + +> [!IMPORTANT] +> To avoid endless recursions: The creation of a Publisher MUST NOT generate any log messages! Nor any of the +> functions in the send-function stack! + + + + +## PubStreamHandler Objects + +```python +class PubStreamHandler(logging.StreamHandler) +``` + +Wrapper for logging.StreamHandler with stream = PubStream + +This serves one purpose: In logger.yaml custom handlers +can be configured (which are automatically instantiated). +Using this Handler, we can output to PubStream whithout +support code to instantiate PubStream keeping this file generic + + + + +# misc.simplecolors + +Zero 3rd-party dependency module to add colors to unix terminal output + +Yes, there are modules out there to do the same and they have more features. +However, this is low-complexity and has zero dependencies + + + + +## Colors Objects + +```python +class Colors() +``` + +Container class for all the colors as constants + + + + +#### resolve + +```python +def resolve(color_name: str) +``` + +Resolve a color name into the respective color constant + +**Arguments**: + +- `color_name`: Name of the color + +**Returns**: + +color constant + + + +#### print + +```python +def print(color: Colors, + *values, + sep=' ', + end='\n', + file=sys.stdout, + flush=False) +``` + +Drop-in replacement for print with color choice and auto color reset for convenience + +Use just as a regular print function, but with first parameter as color + + + + +# components + + + +# components.playermpd.playcontentcallback + + + +## PlayContentCallbacks Objects + +```python +class PlayContentCallbacks(Generic[STATE], CallbackHandler) +``` + +Callbacks are executed in various play functions + + + + +#### register + +```python +def register(func: Callable[[str, STATE], None]) +``` + +Add a new callback function :attr:`func`. + +Callback signature is + +.. py:function:: func(folder: str, state: STATE) + :noindex: + +**Arguments**: + +- `folder`: relativ path to folder to play +- `state`: indicator of the state inside the calling + + + +#### run\_callbacks + +```python +def run_callbacks(folder: str, state: STATE) +``` + + + + + +# components.playermpd + +Package for interfacing with the MPD Music Player Daemon + +Status information in three topics +1) Player Status: published only on change + This is a subset of the MPD status (and not the full MPD status) ?? + - folder + - song + - volume (volume is published only via player status, and not separatly to avoid too many Threads) + - ... +2) Elapsed time: published every 250 ms, unless constant + - elapsed +3) Folder Config: published only on change + This belongs to the folder being played + Publish: + - random, resume, single, loop + On save store this information: + Contains the information for resume functionality of each folder + - random, resume, single, loop + - if resume: + - current song, elapsed + - what is PLAYSTATUS for? + When to save + - on stop + Angstsave: + - on pause (only if box get turned off without proper shutdown - else stop gets implicitly called) + - on status change of random, resume, single, loop (for resume omit current status if currently playing- this has now meaning) + Load checks: + - if resume, but no song, elapsed -> log error and start from the beginning + +Status storing: + - Folder config for each folder (see above) + - Information to restart last folder playback, which is: + - last_folder -> folder_on_close + - song, elapsed + - random, resume, single, loop + - if resume is enabled, after start we need to set last_played_folder, such that card swipe is detected as second swipe?! + on the other hand: if resume is enabled, this is also saved to folder.config -> and that is checked by play card + +Internal status + - last played folder: Needed to detect second swipe + + +Saving {'player_status': {'last_played_folder': 'TraumfaengerStarkeLieder', 'CURRENTSONGPOS': '0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3'}, +'audio_folder_status': +{'TraumfaengerStarkeLieder': {'ELAPSED': '1.0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3', 'CURRENTSONGPOS': '0', 'PLAYSTATUS': 'stop', 'RESUME': 'OFF', 'SHUFFLE': 'OFF', 'LOOP': 'OFF', 'SINGLE': 'OFF'}, +'Giraffenaffen': {'ELAPSED': '1.0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3', 'CURRENTSONGPOS': '0', 'PLAYSTATUS': 'play', 'RESUME': 'OFF', 'SHUFFLE': 'OFF', 'LOOP': 'OFF', 'SINGLE': 'OFF'}}} + +References: +https://github.com/Mic92/python-mpd2 +https://python-mpd2.readthedocs.io/en/latest/topics/commands.html +https://mpd.readthedocs.io/en/latest/protocol.html + +sudo -u mpd speaker-test -t wav -c 2 + + + + +## PlayerMPD Objects + +```python +class PlayerMPD() +``` + +Interface to MPD Music Player Daemon + + + + +#### mpd\_retry\_with\_mutex + +```python +def mpd_retry_with_mutex(mpd_cmd, *args) +``` + +This method adds thread saftey for acceses to mpd via a mutex lock, + +it shall be used for each access to mpd to ensure thread safety +In case of a communication error the connection will be reestablished and the pending command will be repeated 2 times + +I think this should be refactored to a decorator + + + + +#### pause + +```python +@plugs.tag +def pause(state: int = 1) +``` + +Enforce pause to state (1: pause, 0: resume) + +This is what you want as card removal action: pause the playback, so it can be resumed when card is placed +on the reader again. What happens on re-placement depends on configured second swipe option + + + + +#### next + +```python +@plugs.tag +def next() +``` + +Play next track in current playlist + + + + +#### rewind + +```python +@plugs.tag +def rewind() +``` + +Re-start current playlist from first track + +Note: Will not re-read folder config, but leave settings untouched + + + + +#### replay + +```python +@plugs.tag +def replay() +``` + +Re-start playing the last-played folder + +Will reset settings to folder config + + + + +#### toggle + +```python +@plugs.tag +def toggle() +``` + +Toggle pause state, i.e. do a pause / resume depending on current state + + + + +#### replay\_if\_stopped + +```python +@plugs.tag +def replay_if_stopped() +``` + +Re-start playing the last-played folder unless playlist is still playing + +> [!NOTE] +> To me this seems much like the behaviour of play, +> but we keep it as it is specifically implemented in box 2.X + + + + +#### play\_card + +```python +@plugs.tag +def play_card(folder: str, recursive: bool = False) +``` + +Main entry point for trigger music playing from RFID reader. Decodes second swipe options before playing folder content + +Checks for second (or multiple) trigger of the same folder and calls first swipe / second swipe action +accordingly. + +**Arguments**: + +- `folder`: Folder path relative to music library path +- `recursive`: Add folder recursively + + + +#### get\_single\_coverart + +```python +@plugs.tag +def get_single_coverart(song_url) +``` + +Saves the album art image to a cache and returns the filename. + + + + +#### get\_folder\_content + +```python +@plugs.tag +def get_folder_content(folder: str) +``` + +Get the folder content as content list with meta-information. Depth is always 1. + +Call repeatedly to descend in hierarchy + +**Arguments**: + +- `folder`: Folder path relative to music library path + + + +#### play\_folder + +```python +@plugs.tag +def play_folder(folder: str, recursive: bool = False) -> None +``` + +Playback a music folder. + +Folder content is added to the playlist as described by :mod:`jukebox.playlistgenerator`. +The playlist is cleared first. + +**Arguments**: + +- `folder`: Folder path relative to music library path +- `recursive`: Add folder recursively + + + +#### play\_album + +```python +@plugs.tag +def play_album(albumartist: str, album: str) +``` + +Playback a album found in MPD database. + +All album songs are added to the playlist +The playlist is cleared first. + +**Arguments**: + +- `albumartist`: Artist of the Album provided by MPD database +- `album`: Album name provided by MPD database + + + +#### get\_volume + +```python +def get_volume() +``` + +Get the current volume + +For volume control do not use directly, but use through the plugin 'volume', +as the user may have configured a volume control manager other than MPD + + + + +#### set\_volume + +```python +def set_volume(volume) +``` + +Set the volume + +For volume control do not use directly, but use through the plugin 'volume', +as the user may have configured a volume control manager other than MPD + + + + +#### play\_card\_callbacks + +Callback handler instance for play_card events. + +- is executed when play_card function is called +States: +- See :class:`PlayCardState` +See :class:`PlayContentCallbacks` + + + + +# components.playermpd.coverart\_cache\_manager + + + +# components.rpc\_command\_alias + +This file provides definitions for RPC command aliases + +See [RPC Commands](../../builders/rpc-commands.md) + + + + +# components.synchronisation.rfidcards + +Handles the synchronisation of RFID cards (audiofolder and card database entries). + +sync-all -> all card entries and audiofolders are synced from remote including deletions +sync-on-scan -> only the entry and audiofolder for the cardId will be synced from remote. + Deletions are only performed on files and subfolder inside the audiofolder. + A deletion of the audiofolder itself on remote side will not be propagated. + +card database: +On synchronisation the remote file will not be synced with the original cards database, but rather a local copy. +If a full sync is performed, the state is written back to the original file. +If a single card sync is performed, only the state of the specific cardId is updated in the original file. +This is done to allow to play audio offline. +Otherwise we would also update other cardIds where the audiofolders have not been synced yet. +The local copy is kept to reduce unnecessary syncing. + + + + +## SyncRfidcards Objects + +```python +class SyncRfidcards() +``` + +Control class for sync RFID cards functionality + + + + +#### sync\_change\_on\_rfid\_scan + +```python +@plugs.tag +def sync_change_on_rfid_scan(option: str = 'toggle') -> None +``` + +Change activation of 'on_rfid_scan_enabled' + +**Arguments**: + +- `option`: Must be one of 'enable', 'disable', 'toggle' + + + +#### sync\_all + +```python +@plugs.tag +def sync_all() -> bool +``` + +Sync all audiofolder and cardids from the remote server. + +Removes local entries not existing at the remote server. + + + + +#### sync\_card\_database + +```python +@plugs.tag +def sync_card_database(card_id: str) -> bool +``` + +Sync the card database from the remote server, if existing. + +If card_id is provided only this entry is updated. + +**Arguments**: + +- `card_id`: The cardid to update + + + +#### sync\_folder + +```python +@plugs.tag +def sync_folder(folder: str) -> bool +``` + +Sync the folder from the remote server, if existing + +**Arguments**: + +- `folder`: Folder path relative to music library path + + + +# components.synchronisation + + + +# components.synchronisation.syncutils + + + +# components.volume + +PulseAudio Volume Control Plugin Package + +## Features + +* Volume Control +* Two outputs +* Watcher thread on volume / output change + +## Publishes + +* volume.level +* volume.sink + +## PulseAudio References + + + +Check fallback device (on device de-connect): + + $ pacmd list-sinks | grep -e 'name:' -e 'index' + + +## Integration + +Pulse Audio runs as a user process. Processes who want to communicate / stream to it +must also run as a user process. + +This means must also run as user process, as described in +[Music Player Daemon](../../builders/system.md#music-player-daemon-mpd). + +## Misc + +PulseAudio may switch the sink automatically to a connecting bluetooth device depending on the loaded module +with name module-switch-on-connect. On RaspianOS Bullseye, this module is not part of the default configuration +in ``/usr/pulse/default.pa``. So, we don't need to worry about it. +If the module gets loaded it conflicts with the toggle on connect and the selected primary / secondary outputs +from the Jukebox. Remove it from the configuration! + + ### Use hot-plugged devices like Bluetooth or USB automatically (LP: `1702794`) + ### not available on PI? + .ifexists module-switch-on-connect.so + load-module module-switch-on-connect + .endif + +## Why PulseAudio? + +The audio configuration of the system is one of those topics, +which has a myriad of options and possibilities. Every system is different and PulseAudio unifies these and +makes our life easier. Besides, it is only option to support Bluetooth at the moment. + +## Callbacks: + +The following callbacks are provided. Register callbacks with these adder functions (see their documentation for details): + +1. :func:`add_on_connect_callback` +2. :func:`add_on_output_change_callbacks` +3. :func:`add_on_volume_change_callback` + + + + +## PulseMonitor Objects + +```python +class PulseMonitor(threading.Thread) +``` + +A thread for monitoring and interacting with the Pulse Lib via pulsectrl + +Whenever we want to access pulsectl, we need to exit the event listen loop. +This is handled by the context manager. It stops the event loop and returns +the pulsectl instance to be used (it does no return the monitor thread itself!) + +The context manager also locks the module to ensure proper thread sequencing, +as only a single thread may work with pulsectl at any time. Currently, an RLock is +used, even if it may not be necessary + + + + +## SoundCardConnectCallbacks Objects + +```python +class SoundCardConnectCallbacks(CallbackHandler) +``` + +Callbacks are executed when + +* new sound card gets connected + + + + +#### register + +```python +def register(func: Callable[[str, str], None]) +``` + +Add a new callback function :attr:`func`. + +Callback signature is + +.. py:function:: func(card_driver: str, device_name: str) + :noindex: + +**Arguments**: + +- `card_driver`: The PulseAudio card driver module, +e.g. :data:`module-bluez5-device.c` or :data:`module-alsa-card.c` +- `device_name`: The sound card device name as reported +in device properties + + + +#### run\_callbacks + +```python +def run_callbacks(sink_name, alias, sink_index, error_state) +``` + + + + + +#### toggle\_on\_connect + +```python +@property +def toggle_on_connect() +``` + +Returns :data:`True` if the sound card shall be changed when a new card connects/disconnects. Setting this + +property changes the behavior. + +> [!NOTE] +> A new card is always assumed to be the secondary device from the audio configuration. +> At the moment there is no check it actually is the configured device. This means any new +> device connection will initiate the toggle. This, however, is no real issue as the RPi's audio +> system will be relatively stable once setup + + + + +#### toggle\_on\_connect + +```python +@toggle_on_connect.setter +def toggle_on_connect(state=True) +``` + +Toggle Doc 2 + + + + +#### stop + +```python +def stop() +``` + +Stop the pulse monitor thread + + + + +#### run + +```python +def run() -> None +``` + +Starts the pulse monitor thread + + + + +## PulseVolumeControl Objects + +```python +class PulseVolumeControl() +``` + +Volume control manager for PulseAudio + +When accessing the pulse library, it needs to be put into a special +state. Which is ensured by the context manager + + with pulse_monitor as pulse ... + + +All private functions starting with `_function_name` assume that this is ensured by +the calling function. All user functions acquire proper context! + + + + +## OutputChangeCallbackHandler Objects + +```python +class OutputChangeCallbackHandler(CallbackHandler) +``` + +Callbacks are executed when + +* audio sink is changed + + + + +#### register + +```python +def register(func: Callable[[str, str, int, int], None]) +``` + +Add a new callback function :attr:`func`. + +Parameters always give the valid audio sink. That means, if an error +occurred, all parameters are valid. + +Callback signature is + +.. py:function:: func(sink_name: str, alias: str, sink_index: int, error_state: int) + :noindex: + +**Arguments**: + +- `sink_name`: PulseAudio's sink name +- `alias`: The alias for :attr:`sink_name` +- `sink_index`: The index of the sink in the configuration list +- `error_state`: 1 if there was an attempt to change the output +but an error occurred. Above parameters always give the now valid sink! +If a sink change is successful, it is 0. + + + +#### run\_callbacks + +```python +def run_callbacks(sink_name, alias, sink_index, error_state) +``` + + + + + +## OutputVolumeCallbackHandler Objects + +```python +class OutputVolumeCallbackHandler(CallbackHandler) +``` + +Callbacks are executed when + +* audio volume level is changed + + + + +#### register + +```python +def register(func: Callable[[int, bool, bool], None]) +``` + +Add a new callback function :attr:`func`. + +Callback signature is + +.. py:function:: func(volume: int, is_min: bool, is_max: bool) + :noindex: + +**Arguments**: + +- `volume`: Volume level +- `is_min`: 1, if volume level is minimum, else 0 +- `is_max`: 1, if volume level is maximum, else 0 + + + +#### run\_callbacks + +```python +def run_callbacks(sink_name, alias, sink_index, error_state) +``` + + + + + +#### toggle\_output + +```python +@plugin.tag +def toggle_output() +``` + +Toggle the audio output sink + + + + +#### get\_outputs + +```python +@plugin.tag +def get_outputs() +``` + +Get current output and list of outputs + + + + +#### publish\_volume + +```python +@plugin.tag +def publish_volume() +``` + +Publish (volume, mute) + + + + +#### publish\_outputs + +```python +@plugin.tag +def publish_outputs() +``` + +Publish current output and list of outputs + + + + +#### set\_volume + +```python +@plugin.tag +def set_volume(volume: int) +``` + +Set the volume (0-100) for the currently active output + + + + +#### get\_volume + +```python +@plugin.tag +def get_volume() +``` + +Get the volume + + + + +#### change\_volume + +```python +@plugin.tag +def change_volume(step: int) +``` + +Increase/decrease the volume by step for the currently active output + + + + +#### get\_mute + +```python +@plugin.tag +def get_mute() +``` + +Return mute status for the currently active output + + + + +#### mute + +```python +@plugin.tag +def mute(mute=True) +``` + +Set mute status for the currently active output + + + + +#### set\_output + +```python +@plugin.tag +def set_output(sink_index: int) +``` + +Set the active output (sink_index = 0: primary, 1: secondary) + + + + +#### set\_soft\_max\_volume + +```python +@plugin.tag +def set_soft_max_volume(max_volume: int) +``` + +Limit the maximum volume to max_volume for the currently active output + + + + +#### get\_soft\_max\_volume + +```python +@plugin.tag +def get_soft_max_volume() +``` + +Return the maximum volume limit for the currently active output + + + + +#### card\_list + +```python +def card_list() -> List[pulsectl.PulseCardInfo] +``` + +Return the list of present sound card + + + + +# components.rfid + + + +# components.rfid.reader + + + +## RfidCardDetectCallbacks Objects + +```python +class RfidCardDetectCallbacks(CallbackHandler) +``` + +Callbacks are executed if rfid card is detected + + + + +#### register + +```python +def register(func: Callable[[str, RfidCardDetectState], None]) +``` + +Add a new callback function :attr:`func`. + +Callback signature is + +.. py:function:: func(card_id: str, state: int) + :noindex: + +**Arguments**: + +- `card_id`: Card ID +- `state`: See `RfidCardDetectState` + + + +#### run\_callbacks + +```python +def run_callbacks(card_id: str, state: RfidCardDetectState) +``` + + + + + +#### rfid\_card\_detect\_callbacks + +Callback handler instance for rfid_card_detect_callbacks events. + +See [`RfidCardDetectCallbacks`](#components.rfid.reader.RfidCardDetectCallbacks) + + + + +## CardRemovalTimerClass Objects + +```python +class CardRemovalTimerClass(threading.Thread) +``` + +A timer watchdog thread that calls timeout_action on time-out + + + + +#### \_\_init\_\_ + +```python +def __init__(on_timeout_callback, logger: logging.Logger = None) +``` + +**Arguments**: + +- `on_timeout_callback`: The function to execute on time-out + + + +# components.rfid.configure + + + +#### reader\_install\_dependencies + +```python +def reader_install_dependencies(reader_path: str, + dependency_install: str) -> None +``` + +Install dependencies for the selected reader module + +**Arguments**: + +- `reader_path`: Path to the reader module +- `dependency_install`: how to handle installing of dependencies +'query': query user (default) +'auto': automatically +'no': don't install dependencies + + + +#### reader\_load\_module + +```python +def reader_load_module(reader_name) +``` + +Load the module for the reader_name + +A ModuleNotFoundError is unrecoverable, but we at least want to give some hint how to resolve that to the user +All other errors will NOT be handled. Modules that do not load due to compile errors have other problems + +**Arguments**: + +- `reader_name`: Name of the reader to load the module for + +**Returns**: + +module + + + +#### query\_user\_for\_reader + +```python +def query_user_for_reader(dependency_install='query') -> dict +``` + +Ask the user to select a RFID reader and prompt for the reader's configuration + +This function performs the following steps, to find and present all available readers to the user + +- search for available reader subpackages +- dynamically load the description module for each reader subpackage +- queries user for selection +- if no_dep_install=False, install dependencies as given by requirements.txt and execute setup.inc.sh of subpackage +- dynamically load the actual reader module from the reader subpackage +- if selected reader has customization options query user for that now +- return configuration + +There are checks to make sure we have the right reader modules and they are what we expect. +The are as few requirements towards the reader module as possible and everything else is optional +(see reader_template for these requirements) +However, there is no error handling w.r.t to user input and reader's query_config. Firstly, in this script +we cannot gracefully handle an exception that occurs on reader level, and secondly the exception will simply +exit the script w/o writing the config to file. No harm done. + +This script expects to reside in the directory with all the reader subpackages, i.e it is part of the rfid-reader package. +Otherwise you'll need to adjust sys.path + +**Arguments**: + +- `dependency_install`: how to handle installing of dependencies +'query': query user (default) +'auto': automatically +'no': don't install dependencies + +**Returns**: + +`dict as {section: {parameter: value}}`: nested dict with entire configuration that can be read into ConfigParser + + + +#### write\_config + +```python +def write_config(config_file: str, + config_dict: dict, + force_overwrite=False) -> None +``` + +Write configuration to config_file + +**Arguments**: + +- `config_file`: relative or absolute path to config file +- `config_dict`: nested dict with configuration parameters for ConfigParser consumption +- `force_overwrite`: overwrite existing configuration file without asking + + + +# components.rfid.hardware.fake\_reader\_gui.fake\_reader\_gui + + + +# components.rfid.hardware.fake\_reader\_gui.description + + + +# components.rfid.hardware.fake\_reader\_gui.gpioz\_gui\_addon + +Add GPIO input devices and output devices to the RFID Mock Reader GUI + + + + +#### create\_inputs + +```python +def create_inputs(frame, default_btn_width, default_padx, default_pady) +``` + +Add all input devies to the GUI + +**Arguments**: + +- `frame`: The TK frame (e.g. LabelFrame) in the main GUI to add the buttons to + +**Returns**: + +List of all added GUI buttons + + + +#### set\_state + +```python +def set_state(value, box_state_var) +``` + +Change the value of a checkbox state variable + + + + +#### que\_set\_state + +```python +def que_set_state(value, box_state_var) +``` + +Queue the action to change a checkbox state variable to the TK GUI main thread + + + + +#### fix\_state + +```python +def fix_state(box_state_var) +``` + +Prevent a checkbox state variable to change on checkbox mouse press + + + + +#### pbox\_set\_state + +```python +def pbox_set_state(value, pbox_state_var, label_var) +``` + +Update progress bar state and related state label + + + + +#### que\_set\_pbox + +```python +def que_set_pbox(value, pbox_state_var, label_var) +``` + +Queue the action to change the progress bar state to the TK GUI main thread + + + + +#### create\_outputs + +```python +def create_outputs(frame, default_btn_width, default_padx, default_pady) +``` + +Add all output devices to the GUI + +**Arguments**: + +- `frame`: The TK frame (e.g. LabelFrame) in the main GUI to add the representations to + +**Returns**: + +List of all added GUI objects + + + +# components.rfid.hardware.generic\_usb.description + + + +# components.rfid.hardware.generic\_usb.generic\_usb + + + +# components.rfid.hardware.rc522\_spi.description + + + +# components.rfid.hardware.rc522\_spi.rc522\_spi + + + +# components.rfid.hardware.pn532\_i2c\_py532.description + + + +# components.rfid.hardware.pn532\_i2c\_py532.pn532\_i2c\_py532 + + + +# components.rfid.hardware.rdm6300\_serial.rdm6300\_serial + + + +#### decode + +```python +def decode(raw_card_id: bytearray, number_format: int) -> str +``` + +Decode the RDM6300 data format into actual card ID + + + + +# components.rfid.hardware.rdm6300\_serial.description + + + +# components.rfid.hardware.template\_new\_reader.description + +Provide a short title for this reader. + +This is what that user will see when asked for selecting his RFID reader +So, be precise but readable. Precise means 40 characters or less + + + + +# components.rfid.hardware.template\_new\_reader.template\_new\_reader + + + +#### query\_customization + +```python +def query_customization() -> dict +``` + +Query the user for reader parameter customization + +This function will be called during the configuration/setup phase when the user selects this reader module. +It must return all configuration parameters that are necessary to later use the Reader class. +You can ask the user for selections and choices. And/or provide default values. +If your reader requires absolutely no configuration return {} + + + + +## ReaderClass Objects + +```python +class ReaderClass(ReaderBaseClass) +``` + +The actual reader class that is used to read RFID cards. + +It will be instantiated once and then read_card() is called in an endless loop. + +It will be used in a manner + with Reader(reader_cfg_key) as reader: + for card_id in reader: + ... +which ensures proper resource de-allocation. For this to work derive this class from ReaderBaseClass. +All the required interfaces are implemented there. + +Put your code into these functions (see below for more information) + - `__init__` + - read_card + - cleanup + - stop + + + + +#### \_\_init\_\_ + +```python +def __init__(reader_cfg_key) +``` + +In the constructor, you will get the `reader_cfg_key` with which you can access the configuration data + +As you are dealing directly with potentially user-manipulated config information, it is +advisable to do some sanity checks and give useful error messages. Even if you cannot recover gracefully, +a good error message helps :-) + + + + +#### cleanup + +```python +def cleanup() +``` + +The cleanup function: free and release all resources used by this card reader (if any). + +Put all your cleanup code here, e.g. if you are using the serial bus or GPIO pins. +Will be called implicitly via the __exit__ function +This function must exist! If there is nothing to do, just leave the pass statement in place below + + + + +#### stop + +```python +def stop() +``` + +This function is called to tell the reader to exist it's reading function. + +This function is called before cleanup is called. + +> [!NOTE] +> This is usually called from a different thread than the reader's thread! And this is the reason for the +> two-step exit strategy. This function works across threads to indicate to the reader that is should stop attempt +> to read a card. Once called, the function read_card will not be called again. When the reader thread exits +> cleanup is called from the reader thread itself. + + + + +#### read\_card + +```python +def read_card() -> str +``` + +Blocking or non-blocking function that waits for a new card to appear and return the card's UID as string + +This is were your main code goes :-) +This function must return a string with the card id +In case of error, it may return None or an empty string + +The function should break and return with an empty string, once stop() is called + + + + +# components.rfid.readerbase + + + +## ReaderBaseClass Objects + +```python +class ReaderBaseClass(ABC) +``` + +Abstract Base Class for all Reader Classes to ensure common API + +Look at template_new_reader.py for documentation how to integrate a new RFID reader + + + + +# components.rfid.cards + +Handling the RFID card database + +A few considerations: +- Changing the Card DB influences to current state + - rfid.reader: Does not care, as it always freshly looks into the DB when a new card is triggered + - fake_reader_gui: Initializes the Drop-down menu once on start --> Will get out of date! + +Do we need a notifier? Or a callback for modules to get notified? +Do we want to publish the information about a card DB update? +TODO: Add callback for on_database_change + +TODO: check card id type (if int, convert to str) +TODO: check if args is really a list (convert if not?) + + + + +#### list\_cards + +```python +@plugs.register +def list_cards() +``` + +Provide a summarized, decoded list of all card actions + +This is intended as basis for a formatter function + +Format: 'id': {decoded_function_call, ignore_same_id_delay, ignore_card_removal_action, description, from_alias} + + + + +#### delete\_card + +```python +@plugs.register +def delete_card(card_id: str, auto_save: bool = True) +``` + +**Arguments**: + +- `auto_save`: +- `card_id`: + + + +#### register\_card + +```python +@plugs.register +def register_card(card_id: str, + cmd_alias: str, + args: Optional[List] = None, + kwargs: Optional[Dict] = None, + ignore_card_removal_action: Optional[bool] = None, + ignore_same_id_delay: Optional[bool] = None, + overwrite: bool = False, + auto_save: bool = True) +``` + +Register a new card based on quick-selection + +If you are going to call this through the RPC it will get a little verbose + +**Example:** Registering a new card with ID *0009* for increment volume with a custom argument to inc_volume +(*here: 15*) and custom *ignore_same_id_delay value*:: + + plugin.call_ignore_errors('cards', 'register_card', + args=['0009', 'inc_volume'], + kwargs={'args': [15], 'ignore_same_id_delay': True, 'overwrite': True}) + + + + +#### register\_card\_custom + +```python +@plugs.register +def register_card_custom() +``` + +Register a new card with full RPC call specification (Not implemented yet) + + + + +#### save\_card\_database + +```python +@plugs.register +def save_card_database(filename=None, *, only_if_changed=True) +``` + +Store the current card database. If filename is None, it is saved back to the file it was loaded from + + + + +# components.rfid.cardutils + +Common card decoding functions + +TODO: Thread safety when accessing the card DB! + + + + +#### decode\_card\_command + +```python +def decode_card_command(cfg_rpc_cmd: Mapping, logger: logging.Logger = log) +``` + +Extension of utils.decode_action with card-specific parameters + + + + +#### card\_command\_to\_str + +```python +def card_command_to_str(cfg_rpc_cmd: Mapping, long=False) -> List[str] +``` + +Returns a list of strings with [card_action, ignore_same_id_delay, ignore_card_removal_action] + +The last two parameters are only present, if *long* is True and if they are present in the cfg_rpc_cmd + + + + +#### card\_to\_str + +```python +def card_to_str(card_id: str, long=False) -> List[str] +``` + +Returns a list of strings from card entry command in the format of :func:`card_command_to_str` + + + + +# components.publishing + +Plugin interface for Jukebox Publisher + +Thin wrapper around jukebox.publishing to benefit from the plugin loading / exit handling / function handling + +This is the first package to be loaded and the last to be closed: put Hello and Goodbye publish messages here. + + + + +#### republish + +```python +@plugin.register +def republish(topic=None) +``` + +Re-publish the topic tree 'topic' to all subscribers + +**Arguments**: + +- `topic`: Topic tree to republish. None = resend all + + + +# components.player + + + +## MusicLibPath Objects + +```python +class MusicLibPath() +``` + +Extract the music directory from the mpd.conf file + + + + +#### get\_music\_library\_path + +```python +def get_music_library_path() +``` + +Get the music library path + + + + +# components.jingle + +Jingle Playback Factory for extensible run-time support of various file types + + + + +## JingleFactory Objects + +```python +class JingleFactory() +``` + +Jingle Factory + + + + +#### list + +```python +def list() +``` + +List the available volume services + + + + +#### play + +```python +@plugin.register +def play(filename) +``` + +Play the jingle using the configured jingle service + +> [!NOTE] +> This runs in a separate thread. And this may cause troubles +> when changing the volume level before +> and after the sound playback: There is nothing to prevent another +> thread from changing the volume and sink while playback happens +> and afterwards we change the volume back to where it was before! + +There is no way around this dilemma except for not running the jingle as a +separate thread. Currently (as thread) even the RPC is started before the sound +is finished and the volume is reset to normal... + +However: Volume plugin is loaded before jingle and sets the default +volume. No interference here. It can now only happen +if (a) through the RPC or (b) some other plugin the volume is changed. Okay, now +(a) let's hope that there is enough delay in the user requesting a volume change +(b) let's hope no other plugin wants to do that +(c) no bluetooth device connects during this time (and pulseaudio control is set to toggle_on_connect) +and take our changes with the threaded approach. + + + + +#### play\_startup + +```python +@plugin.register +def play_startup() +``` + +Play the startup sound (using jingle.play) + + + + +#### play\_shutdown + +```python +@plugin.register +def play_shutdown() +``` + +Play the shutdown sound (using jingle.play) + + + + +# components.jingle.alsawave + +ALSA wave jingle Service for jingle.JingleFactory + + + + +## AlsaWave Objects + +```python +@plugin.register +class AlsaWave() +``` + +Jingle Service for playing wave files directly from Python through ALSA + + + + +#### play + +```python +@plugin.tag +def play(filename) +``` + +Play the wave file + + + + +## AlsaWaveBuilder Objects + +```python +class AlsaWaveBuilder() +``` + + + +#### \_\_init\_\_ + +```python +def __init__() +``` + +Builder instantiates AlsaWave during init and not during first call because + +we want AlsaWave registers as plugin function in any case if this plugin is loaded +(and not only on first use!) + + + + +# components.jingle.jinglemp3 + +Generic MP3 jingle Service for jingle.JingleFactory + + + + +## JingleMp3Play Objects + +```python +@plugin.register(auto_tag=True) +class JingleMp3Play() +``` + +Jingle Service for playing MP3 files + + + + +#### play + +```python +def play(filename) +``` + +Play the MP3 file + + + + +## JingleMp3PlayBuilder Objects + +```python +class JingleMp3PlayBuilder() +``` + + + +#### \_\_init\_\_ + +```python +def __init__() +``` + +Builder instantiates JingleMp3Play during init and not during first call because + +we want JingleMp3Play registers as plugin function in any case if this plugin is loaded +(and not only on first use!) + + + + +# components.hostif.linux + + + +#### shutdown + +```python +@plugin.register +def shutdown() +``` + +Shutdown the host machine + + + + +#### reboot + +```python +@plugin.register +def reboot() +``` + +Reboot the host machine + + + + +#### jukebox\_is\_service + +```python +@plugin.register +def jukebox_is_service() +``` + +Check if current Jukebox process is running as a service + + + + +#### is\_any\_jukebox\_service\_active + +```python +@plugin.register +def is_any_jukebox_service_active() +``` + +Check if a Jukebox service is running + +> [!NOTE] +> Does not have the be the current app, that is running as a service! + + + + +#### restart\_service + +```python +@plugin.register +def restart_service() +``` + +Restart Jukebox App if running as a service + + + + +#### get\_disk\_usage + +```python +@plugin.register() +def get_disk_usage(path='/') +``` + +Return the disk usage in Megabytes as dictionary for RPC export + + + + +#### get\_cpu\_temperature + +```python +@plugin.register +def get_cpu_temperature() +``` + +Get the CPU temperature with single decimal point + +No error handling: this is expected to take place up-level! + + + + +#### get\_ip\_address + +```python +@plugin.register +def get_ip_address() +``` + +Get the IP address + + + + +#### wlan\_disable\_power\_down + +```python +@plugin.register() +def wlan_disable_power_down(card=None) +``` + +Turn off power management of wlan. Keep RPi reachable via WLAN + +This must be done after every reboot +card=None takes card from configuration file + + + + +#### get\_autohotspot\_status + +```python +@plugin.register +def get_autohotspot_status() +``` + +Get the status of the auto hotspot feature + + + + +#### stop\_autohotspot + +```python +@plugin.register() +def stop_autohotspot() +``` + +Stop auto hotspot functionality + +Basically disabling the cronjob and running the script one last time manually + + + + +#### start\_autohotspot + +```python +@plugin.register() +def start_autohotspot() +``` + +start auto hotspot functionality + +Basically enabling the cronjob and running the script one time manually + + + + +# components.misc + +Miscellaneous function package + + + + +#### rpc\_cmd\_help + +```python +@plugin.register +def rpc_cmd_help() +``` + +Return all commands for RPC + + + + +#### get\_all\_loaded\_packages + +```python +@plugin.register +def get_all_loaded_packages() +``` + +Get all successfully loaded plugins + + + + +#### get\_all\_failed\_packages + +```python +@plugin.register +def get_all_failed_packages() +``` + +Get all plugins with error during load or initialization + + + + +#### get\_start\_time + +```python +@plugin.register +def get_start_time() +``` + +Time when JukeBox has been started + + + + +#### get\_log + +```python +def get_log(handler_name: str) +``` + +Get the log file from the loggers (debug_file_handler, error_file_handler) + + + + +#### get\_log\_debug + +```python +@plugin.register +def get_log_debug() +``` + +Get the log file (from the debug_file_handler) + + + + +#### get\_log\_error + +```python +@plugin.register +def get_log_error() +``` + +Get the log file (from the error_file_handler) + + + + +#### get\_git\_state + +```python +@plugin.register +def get_git_state() +``` + +Return git state information for the current branch + + + + +#### empty\_rpc\_call + +```python +@plugin.register +def empty_rpc_call(msg: str = '') +``` + +This function does nothing. + +The RPC command alias 'none' is mapped to this function. + +This is also used when configuration errors lead to non existing RPC command alias definitions. +When the alias definition is void, we still want to return a valid function to simplify error handling +up the module call stack. + +**Arguments**: + +- `msg`: If present, this message is send to the logger with severity warning + + + +# components.controls + + + +# components.controls.bluetooth\_audio\_buttons + +Plugin to attempt to automatically listen to it's buttons (play, next, ...) + +when a bluetooth sound device (headphone, speakers) connects + +This effectively does: + +* register a callback with components.volume to get notified when a new sound card connects +* if that is a bluetooth device, try opening an input device with similar name using +* button listeners are run each in its own thread + + + + +# components.controls.common.evdev\_listener + +Generalized listener for ``dev/input`` devices + + + + +#### find\_device + +```python +def find_device(device_name: str, + exact_name: bool = True, + mandatory_keys: Optional[Set[int]] = None) -> str +``` + +Find an input device with device_name and mandatory keys. + +**Arguments**: + +- `device_name`: See :func:`_filter_by_device_name` +- `exact_name`: See :func:`_filter_by_device_name` +- `mandatory_keys`: See :func:`_filter_by_mandatory_keys` + +**Raises**: + +- `FileNotFoundError`: if no device is found. +- `AttributeError`: if device does not have the mandatory key +If multiple devices match, the first match is returned + +**Returns**: + +The path to the device + + + +## EvDevKeyListener Objects + +```python +class EvDevKeyListener(threading.Thread) +``` + +Opens and event input device from ``/dev/inputs``, and runs callbacks upon the button presses. + +Input devices could be .e.g. Keyboard, Bluetooth audio buttons, USB buttons + +Runs as a separate thread. When device disconnects or disappears, thread exists. A new thread must be started +when device re-connects. + +Assign callbacks to :attr:`EvDevKeyListener.button_callbacks` + + + + +#### \_\_init\_\_ + +```python +def __init__(device_name_request: str, exact_name: bool, thread_name: str) +``` + +**Arguments**: + +- `device_name_request`: The device name to look for +- `exact_name`: If true, device_name must mach exactly, else a match is returned if device_name is a substring of +the reported device name +- `thread_name`: Name of the listener thread + + + +#### run + +```python +def run() +``` + + + + + +#### start + +```python +def start() -> None +``` + +Start the tread and start listening + + + + +# components.battery\_monitor + + + +# components.battery\_monitor.BatteryMonitorBase + + + +## pt1\_frac Objects + +```python +class pt1_frac() +``` + +fixed point first order filter, fractional format: 2^16,2^16 + + + + +## BattmonBase Objects + +```python +class BattmonBase() +``` + +Battery Monitor base class + + + + +# components.battery\_monitor.batt\_mon\_simulator + + + +## battmon\_simulator Objects + +```python +class battmon_simulator(BatteryMonitorBase.BattmonBase) +``` + +Battery Monitor Simulator + + + + +# components.battery\_monitor.batt\_mon\_i2c\_ads1015 + + + +## battmon\_ads1015 Objects + +```python +class battmon_ads1015(BatteryMonitorBase.BattmonBase) +``` + +Battery Monitor based on a ADS1015 + +> [!CAUTION] +> Lithium and other batteries are dangerous and must be treated with care. +> Rechargeable Lithium Ion batteries are potentially hazardous and can +> present a serious **FIRE HAZARD** if damaged, defective or improperly used. +> Do not use this circuit to a lithium ion battery without expertise and +> training in handling and use of batteries of this type. +> Use appropriate test equipment and safety protocols during development. +> There is no warranty, this may not work as expected or at all! + +This script is intended to read out the Voltage of a single Cell LiIon Battery using a CY-ADS1015 Board: + + 3.3V + + + | + .----o----. + ___ | | SDA + .--------|___|---o----o---------o AIN0 o------ + | 2MΩ | | | | SCL + | .-. | | ADS1015 o------ + --- | | --- | | + Battery - 1.5MΩ| | ---100nF '----o----' + 2.9V-4.2V| '-' | | + | | | | + === === === === + +Attention: +* the circuit is constantly draining the battery! (leak current up to: 2.1µA) +* the time between sample needs to be a minimum 1sec with this high impedance voltage divider + don't use the continuous conversion method! + + + + +# components.gpio.gpioz.plugin + +The GPIOZ plugin interface build all input and output devices from the configuration file and connects + +the actions and callbacks. It also provides a very restricted, but common API for the output devices to the RPC. +That API is mainly used for testing. All the relevant output state changes are usually made through callbacks directly +using the output device's API. + + + + +#### output\_devices + +List of all created output devices + + + + +#### input\_devices + +List of all created input devices + + + + +#### factory + +The global pin factory used in this module + +Using different pin factories for different devices is not supported + + + + +#### IS\_ENABLED + +Indicates that the GPIOZ module is enabled and loaded w/o errors + + + + +#### IS\_MOCKED + +Indicates that the pin factory is a mock factory + + + + +#### CONFIG\_FILE + +The path of the config file the GPIOZ configuration was loaded from + + + + +## ServiceIsRunningCallbacks Objects + +```python +class ServiceIsRunningCallbacks(CallbackHandler) +``` + +Callbacks are executed when + +* Jukebox app started +* Jukebox shuts down + +This is intended to e.g. signal an LED to change state. +This is integrated into this module because: + +* we need the GPIO to control a LED (it must be available when the status callback comes) +* the plugin callback functions provide all the functionality to control the status of the LED +* which means no need to adapt other modules + + + + +#### register + +```python +def register(func: Callable[[int], None]) +``` + +Add a new callback function :attr:`func`. + +Callback signature is + +.. py:function:: func(status: int) + :noindex: + +**Arguments**: + +- `status`: 1 if app started, 0 if app shuts down + + + +#### run\_callbacks + +```python +def run_callbacks(status: int) +``` + + + + + +#### service\_is\_running\_callbacks + +Callback handler instance for service_is_running_callbacks events. + +See :class:`ServiceIsRunningCallbacks` + + + + +#### build\_output\_device + +```python +def build_output_device(name: str, config: Dict) +``` + +Construct and register a new output device + +In principal all supported GPIOZero output devices can be used. +For all devices a custom functions need to be written to control the state of the outputs + + + + +#### build\_input\_device + +```python +def build_input_device(name: str, config) +``` + +Construct and connect a new input device + +Supported input devices are those from gpio.gpioz.core.input_devices + + + + +#### get\_output + +```python +def get_output(name: str) +``` + +Get the output device instance based on the configured name + +**Arguments**: + +- `name`: The alias name output device instance + + + +#### on + +```python +@plugin.register +def on(name: str) +``` + +Turn an output device on + +**Arguments**: + +- `name`: The alias name output device instance + + + +#### off + +```python +@plugin.register +def off(name: str) +``` + +Turn an output device off + +**Arguments**: + +- `name`: The alias name output device instance + + + +#### set\_value + +```python +@plugin.register +def set_value(name: str, value: Any) +``` + +Set the output device to :attr:`value` + +**Arguments**: + +- `name`: The alias name output device instance +- `value`: Value to set the device to + + + +#### flash + +```python +@plugin.register +def flash(name, + on_time=1, + off_time=1, + n=1, + *, + fade_in_time=0, + fade_out_time=0, + tone=None, + color=(1, 1, 1)) +``` + +Flash (blink or beep) an output device + +This is a generic function for all types of output devices. Parameters not applicable to an +specific output device are silently ignored + +**Arguments**: + +- `name`: The alias name output device instance +- `on_time`: Time in seconds in state ``ON`` +- `off_time`: Time in seconds in state ``OFF`` +- `n`: Number of flash cycles +- `tone`: The tone in to play, e.g. 'A4'. *Only for TonalBuzzer*. +- `color`: The RGB color *only for PWMLED*. +- `fade_in_time`: Time in seconds for transitioning to on. *Only for PWMLED and RGBLED* +- `fade_out_time`: Time in seconds for transitioning to off. *Only for PWMLED and RGBLED* + + + +# components.gpio.gpioz.plugin.connectivity + +Provide connector functions to hook up to some kind of Jukebox functionality and change the output device's state + +accordingly. + +Connector functions can often be used for various output devices. Some connector functions are specific to +an output device type. + + + + +#### BUZZ\_TONE + +The tone to be used as buzz tone when the buzzer is an active buzzer + + + + +#### register\_rfid\_callback + +```python +def register_rfid_callback(device) +``` + +Flash the output device once on successful RFID card detection and thrice if card ID is unknown + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.LED` +* :class:`components.gpio.gpioz.core.output_devices.PWMLED` +* :class:`components.gpio.gpioz.core.output_devices.RGBLED` +* :class:`components.gpio.gpioz.core.output_devices.Buzzer` +* :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + + + + +#### register\_status\_led\_callback + +```python +def register_status_led_callback(device) +``` + +Turn LED on when Jukebox App has started + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.LED` +* :class:`components.gpio.gpioz.core.output_devices.PWMLED` +* :class:`components.gpio.gpioz.core.output_devices.RGBLED` + + + + +#### register\_status\_buzzer\_callback + +```python +def register_status_buzzer_callback(device) +``` + +Buzz once when Jukebox App has started, twice when closing down + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.Buzzer` +* :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + + + + +#### register\_status\_tonalbuzzer\_callback + +```python +def register_status_tonalbuzzer_callback(device) +``` + +Buzz a multi-note melody when Jukebox App has started and when closing down + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + + + + +#### register\_audio\_sink\_change\_callback + +```python +def register_audio_sink_change_callback(device) +``` + +Turn LED on if secondary audio output is selected. If audio output change + +fails, blink thrice + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.LED` +* :class:`components.gpio.gpioz.core.output_devices.PWMLED` +* :class:`components.gpio.gpioz.core.output_devices.RGBLED` + + + + +#### register\_volume\_led\_callback + +```python +def register_volume_led_callback(device) +``` + +Have a PWMLED change it's brightness according to current volume. LED flashes when minimum or maximum volume + +is reached. Minimum value is still a very dimly turned on LED (i.e. LED is never off). + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.PWMLED` + + + + +#### register\_volume\_buzzer\_callback + +```python +def register_volume_buzzer_callback(device) +``` + +Sound a buzzer once when minimum or maximum value is reached + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.Buzzer` +* :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + + + + +#### register\_volume\_rgbled\_callback + +```python +def register_volume_rgbled_callback(device) +``` + +Have a :class:`RGBLED` change it's color according to current volume. LED flashes when minimum or maximum volume + +is reached. + +Compatible devices: + +* :class:`components.gpio.gpioz.core.output_devices.RGBLED` + + + + +# components.gpio.gpioz.core.converter + +Provides converter functions/classes for various Jukebox parameters to + +values that can be assigned to GPIO output devices + + + + +## ColorProperty Objects + +```python +class ColorProperty() +``` + +Color descriptor ensuring valid weight ranges + + + + +## VolumeToRGB Objects + +```python +class VolumeToRGB() +``` + +Converts linear volume level to an RGB color value running through the color spectrum + +**Arguments**: + +- `max_input`: Maximum input value of linear input data +- `offset`: Offset in degrees in the color circle. Color circle +traverses blue (0), cyan(60), green (120), yellow(180), red (240), magenta (340) +- `section`: The section of the full color circle to use in degrees +Map input :data:`0...100` to color range :data:`green...magenta` and get the color for level 50 + + conv = VolumeToRGB(100, offset=120, section=180) + (r, g, b) = conv(50) + +The three components of an RGB LEDs do not have the same luminosity. +Weight factors are used to get a balanced color output + + + +#### \_\_call\_\_ + +```python +def __call__(volume) -> Tuple[float, float, float] +``` + +Perform conversion for single volume level + +**Returns**: + +Tuple(red, green, blue) + + + +#### luminize + +```python +def luminize(r, g, b) +``` + +Apply the color weight factors to the input color values + + + + +# components.gpio.gpioz.core.mock + +Changes to the GPIOZero devices for using with the Mock RFID Reader + + + + +#### patch\_mock\_outputs\_with\_callback + +```python +def patch_mock_outputs_with_callback() +``` + +Monkey Patch LED + Buzzer to get a callback when state changes + +This targets to represent the state in the TK GUI. +Other output devices cannot be represented in the GUI and are silently ignored. + +> [!NOTE] +> Only for developing purposes! + + + + +# components.gpio.gpioz.core.input\_devices + +Provides all supported input devices for the GPIOZ plugin. + +Input devices are based on GPIOZero devices. So for certain configuration parameters, you should +their documentation. + +All callback handlers are replaced by GPIOZ callback handlers. These are usually configured +by using the :func:`set_rpc_actions` each input device exhibits. + +For examples how to use the devices from the configuration files, see +[GPIO: Input Devices](../../builders/gpio.md#input-devices). + + + + +## NameMixin Objects + +```python +class NameMixin(ABC) +``` + +Provides name property and RPC decode function + + + + +#### set\_rpc\_actions + +```python +@abstractmethod +def set_rpc_actions(action_config) -> None +``` + +Set all input device callbacks from :attr:`action_config` + +**Arguments**: + +- `action_config`: Dictionary with one +[RPC Commands](../../builders/rpc-commands.md) definition entry for every device callback + + + +## EventProperty Objects + +```python +class EventProperty() +``` + +Event callback property + + + + +## ButtonBase Objects + +```python +class ButtonBase(ABC) +``` + +Common stuff for single button devices + + + + +#### value + +```python +@property +def value() +``` + +Returns 1 if the button is currently pressed, and 0 if it is not. + + + + +#### pin + +```python +@property +def pin() +``` + +Returns the underlying pin class from GPIOZero. + + + + +#### pull\_up + +```python +@property +def pull_up() +``` + +If :data:`True`, the device uses an internal pull-up resistor to set the GPIO pin “high” by default. + + + + +#### close + +```python +def close() +``` + +Close the device and release the pin + + + + +## Button Objects + +```python +class Button(NameMixin, ButtonBase) +``` + +A basic Button that runs a single actions on button press + +**Arguments**: + +- `pull_up` (`bool`): If :data:`True`, the device uses an internal pull-up resistor to set the GPIO pin “high” by default. +If :data:`False` the internal pull-down resistor is used. If :data:`None`, the pin will be floating and an external +resistor must be used and the :attr:`active_state` must be set. +- `active_state` (`bool or None`): If :data:`True`, when the hardware pin state is ``HIGH``, the software +pin is ``HIGH``. If :data:`False`, the input polarity is reversed: when +the hardware pin state is ``HIGH``, the software pin state is ``LOW``. +Use this parameter to set the active state of the underlying pin when +configuring it as not pulled (when *pull_up* is :data:`None`). When +*pull_up* is :data:`True` or :data:`False`, the active state is +automatically set to the proper value. +- `bounce_time` (`float or None`): Specifies the length of time (in seconds) that the component will +ignore changes in state after an initial change. This defaults to +:data:`None` which indicates that no bounce compensation will be +performed. +- `hold_repeat` (`bool`): If :data:`True` repeat the :attr:`on_press` every :attr:`hold_time` seconds. Else action +is run only once independent of the length of time the button is pressed for. +- `hold_time` (`float`): Time in seconds to wait between invocations of :attr:`on_press`. +- `pin_factory`: The GPIOZero pin factory. This parameter cannot be set through the configuration file +- `name` (`str`): The name of the button for use in error messages. This parameter cannot be set explicitly +through the configuration file + +.. copied from GPIOZero's documentation: active_state, bounce_time +.. Copyright Ben Nuttall / SPDX-License-Identifier: BSD-3-Clause + + + +#### on\_press + +```python +@property +def on_press() +``` + +The function to run when the device has been pressed + + + + +## LongPressButton Objects + +```python +class LongPressButton(NameMixin, ButtonBase) +``` + +A Button that runs a single actions only when the button is pressed long enough + +**Arguments**: + +- `pull_up`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `active_state`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `bounce_time`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `hold_repeat`: If :data:`True` repeat the :attr:`on_press` every :attr:`hold_time` seconds. Else only action +is run only once independent of the length of time the button is pressed for. +- `hold_time`: The minimum time, the button must be pressed be running :attr:`on_press` for the first time. +Also the time in seconds to wait between invocations of :attr:`on_press`. + + + +#### on\_press + +```python +@on_press.setter +def on_press(func) +``` + +The function to run when the device has been pressed for longer than :attr:`hold_time` + + + + +## ShortLongPressButton Objects + +```python +class ShortLongPressButton(NameMixin, ButtonBase) +``` + +A single button that runs two different actions depending if the button is pressed for a short or long time. + +The shortest possible time is used to ensure a unique identification to an action can be made. For example a short press +can only be identified, when a button is released before :attr:`hold_time`, i.e. not directly on button press. +But a long press can be identified as soon as :attr:`hold_time` is reached and there is no need to wait for the release +event. Furthermore, if there is a long hold, only the long hold action is executed - the short press action is not run +in this case! + +**Arguments**: + +- `pull_up`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `active_state`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `bounce_time`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `hold_time`: The time in seconds to differentiate if it is a short or long press. If the button is released before +this time, it is a short press. As soon as the button is held for :attr:`hold_time` it is a long press and the +short press action is ignored +- `hold_repeat`: If :data:`True` repeat the long press action every :attr:`hold_time` seconds after first long press +action +- `pin_factory`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `name`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) + + + +## RotaryEncoder Objects + +```python +class RotaryEncoder(NameMixin) +``` + +A rotary encoder to run one of two actions depending on the rotation direction. + +**Arguments**: + +- `bounce_time`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `pin_factory`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `name`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) + + + +#### pin\_a + +```python +@property +def pin_a() +``` + +Returns the underlying pin A + + + + +#### pin\_b + +```python +@property +def pin_b() +``` + +Returns the underlying pin B + + + + +#### on\_rotate\_clockwise + +```python +@property +def on_rotate_clockwise() +``` + +The function to run when the encoder is rotated clockwise + + + + +#### on\_rotate\_counter\_clockwise + +```python +@property +def on_rotate_counter_clockwise() +``` + +The function to run when the encoder is rotated counter clockwise + + + + +#### close + +```python +def close() +``` + +Close the device and release the pin + + + + +## TwinButton Objects + +```python +class TwinButton(NameMixin) +``` + +A two-button device which can run up to six different actions, a.k.a the six function beast. + +Per user press "input" of the TwinButton, only a single callback is executed (but this callback +may be executed several times). +The shortest possible time is used to ensure a unique identification to an action can be made. For example a short press +can only be identified, when a button is released before :attr:`hold_time`, i.e. not directly on button press. +But a long press can be identified as soon as :attr:`hold_time` is reached and there is no need to wait for the release +event. Furthermore, if there is a long hold, only the long hold action is executed - the short press action is not run +in this case! + +It is not necessary to configure all actions. + +**Arguments**: + +- `pull_up`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `active_state`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `bounce_time`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `hold_time`: The time in seconds to differentiate if it is a short or long press. If the button is released before +this time, it is a short press. As soon as the button is held for :attr:`hold_time` it is a long press and the +short press action is ignored. +- `hold_repeat`: If :data:`True` repeat the long press action every :attr:`hold_time` seconds after first long press +action. A long dual press is never repeated independent of this setting +- `pin_factory`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) +- `name`: See [`Button`](#components.gpio.gpioz.core.input_devices.Button) + + + +## StateVar Objects + +```python +class StateVar(Enum) +``` + +State encoding of the Mealy FSM + + + + +#### close + +```python +def close() +``` + +Close the device and release the pins + + + + +#### value + +```python +@property +def value() +``` + +2 bit integer indicating if and which button is currently pressed. Button A is the LSB. + + + + +#### is\_active + +```python +@property +def is_active() +``` + + + + + +# components.gpio.gpioz.core.output\_devices + +Provides all supported output devices for the GPIOZ plugin. + +For each device all constructor parameters can be set via the configuration file. Only exceptions +are the :attr:`name` and :attr:`pin_factory` which are set by internal mechanisms. + +The devices a are a relatively thin wrapper around the GPIOZero devices with the same name. +We add a name property to be used for error log message and similar and a :func:`flash` function +to all devices. This function provides a unified API to all devices. This means it can be called for every device +with parameters for this device and optional parameters from another device. Unused/unsupported parameters +are silently ignored. This is done to reduce the amount of coding required for connectivity functions. + +For examples how to use the devices from the configuration files, see +[GPIO: Output Devices](../../builders/gpio.md#output-devices). + + + + +## LED Objects + +```python +class LED(NameMixin, gpiozero.LED) +``` + +A binary LED + +**Arguments**: + +- `pin`: The GPIO pin which the LED is connected +- `active_high`: If :data:`true` the output pin will have a high logic level when the device is turned on. +- `pin_factory`: The GPIOZero pin factory. This parameter cannot be set through the configuration file +- `name` (`str`): The name of the button for use in error messages. This parameter cannot be set explicitly +through the configuration file + + + +#### flash + +```python +def flash(on_time=1, off_time=1, n=1, *, background=True, **ignored_kwargs) +``` + +Exactly like :func:`blink` but restores the original state after flashing the device + +**Arguments**: + +- `on_time` (`float`): Number of seconds on. Defaults to 1 second. +- `off_time` (`float`): Number of seconds off. Defaults to 1 second. +- `n`: Number of times to blink; :data:`None` means forever. +- `background` (`bool`): If :data:`True` (the default), start a background thread to +continue blinking and return immediately. If :data:`False`, only +return when the blink is finished +- `ignored_kwargs`: Ignore all other keywords so this function can be called with identical +parameters also for all other output devices + + + +## Buzzer Objects + +```python +class Buzzer(NameMixin, gpiozero.Buzzer) +``` + + + +#### flash + +```python +def flash(on_time=1, off_time=1, n=1, *, background=True, **ignored_kwargs) +``` + +Flash the device and restore the previous value afterwards + + + + +## PWMLED Objects + +```python +class PWMLED(NameMixin, gpiozero.PWMLED) +``` + + + +#### flash + +```python +def flash(on_time=1, + off_time=1, + n=1, + *, + fade_in_time=0, + fade_out_time=0, + background=True, + **ignored_kwargs) +``` + +Flash the LED and restore the previous value afterwards + + + + +## RGBLED Objects + +```python +class RGBLED(NameMixin, gpiozero.RGBLED) +``` + + + +#### flash + +```python +def flash(on_time=1, + off_time=1, + *, + fade_in_time=0, + fade_out_time=0, + on_color=(1, 1, 1), + off_color=(0, 0, 0), + n=None, + background=True, + **igorned_kwargs) +``` + +Flash the LED with :attr:`on_color` and restore the previous value afterwards + + + + +## TonalBuzzer Objects + +```python +class TonalBuzzer(NameMixin, gpiozero.TonalBuzzer) +``` + + + +#### flash + +```python +def flash(on_time=1, + off_time=1, + n=1, + *, + tone=None, + background=True, + **ignored_kwargs) +``` + +Play the tone :data:`tone` for :attr:`n` times + + + + +#### melody + +```python +def melody(on_time=0.2, + off_time=0.05, + *, + tone: Optional[List[Tone]] = None, + background=True) +``` + +Play a melody from the list of tones in :attr:`tone` + + + + +# components.timers + + + +# jukebox + + + +# jukebox.callingback + +Provides a generic callback handler + + + + +## CallbackHandler Objects + +```python +class CallbackHandler() +``` + +Generic Callback Handler to collect callbacks functions through :func:`register` and execute them + +with :func:`run_callbacks` + +A lock is used to sequence registering of new functions and running callbacks. + +**Arguments**: + +- `name`: A name of this handler for usage in log messages +- `logger`: The logger instance to use for logging +- `context`: A custom context handler to use as lock. If none, a local :class:`threading.Lock()` will be created + + + +#### register + +```python +def register(func: Optional[Callable[..., None]]) +``` + +Register a new function to be executed when the callback event happens + +**Arguments**: + +- `func`: The function to register. If set to :data:`None`, this register request is silently ignored. + + + +#### run\_callbacks + +```python +def run_callbacks(*args, **kwargs) +``` + +Run all registered callbacks. + +*ALL* exceptions from callback functions will be caught and logged only. +Exceptions are not raised upwards! + + + + +#### has\_callbacks + +```python +@property +def has_callbacks() +``` + + + + + +# jukebox.version + + + +#### version + +```python +def version() +``` + +Return the Jukebox version as a string + + + + +#### version\_info + +```python +def version_info() +``` + +Return the Jukebox version as a tuple of three numbers + +If this is a development version, an identifier string will be appended after the third integer. + + + + +# jukebox.cfghandler + +This module handles global and local configuration data + +The concept is that config handler is created and initialized once in the main thread:: + + cfg = get_handler('global') + load_yaml(cfg, 'filename.yaml') + +In all other modules (in potentially different threads) the same handler is obtained and used by:: + + cfg = get_handler('global') + +This eliminates the need to pass an effectively global configuration handler by parameters across the entire design. +Handlers are identified by their name (in the above example *global*) + +The function :func:`get_handler` is the main entry point to obtain a new or existing handler. + + + + +## ConfigHandler Objects + +```python +class ConfigHandler() +``` + +The configuration handler class + +Don't instantiate directly. Always use :func:`get_handler`! + +**Threads:** + +All threads can read and write to the configuration data. +**Proper thread-safeness must be ensured** by the the thread modifying the data by acquiring the lock +Easiest and best way is to use the context handler:: + + with cfg: + cfg['key'] = 66 + cfg.setndefault('hello', value='world') + +For a single function call, this is done implicitly. In this case, there is no need +to explicitly acquire the lock. + +Alternatively, you can lock and release manually by using :func:`acquire` and :func:`release` +But be very sure to release the lock even in cases of errors an exceptions! +Else we have a deadlock. + +Reading may be done without acquiring a lock. But be aware that when reading multiple values without locking, another +thread may intervene and modify some values in between! So, locking is still recommended. + + + + +#### loaded\_from + +```python +@property +def loaded_from() -> Optional[str] +``` + +Property to store filename from which the config was loaded + + + + +#### get + +```python +def get(key, *, default=None) +``` + +Enforce keyword on default to avoid accidental misuse when actually getn is wanted + + + + +#### setdefault + +```python +def setdefault(key, *, value) +``` + +Enforce keyword on default to avoid accidental misuse when actually setndefault is wanted + + + + +#### getn + +```python +def getn(*keys, default=None) +``` + +Get the value at arbitrary hierarchy depth. Return ``default`` if key not present + +The *default* value is returned no matter at which hierarchy level the path aborts. +A hierarchy is considered as any type with a :func:`get` method. + + + + +#### setn + +```python +def setn(*keys, value, hierarchy_type=None) -> None +``` + +Set the ``key: value`` pair at arbitrary hierarchy depth + +All non-existing hierarchy levels are created. + +**Arguments**: + +- `keys`: Key hierarchy path through the nested levels +- `value`: The value to set +- `hierarchy_type`: The type for new hierarchy levels. If *None*, the top-level type +is used + + + +#### setndefault + +```python +def setndefault(*keys, value, hierarchy_type=None) +``` + +Set the ``key: value`` pair at arbitrary hierarchy depth unless the key already exists + +All non-existing hierarchy levels are created. + +**Arguments**: + +- `keys`: Key hierarchy path through the nested levels +- `value`: The default value to set +- `hierarchy_type`: The type for new hierarchy levels. If *None*, the top-level type +is used + +**Returns**: + +The actual value or or the default value if key does not exit + + + +#### config\_dict + +```python +def config_dict(data) +``` + +Initialize configuration data from dict-like data structure + +**Arguments**: + +- `data`: configuration data + + + +#### is\_modified + +```python +def is_modified() -> bool +``` + +Check if the data has changed since the last load/store + +> [!NOTE] +> This relies on the *__str__* representation of the underlying data structure +> In case of ruamel, this ignores comments and only looks at the data + + + + +#### clear\_modified + +```python +def clear_modified() -> None +``` + +Sets the current state as new baseline, clearing the is_modified state + + + + +#### save + +```python +def save(only_if_changed: bool = False) -> None +``` + +Save config back to the file it was loaded from + +If you want to save to a different file, use :func:`write_yaml`. + + + + +#### load + +```python +def load(filename: str) -> None +``` + +Load YAML config file into memory + + + + +#### get\_handler + +```python +def get_handler(name: str) -> ConfigHandler +``` + +Get a configuration data handler with the specified name, creating it + +if it doesn't yet exit. If created, it is always created empty. + +This is the main entry point for obtaining an configuration handler + +**Arguments**: + +- `name`: Name of the config handler + +**Returns**: + +`ConfigHandler`: The configuration data handler for *name* + + + +#### load\_yaml + +```python +def load_yaml(cfg: ConfigHandler, filename: str) -> None +``` + +Load a yaml file into a ConfigHandler + +**Arguments**: + +- `cfg`: ConfigHandler instance +- `filename`: filename to yaml file + +**Returns**: + +None + + + +#### write\_yaml + +```python +def write_yaml(cfg: ConfigHandler, + filename: str, + only_if_changed: bool = False, + *args, + **kwargs) -> None +``` + +Writes ConfigHandler data to yaml file / sys.stdout + +**Arguments**: + +- `cfg`: ConfigHandler instance +- `filename`: filename to output file. If *sys.stdout*, output is written to console +- `only_if_changed`: Write file only, if ConfigHandler.is_modified() +- `args`: passed on to yaml.dump(...) +- `kwargs`: passed on to yaml.dump(...) + +**Returns**: + +None + + + +# jukebox.playlistgenerator + +Playlists are build from directory content in the following way: + +a directory is parsed and files are added to the playlist in the following way + +1. files are added in alphabetic order +2. files ending with ``*livestream.txt`` are unpacked and the containing URL(s) are added verbatim to the playlist +3. files ending with ``*podcast.txt`` are unpacked and the containing Podcast URL(s) are expanded and added to the playlist +4. files ending with ``*.m3u`` are treated as folder playlist. Regular folder processing is suspended and the playlist + is build solely from the ``*.m3u`` content. Only the alphabetically first ``*.m3u`` is processed. URLs are added verbatim + to the playlist except for ``*.xml`` and ``*.podcast`` URLS, which are expanded first + +An directory may contain a mixed set of files and multiple ``*.txt`` files, e.g. + + 01-livestream.txt + 02-livestream.txt + music.mp3 + podcast.txt + +All files are treated as music files and are added to the playlist, except those: + + * starting with ``.``, + * not having a file ending, i.e. do not contain a ``.``, + * ending with ``.txt``, + * ending with ``.m3u``, + * ending with one of the excluded file endings in :attr:`PlaylistCollector._exclude_endings` + +In recursive mode, the playlist is generated by concatenating all sub-folder playlists. Sub-folders are parsed +in alphabetic order. Symbolic links are being followed. The above rules are enforced on a per-folder bases. +This means, one ``*.m3u`` file per sub-folder is processed (if present). + +In ``*.txt`` and ``*.m3u`` files, all lines starting with ``#`` are ignored. + + + + +#### TYPE\_DECODE + +Types if file entires in parsed directory + + + + +## PlaylistCollector Objects + +```python +class PlaylistCollector() +``` + +Build a playlist from directory(s) + +This class is intended to be used with an absolute path to the music library:: + + plc = PlaylistCollector('/home/chris/music') + plc.parse('Traumfaenger') + print(f"res = {plc}") + +But it can also be used with relative paths from current working directory:: + + plc = PlaylistCollector('.') + plc.parse('../../../../music/Traumfaenger') + print(f"res = {plc}") + +The file ending exclusion list :attr:`PlaylistCollector._exclude_endings` is a class variable for performance reasons. +If changed it will affect all instances. For modifications always call :func:`set_exclusion_endings`. + + + + +#### \_\_init\_\_ + +```python +def __init__(music_library_base_path='/') +``` + +Initialize the playlist generator with music_library_base_path + +**Arguments**: + +- `music_library_base_path`: Base path the the music library. This is used to locate the file in the disk +but is omitted when generating the playlist entries. I.e. all files in the playlist are relative to this base dir + + + +#### set\_exclusion\_endings + +```python +@classmethod +def set_exclusion_endings(cls, endings: List[str]) +``` + +Set the class-wide file ending exclusion list + +See :attr:`PlaylistCollector._exclude_endings` + + + + +#### get\_directory\_content + +```python +def get_directory_content(path='.') +``` + +Parse the folder ``path`` and create a content list. Depth is always the current level + +**Arguments**: + +- `path`: Path to folder **relative** to ``music_library_base_path`` + +**Returns**: + +[ { type: 'directory', name: 'Simone', path: '/some/path/to/Simone' }, {...} ] +where type is one of :attr:`TYPE_DECODE` + + + +#### parse + +```python +def parse(path='.', recursive=False) +``` + +Parse the folder ``path`` and create a playlist from it's content + +**Arguments**: + +- `path`: Path to folder **relative** to ``music_library_base_path`` +- `recursive`: Parse folder recursivley, or stay in top-level folder + + + +# jukebox.NvManager + + + +# jukebox.publishing + + + +#### get\_publisher + +```python +def get_publisher() +``` + +Return the publisher instance for this thread + +Per thread, only one publisher instance is required to connect to the inproc socket. +A new instance is created if it does not already exist. + +If there is a remote-chance that your function publishing something may be called form +different threads, always make a fresh call to ``get_publisher()`` to get the correct instance for the current thread. + +Example:: + + import jukebox.publishing as publishing + + class MyClass: + def __init__(self): + pass + + def say_hello(name): + publishing.get_publisher().send('hello', f'Hi {name}, howya?') + +To stress what **NOT** to do: don't get a publisher instance in the constructor and save it to ``self._pub``. +If you do and ``say_hello`` gets called from different threads, the publisher of the thread which instantiated the class +will be used. + +If you need your very own private Publisher Instance, you'll need to instantiate it yourself. +But: the use cases are very rare for that. I cannot think of one at the moment. + +**Remember**: Don’t share ZeroMQ sockets between threads. + + + + +# jukebox.publishing.subscriber + + + +# jukebox.publishing.server + +## Publishing Server + +The common publishing server for the entire Jukebox using ZeroMQ + +### Structure + + +-----------------------+ + | functional interface | Publisher + | | - functional interface for single Thread + | PUB | - sends data to publisher (and thus across threads) + +-----------------------+ + | (1) + v + +-----------------------+ + | SUB (bind) | PublishServer + | | - Last Value (LV) Cache + | XPUB (bind) | - Subscriber notification and LV resend + +-----------------------+ - independent thread + | (2) + v + +#### Connection (1): Internal connection + +Internal connection only - do not use (no, not even inside this App for you own plugins - always bind to the PublishServer) + + Protocol: Multi-part message + + Part 1: Topic (in topic tree format) + E.g. player.status.elapsed + + Part 2: Payload or Message in json serialization + If empty (i.e. ``b''``), it means delete the topic sub-tree from cache. And instruct subscribers to do the same + + Part 3: Command + Usually empty, i.e. ``b''``. If not empty the message is treated as command for the PublishServer + and the message is not forwarded to the outside. This third part of the message is never forwarded + +#### Connection (2): External connection + +Upon connection of a new subscriber, the entire current state is resend from cache to ALL subscribers! +Subscribers must subscribe to topics. Topics are treated as topic trees! Subscribing to a root tree will +also get you all the branch topics. To get everything, subscribe to ``b''`` + + Protocol: Multi-part message + + Part 1: Topic (in topic tree format) + E.g. player.status.elapsed + + Part 2: Payload or Message in json serialization + If empty (i.e. b''), it means the subscriber must delete this key locally (not valid anymore) + +### Why? Why? + +Check out the [ZeroMQ Documentation](https://zguide.zeromq.org/docs/chapter5) +for why you need a proxy in a good design. + +For use case, we made a few simplifications + +### Design Rationales + +* "If you need [millions of messages per second](https://zguide.zeromq.org/docs/chapter5/`Pros`-and-Cons-of-Pub-Sub) + sent to thousands of points, + you'll appreciate pub-sub a lot more than if you need a few messages a second sent to a handful of recipients." +* "lower-volume network with a few dozen subscribers and a limited number of topics, we can use TCP and then + the [XSUB and XPUB](https://zguide.zeromq.org/docs/chapter5/`Last`-Value-Caching)" +* "Let's imagine [our feed has an average of 100,000 100-byte messages a + second](https://zguide.zeromq.org/docs/chapter5/`High`-Speed-Subscribers-Black-Box-Pattern) [...]. + While 100K messages a second is easy for a ZeroMQ application, ..." + +**But we have:** + +* few dozen subscribers --> Check! +* limited number of topics --> Check! +* max ~10 messages per second --> Check! +* small common state information --> Check! +* only the server updates the state --> Check! + +This means, we can use less complex patters than used for these high-speed, high code count, high data rate networks :-) + +* XPUB / XSUB to detect new subscriber +* Cache the entire state in the publisher +* Re-send the entire state on-demand (and then even to every subscriber) +* Using the same channel: sends state to every subscriber + +**Reliability considerations** + +* Late joining client (or drop-off and re-join): get full state update +* Server crash etc: No special handling necessary, we are simple + and don't need recovery in this case. Server will publish initial state + after re-start +* Subscriber too slow: Subscribers problem (TODO: Do we need to do anything about it?) + +**Start-up sequence:** + +* Publisher plugin is first plugin to be loaded +* Due to Publisher - PublisherServer structure no further sequencing required + +### Plugin interactions and usage + +RPC can trigger through function call in components/publishing plugin that + +* entire state is re-published (from the cache) +* a specific topic tree is re-published (from the cache) + +Plugins publishing state information should publish initial state at @plugin.finalize + +> [!IMPORTANT] +> Do not direclty instantiate the Publisher in your plugin module. Only one Publisher is +> required per thread. But the publisher instance **must** be thread-local! +> Always go through :func:`publishing.get_publisher()`. + +**Sockets** + +Three sockets are opened: + +1. TCP (on a configurable port) +2. Websocket (on a configurable port) +3. Inproc: On ``inproc://PublisherToProxy`` all topics are published app-internally. This can be used for plugin modules + that want to know about the current state on event based updates. + +**Further ZeroMQ References:** + +* [Working with Messages](https://zguide.zeromq.org/docs/chapter2/`Working`-with-Messages) +* [Multiple Threads](https://zguide.zeromq.org/docs/chapter2/`Multithreading`-with-ZeroMQ) + + + + +## PublishServer Objects + +```python +class PublishServer(threading.Thread) +``` + +The publish proxy server that collects and caches messages from all internal publishers and + +forwards them to the outside world + +Handles new subscriptions by sending out the entire cached state to **all** subscribers + +The code is structures using a [Reactor Pattern](https://zguide.zeromq.org/docs/chapter5/`Using`-a-Reactor) + + + + +#### run + +```python +def run() +``` + +Thread's activity + + + + +#### handle\_message + +```python +def handle_message(msg) +``` + +Handle incoming messages + + + + +#### handle\_subscription + +```python +def handle_subscription(msg) +``` + +Handle new subscribers + + + + +## Publisher Objects + +```python +class Publisher() +``` + +The publisher that provides the functional interface to the application + +> [!NOTE] +> * An instance must not be shared across threads! +> * One instance per thread is enough + + + + +#### \_\_init\_\_ + +```python +def __init__(check_thread_owner=True) +``` + +**Arguments**: + +- `check_thread_owner`: Check if send() is always called from the correct thread. This is debug feature +and is intended to expose the situation before it leads to real trouble. Leave it on! + + + +#### send + +```python +def send(topic: str, payload) +``` + +Send out a message for topic + + + + +#### revoke + +```python +def revoke(topic: str) +``` + +Revoke a single topic element (not a topic tree!) + + + + +#### resend + +```python +def resend(topic: Optional[str] = None) +``` + +Instructs the PublishServer to resend current status to all subscribers + +Not necessary to call after incremental updates or new subscriptions - that will happen automatically! + + + + +#### close\_server + +```python +def close_server() +``` + +Instructs the PublishServer to close itself down + + + + +# jukebox.daemon + + + +#### log\_active\_threads + +```python +@atexit.register +def log_active_threads() +``` + +This functions is registered with atexit very early, meaning it will be run very late. It is the best guess to + +evaluate which Threads are still running (and probably shouldn't be) + +This function is registered before all the plugins and their dependencies are loaded + + + + +## JukeBox Objects + +```python +class JukeBox() +``` + + + +#### signal\_handler + +```python +def signal_handler(esignal, frame) +``` + +Signal handler for orderly shutdown + +On first Ctrl-C (or SIGTERM) orderly shutdown procedure is embarked upon. It gets allocated a time-out! +On third Ctrl-C (or SIGTERM), this is interrupted and there will be a hard exit! + + + + +# jukebox.plugs + +A plugin package with some special functionality + +Plugins packages are python packages that are dynamically loaded. From these packages only a subset of objects is exposed +through the plugs.call interface. The python packages can use decorators or dynamic function call to register (callable) +objects. + +The python package name may be different from the name the package is registered under in plugs. This allows to load different +python packages for a specific feature based on a configuration file. Note: Python package are still loaded as regular +python packages and can be accessed by normal means + +If you want to provide additional functionality to the same feature (probably even for run-time switching) +you can implement a Factory Pattern using this package. Take a look at volume.py as an example. + +**Example:** Decorate a function for auto-registering under it's own name: + + import jukebox.plugs as plugs + @plugs.register + def func1(param): + pass + +**Example:** Decorate a function for auto-registering under a new name: + + @plugs.register(name='better_name') + def func2(param): + pass + +**Example:** Register a function during run-time under it's own name: + + def func3(param): + pass + plugs.register(func3) + +**Example:** Register a function during run-time under a new name: + + def func4(param): + pass + plugs.register(func4, name='other_name', package='other_package') + +**Example:** Decorate a class for auto registering during initialization, +including all methods (see _register_class for more info): + + @plugs.register(auto_tag=True) + class MyClass1: + pass + +**Example:** Register a class instance, from which only report is a callable method through the plugs interface: + + class MyClass2: + @plugs.tag + def report(self): + pass + myinst2 = MyClass2() + plugin.register(myinst2, name='myinst2') + +Naming convention: + +* package + * Either a python package + * or a plugin package (which is the python package but probably loaded under a different name inside plugs) +* plugin + * An object from the package that can be accessed through the plugs call function (i.e. a function or a class instance) + * The string name to above object +* name + * The string name of the plugin object for registration +* method + * In case the object is a class instance a bound method to call from the class instance + * The string name to above object + + + + +## PluginPackageClass Objects + +```python +class PluginPackageClass() +``` + +A local data class for holding all information about a loaded plugin package + + + + +#### register + +```python +@overload +def register(plugin: Callable) -> Callable +``` + +1-level decorator around a function + + + + +#### register + +```python +@overload +def register(plugin: Type) -> Any +``` + +Signature: 1-level decorator around a class + + + + +#### register + +```python +@overload +def register(*, name: str, package: Optional[str] = None) -> Callable +``` + +Signature: 2-level decorator around a function + + + + +#### register + +```python +@overload +def register(*, auto_tag: bool = False, package: Optional[str] = None) -> Type +``` + +Signature: 2-level decorator around a class + + + + +#### register + +```python +@overload +def register(plugin: Callable[..., Any] = None, + *, + name: Optional[str] = None, + package: Optional[str] = None, + replace: bool = False) -> Callable +``` + +Signature: Run-time registration of function / class instance / bound method + + + + +#### register + +```python +def register(plugin: Optional[Callable] = None, + *, + name: Optional[str] = None, + package: Optional[str] = None, + replace: bool = False, + auto_tag: bool = False) -> Callable +``` + +A generic decorator / run-time function to register plugin module callables + +The functions comes in five distinct signatures for 5 use cases: + +1. ``@plugs.register``: decorator for a class w/o any arguments +2. ``@plugs.register``: decorator for a function w/o any arguments +3. ``@plugs.register(auto_tag=bool)``: decorator for a class with 1 arguments +4. ``@plugs.register(name=name, package=package)``: decorator for a function with 1 or 2 arguments +5. ``plugs.register(plugin, name=name, package=package)``: run-time registration of + * function + * bound method + * class instance + +For more documentation see the functions +* :func:`_register_obj` +* :func:`_register_class` + +See the examples in Module :mod:`plugs` how to use this decorator / function + +**Arguments**: + +- `plugin`: +- `name`: +- `package`: +- `replace`: +- `auto_tag`: + + + +#### tag + +```python +def tag(func: Callable) -> Callable +``` + +Method decorator for tagging a method as callable through the plugs interface + +Note that the instantiated class must still be registered as plugin object +(either with the class decorator or dynamically) + +**Arguments**: + +- `func`: function to decorate + +**Returns**: + +the function + + + +#### initialize + +```python +def initialize(func: Callable) -> Callable +``` + +Decorator for functions that shall be called by the plugs package directly after the module is loaded + +**Arguments**: + +- `func`: Function to decorate + +**Returns**: + +The function itself + + + +#### finalize + +```python +def finalize(func: Callable) -> Callable +``` + +Decorator for functions that shall be called by the plugs package directly after ALL modules are loaded + +**Arguments**: + +- `func`: Function to decorate + +**Returns**: + +The function itself + + + +#### atexit + +```python +def atexit(func: Callable[[int], Any]) -> Callable[[int], Any] +``` + +Decorator for functions that shall be called by the plugs package directly after at exit of program. + +> [!IMPORTANT] +> There is no automatism as in atexit.atexit. The function plugs.shutdown() must be explicitly called +> during the shutdown procedure of your program. This is by design, so you can choose the exact situation in your +> shutdown handler. + +The atexit-functions are called with a single integer argument, which is passed down from plugin.exit(int) +It is intended for passing down the signal number that initiated the program termination + +**Arguments**: + +- `func`: Function to decorate + +**Returns**: + +The function itself + + + +#### load + +```python +def load(package: str, + load_as: Optional[str] = None, + prefix: Optional[str] = None) +``` + +Loads a python package as plugin package + +Executes a regular python package load. That means a potentially existing `__init__.py` is executed. +Decorator `@register` can by used to register functions / classes / class istances as plugin callable +Decorator `@initializer` can be used to tag functions that shall be called after package loading +Decorator `@finalizer` can be used to tag functions that shall be called after ALL plugin packges have been loaded +Instead of using `@initializer`, you may of course use `__init__.py` + +Python packages may be loaded under a different plugs package name. Python packages must be unique and the name under +which they are loaded as plugin package also. + +**Arguments**: + +- `package`: Python package to load as plugin package +- `load_as`: Plugin package registration name. If None the name is the python's package simple name +- `prefix`: Prefix to python package to create fully qualified name. This is used only to locate the python package +and ignored otherwise. Useful if all the plugin module are in a dedicated folder + + + +#### load\_all\_named + +```python +def load_all_named(packages_named: Mapping[str, str], + prefix: Optional[str] = None, + ignore_errors=False) +``` + +Load all packages in packages_named with mapped names + +**Arguments**: + +- `packages_named`: Dict[load_as, package] + + + +#### load\_all\_unnamed + +```python +def load_all_unnamed(packages_unnamed: Iterable[str], + prefix: Optional[str] = None, + ignore_errors=False) +``` + +Load all packages in packages_unnamed with default names + + + + +#### load\_all\_finalize + +```python +def load_all_finalize(ignore_errors=False) +``` + +Calls all functions registered with @finalize from all loaded modules in the order they were loaded + +This must be executed after the last plugin package is loaded + + + + +#### close\_down + +```python +def close_down(**kwargs) -> Any +``` + +Calls all functions registered with @atexit from all loaded modules in reverse order of module load order + +Modules are processed in reverse order. Several at-exit tagged functions of a single module are processed +in the order of registration. + +Errors raised in functions are suppressed to ensure all plugins are processed + + + + +#### call + +```python +def call(package: str, + plugin: str, + method: Optional[str] = None, + *, + args=(), + kwargs=None, + as_thread: bool = False, + thread_name: Optional[str] = None) -> Any +``` + +Call a function/method from the loaded plugins + +If a plugin is a function or a callable instance of a class, this is equivalent to + +``package.plugin(*args, **kwargs)`` + +If plugin is a class instance from which a method is called, this is equivalent to the followig. +Also remember, that method must have the attribute ``plugin_callable = True`` + +``package.plugin.method(*args, **kwargs)`` + +Calls are serialized by a thread lock. The thread lock is shared with call_ignore_errors. + +> [!NOTE] +> There is no logger in this function as they all belong up-level where the exceptions are handled. +> If you want logger messages instead of exceptions, use :func:`call_ignore_errors` + +**Arguments**: + +- `package`: Name of the plugin package in which to look for function/class instance +- `plugin`: Function name or instance name of a class +- `method`: Method name when accessing a class instance' method. Leave at *None* if unneeded. +- `as_thread`: Run the callable in separate daemon thread. +There is no return value from the callable in this case! The return value is the thread object. +Also note that Exceptions in the Thread must be handled in the Thread and are not propagated to the main Thread. +All threads are started as daemon threads with terminate upon main program termination. +There is not stop-thread mechanism. This is intended for short lived threads. +- `thread_name`: Name of the thread +- `args`: Arguments passed to callable +- `kwargs`: Keyword arguments passed to callable + +**Returns**: + +The return value from the called function, or, if started as thread the thread object + + + +#### call\_ignore\_errors + +```python +def call_ignore_errors(package: str, + plugin: str, + method: Optional[str] = None, + *, + args=(), + kwargs=None, + as_thread: bool = False, + thread_name: Optional[str] = None) -> Any +``` + +Call a function/method from the loaded plugins ignoring all raised Exceptions. + +Errors get logged. + +See :func:`call` for parameter documentation. + + + + +#### exists + +```python +def exists(package: str, + plugin: Optional[str] = None, + method: Optional[str] = None) -> bool +``` + +Check if an object is registered within the plugs package + + + + +#### get + +```python +def get(package: str, + plugin: Optional[str] = None, + method: Optional[str] = None) -> Any +``` + +Get a plugs-package registered object + +The return object depends on the number of parameters + +* 1 argument: Get the python module reference for the plugs *package* +* 2 arguments: Get the plugin reference for the plugs *package.plugin* +* 3 arguments: Get the plugin reference for the plugs *package.plugin.method* + + + + +#### loaded\_as + +```python +def loaded_as(module_name: str) -> str +``` + +Return the plugin name a python module is loaded as + + + + +#### delete + +```python +def delete(package: str, plugin: Optional[str] = None, ignore_errors=False) +``` + +Delete a plugin object from the registered plugs callables + +> [!NOTE] +> This does not 'unload' the python module. It merely makes it un-callable via plugs! + + + + +#### dump\_plugins + +```python +def dump_plugins(stream) +``` + +Write a human readable summary of all plugin callables to stream + + + + +#### summarize + +```python +def summarize() +``` + +Create a reference summary of all plugin callables in dictionary format + + + + +#### generate\_help\_rst + +```python +def generate_help_rst(stream) +``` + +Write a reference of all plugin callables in Restructured Text format + + + + +#### get\_all\_loaded\_packages + +```python +def get_all_loaded_packages() -> Dict[str, str] +``` + +Report a short summary of all loaded packages + +**Returns**: + +Dictionary of the form `{loaded_as: loaded_from, ...}` + + + +#### get\_all\_failed\_packages + +```python +def get_all_failed_packages() -> Dict[str, str] +``` + +Report those packages that did not load error free + +> [!NOTE] +> Package could fail to load +> * altogether: these package are not registered +> * partially: during initializer, finalizer functions: The package is loaded, +> but the function did not execute error-free +> +> Partially loaded packages are listed in both _PLUGINS and _PLUGINS_FAILED + +**Returns**: + +Dictionary of the form `{loaded_as: loaded_from, ...}` + + + +# jukebox.speaking\_text + +Text to Speech. Plugin to speak any given text via speaker + + + + +# jukebox.multitimer + +Multitimer Module + + + + +## MultiTimer Objects + +```python +class MultiTimer(threading.Thread) +``` + +Call a function after a specified number of seconds, repeat that iteration times + +May be cancelled during any of the wait times. +Function is called with keyword parameter 'iteration' (which decreases down to 0 for the last iteration) + +If iterations is negative, an endlessly repeating timer is created (which needs to be cancelled with cancel()) + +Initiates start and publishing by calling self.publish_callback + +Note: Inspired by threading.Timer and generally using the same API + + + + +#### cancel + +```python +def cancel() +``` + +Stop the timer if it hasn't finished all iterations yet. + + + + +## GenericTimerClass Objects + +```python +class GenericTimerClass() +``` + +Interface for plugin / RPC accessibility for a single event timer + + + + +#### \_\_init\_\_ + +```python +def __init__(name, wait_seconds: float, function, args=None, kwargs=None) +``` + +**Arguments**: + +- `wait_seconds`: The time in seconds to wait before calling function +- `function`: The function to call with args and kwargs. +- `args`: Parameters for function call +- `kwargs`: Parameters for function call + + + +#### start + +```python +@plugin.tag +def start(wait_seconds=None) +``` + +Start the timer (with default or new parameters) + + + + +#### cancel + +```python +@plugin.tag +def cancel() +``` + +Cancel the timer + + + + +#### toggle + +```python +@plugin.tag +def toggle() +``` + +Toggle the activation of the timer + + + + +#### trigger + +```python +@plugin.tag +def trigger() +``` + +Trigger the next target execution before the time is up + + + + +#### is\_alive + +```python +@plugin.tag +def is_alive() +``` + +Check if timer is active + + + + +#### get\_timeout + +```python +@plugin.tag +def get_timeout() +``` + +Get the configured time-out + +**Returns**: + +The total wait time. (Not the remaining wait time!) + + + +#### set\_timeout + +```python +@plugin.tag +def set_timeout(wait_seconds: float) +``` + +Set a new time-out in seconds. Re-starts the timer if already running! + + + + +#### publish + +```python +@plugin.tag +def publish() +``` + +Publish the current state and config + + + + +#### get\_state + +```python +@plugin.tag +def get_state() +``` + +Get the current state and config as dictionary + + + + +## GenericEndlessTimerClass Objects + +```python +class GenericEndlessTimerClass(GenericTimerClass) +``` + +Interface for plugin / RPC accessibility for an event timer call function endlessly every m seconds + + + + +## GenericMultiTimerClass Objects + +```python +class GenericMultiTimerClass(GenericTimerClass) +``` + +Interface for plugin / RPC accessibility for an event timer that performs an action n times every m seconds + + + + +#### \_\_init\_\_ + +```python +def __init__(name, + iterations: int, + wait_seconds_per_iteration: float, + callee, + args=None, + kwargs=None) +``` + +**Arguments**: + +- `iterations`: Number of times callee is called +- `wait_seconds_per_iteration`: Wait in seconds before each iteration +- `callee`: A builder class that gets instantiated once as callee(*args, iterations=iterations, **kwargs). +Then with every time out iteration __call__(*args, iteration=iteration, **kwargs) is called. +'iteration' is the current iteration count in decreasing order! +- `args`: +- `kwargs`: + + + +#### start + +```python +@plugin.tag +def start(iterations=None, wait_seconds_per_iteration=None) +``` + +Start the timer (with default or new parameters) + + + + +# jukebox.utils + +Common utility functions + + + + +#### decode\_rpc\_call + +```python +def decode_rpc_call(cfg_rpc_call: Dict) -> Optional[Dict] +``` + +Makes sure that the core rpc call parameters have valid default values in cfg_rpc_call. + +> [!IMPORTANT] +> Leaves all other parameters in cfg_action untouched or later downstream processing! + +**Arguments**: + +- `cfg_rpc_call`: RPC command as configuration entry + +**Returns**: + +A fully populated deep copy of cfg_rpc_call + + + +#### decode\_rpc\_command + +```python +def decode_rpc_command(cfg_rpc_cmd: Dict, + logger: logging.Logger = log) -> Optional[Dict] +``` + +Decode an RPC Command from a config entry. + +This means + +* Decode RPC command alias (if present) +* Ensure all RPC call parameters have valid default values + +If the command alias cannot be decoded correctly, the command is mapped to misc.empty_rpc_call +which emits a misuse warning when called +If an explicitly specified this is not done. However, it is ensured that the returned +dictionary contains all mandatory parameters for an RPC call. RPC call functions have error handling +for non-existing RPC commands and we get a clearer error message. + +**Arguments**: + +- `cfg_rpc_cmd`: RPC command as configuration entry +- `logger`: The logger to use + +**Returns**: + +A decoded, fully populated deep copy of cfg_rpc_cmd + + + +#### decode\_and\_call\_rpc\_command + +```python +def decode_and_call_rpc_command(rpc_cmd: Dict, logger: logging.Logger = log) +``` + +Convenience function combining decode_rpc_command and plugs.call_ignore_errors + + + + +#### bind\_rpc\_command + +```python +def bind_rpc_command(cfg_rpc_cmd: Dict, + dereference=False, + logger: logging.Logger = log) +``` + +Decode an RPC command configuration entry and bind it to a function + +**Arguments**: + +- `dereference`: Dereference even the call to plugs.call(...) + ``. If false, the returned function is ``plugs.call(package, plugin, method, *args, **kwargs)`` with + all checks applied at bind time + ``. If true, the returned function is ``package.plugin.method(*args, **kwargs)`` with + all checks applied at bind time. + +Setting deference to True, circumvents the dynamic nature of the plugins: the function to call + must exist at bind time and cannot change. If False, the function to call must only exist at call time. + This can be important during the initialization where package ordering and initialization means that not all + classes have been instantiated yet. With dereference=True also the plugs thread lock for serialization of calls + is circumvented. Use with care! + +**Returns**: + +Callable function w/o parameters which directly runs the RPC command +using plugs.call_ignore_errors + + + +#### rpc\_call\_to\_str + +```python +def rpc_call_to_str(cfg_rpc_call: Dict, with_args=True) -> str +``` + +Return a readable string of an RPC call config + +**Arguments**: + +- `cfg_rpc_call`: RPC call configuration entry +- `with_args`: Return string shall include the arguments of the function + + + +#### generate\_cmd\_alias\_rst + +```python +def generate_cmd_alias_rst(stream) +``` + +Write a reference of all rpc command aliases in Restructured Text format + + + + +#### generate\_cmd\_alias\_reference + +```python +def generate_cmd_alias_reference(stream) +``` + +Write a reference of all rpc command aliases in text format + + + + +#### get\_git\_state + +```python +def get_git_state() +``` + +Return git state information for the current branch + + + + +# jukebox.rpc + + + +# jukebox.rpc.client + + + +# jukebox.rpc.server + +## Remote Procedure Call Server (RPC) + +Bind to tcp and/or websocket port and translates incoming requests to procedure calls. +Avaiable procedures to call are all functions registered with the plugin package. + +The protocol is loosely based on [jsonrpc](https://www.jsonrpc.org/specification) + +But with different elements directly relating to the plugin concept and Python function argument options + + { + 'package' : str # The plugin package loaded from python module + 'plugin' : str # The plugin object to be accessed from the package + # (i.e. function or class instance) + 'method' : str # (optional) The method of the class instance + 'args' : [ ] # (optional) Positional arguments as list + 'kwargs' : { } # (optional) Keyword arguments as dictionary + 'as_thread': bool # (optional) start call in separate thread + 'id' : Any # (optional) Round-trip id for response (may not be None) + 'tsp' : Any # (optional) measure and return total processing time for + # the call request (may not be None) + } + +**Response** + +A response will ALWAYS be send, independent of presence of 'id'. This is in difference to the +jsonrpc specification. But this is a ZeroMQB REQ/REP pattern requirement! + +If 'id' is omitted, the response will be 'None'! Unless an error occurred, then the error is returned. +The absence of 'id' indicates that the requester is not interested in the response. +If present, 'id' and 'tsp' may not be None. If they are None, there are treated as if non-existing. + +**Sockets** + +Three sockets are opened + +1. TCP (on a configurable port) +2. Websocket (on a configurable port) +3. Inproc: On ``inproc://JukeBoxRpcServer`` connection from the internal app are accepted. This is indented be + call arbitrary RPC functions from plugins that provide an interface to the outside world (e.g. GPIO). By also going though + the RPC instead of calling function directly we increase thread-safety and provide easy configurability (e.g. which + button triggers what action) + + + + +## RpcServer Objects + +```python +class RpcServer() +``` + +The RPC Server Class + + + + +#### \_\_init\_\_ + +```python +def __init__(context=None) +``` + +Initialize the connections and bind to the ports + + + + +#### run + +```python +def run() +``` + +The main endless loop waiting for requests and forwarding the + +call request to the plugin module + + diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index 817298c60..db3429bcc 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -16,6 +16,8 @@ RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ make && make install ``` +[libzmq details](./libzmq.md) + ## Configuration In `jukebox.yaml` (and all other config files): diff --git a/pydoc-markdown.yml b/pydoc-markdown.yml new file mode 100644 index 000000000..62519e694 --- /dev/null +++ b/pydoc-markdown.yml @@ -0,0 +1,13 @@ +loaders: +- type: python + search_path: [./src/jukebox] +processors: + - type: filter +# skip_empty_modules: true # Uncommenting this skips also run_jukebox etc. + - type: sphinx + - type: crossref +renderer: + type: markdown + render_toc: true + filename: ./documentation/developers/docstring/README.md + render_page_title: true diff --git a/requirements.txt b/requirements.txt index ea9546315..c172a7636 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,3 +34,6 @@ flake8>=4.0.0 pytest pytest-cov mock + +# API docs generation +pydoc-markdown diff --git a/run_docgeneration.sh b/run_docgeneration.sh new file mode 100755 index 000000000..22ab8bc14 --- /dev/null +++ b/run_docgeneration.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# Runner script for pydoc-markdown to ensure +# - independent from working directory + +# Change working directory to location of script +SOURCE=${BASH_SOURCE[0]} +SCRIPT_DIR="$(dirname "$SOURCE")" +cd "$SCRIPT_DIR" || (echo "Could not change to top-level project directory" && exit 1) + +# Run pydoc-markdown +# make sure, directory exists +mkdir -p ./documentation/developers/docstring +# expects pydoc-markdown.yml at working dir +pydoc-markdown diff --git a/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py b/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py index 04459c9ea..af193a37f 100644 --- a/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py +++ b/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py @@ -35,42 +35,39 @@ class battmon_ads1015(BatteryMonitorBase.BattmonBase): - '''Battery Monitor based on a ADS1015 + """Battery Monitor based on a ADS1015 - CAUTION - WARNING - ======================================================================== - Lithium and other batteries are dangerous and must be treated with care. - Rechargeable Lithium Ion batteries are potentially hazardous and can - present a serious FIRE HAZARD if damaged, defective or improperly used. - Do not use this circuit to a lithium ion battery without expertise and - training in handling and use of batteries of this type. - Use appropriate test equipment and safety protocols during development. - - There is no warranty, this may not work as expected or at all! - ========================================================================= + > [!CAUTION] + > Lithium and other batteries are dangerous and must be treated with care. + > Rechargeable Lithium Ion batteries are potentially hazardous and can + > present a serious **FIRE HAZARD** if damaged, defective or improperly used. + > Do not use this circuit to a lithium ion battery without expertise and + > training in handling and use of batteries of this type. + > Use appropriate test equipment and safety protocols during development. + > There is no warranty, this may not work as expected or at all! This script is intended to read out the Voltage of a single Cell LiIon Battery using a CY-ADS1015 Board: - 3.3V - + - | - .----o----. - ___ | | SDA - .--------|___|---o----o---------o AIN0 o------ - | 2MΩ | | | | SCL - | .-. | | ADS1015 o------ - --- | | --- | | - Battery - 1.5MΩ| | ---100nF '----o----' - 2.9V-4.2V| '-' | | - | | | | - === === === === + 3.3V + + + | + .----o----. + ___ | | SDA + .--------|___|---o----o---------o AIN0 o------ + | 2MΩ | | | | SCL + | .-. | | ADS1015 o------ + --- | | --- | | + Battery - 1.5MΩ| | ---100nF '----o----' + 2.9V-4.2V| '-' | | + | | | | + === === === === Attention: - - the circuit is constantly draining the battery! (leak current up to: 2.1µA) - - the time between sample needs to be a minimum 1sec with this high impedance voltage divider + * the circuit is constantly draining the battery! (leak current up to: 2.1µA) + * the time between sample needs to be a minimum 1sec with this high impedance voltage divider don't use the continuous conversion method! - ''' + """ def __init__(self, cfg): super().__init__(cfg, logger) diff --git a/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py b/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py index 999a4f218..4d17f398e 100644 --- a/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py +++ b/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py @@ -4,9 +4,9 @@ This effectively does: - * register a callback with components.volume to get notified when a new sound card connects - * if that is a bluetooth device, try opening an input device with similar name using - * button listeners are run each in its own thread +* register a callback with components.volume to get notified when a new sound card connects +* if that is a bluetooth device, try opening an input device with similar name using +* button listeners are run each in its own thread """ import logging diff --git a/src/jukebox/components/controls/common/evdev_listener.py b/src/jukebox/components/controls/common/evdev_listener.py index 05b92005c..a4279afda 100644 --- a/src/jukebox/components/controls/common/evdev_listener.py +++ b/src/jukebox/components/controls/common/evdev_listener.py @@ -49,10 +49,8 @@ def _filter_by_device_name(all_devices: List[evdev.InputDevice], def find_device(device_name: str, exact_name: bool = True, mandatory_keys: Optional[Set[int]] = None) -> str: """Find an input device with device_name and mandatory keys. - Raises - - #. FileNotFoundError, if no device is found. - #. AttributeError, if device does not have the mandatory keys + :raise FileNotFoundError: if no device is found. + :raise AttributeError: if device does not have the mandatory key If multiple devices match, the first match is returned diff --git a/src/jukebox/components/gpio/gpioz/core/converter.py b/src/jukebox/components/gpio/gpioz/core/converter.py index ba9581113..849bc8e17 100644 --- a/src/jukebox/components/gpio/gpioz/core/converter.py +++ b/src/jukebox/components/gpio/gpioz/core/converter.py @@ -43,8 +43,6 @@ class VolumeToRGB: Map input :data:`0...100` to color range :data:`green...magenta` and get the color for level 50 - .. code-block:: python - conv = VolumeToRGB(100, offset=120, section=180) (r, g, b) = conv(50) diff --git a/src/jukebox/components/gpio/gpioz/core/input_devices.py b/src/jukebox/components/gpio/gpioz/core/input_devices.py index 5090762f4..bae049cc5 100644 --- a/src/jukebox/components/gpio/gpioz/core/input_devices.py +++ b/src/jukebox/components/gpio/gpioz/core/input_devices.py @@ -9,7 +9,8 @@ All callback handlers are replaced by GPIOZ callback handlers. These are usually configured by using the :func:`set_rpc_actions` each input device exhibits. -For examples how to use the devices from the configuration files, see :ref:`userguide/gpioz:Input devices` +For examples how to use the devices from the configuration files, see +[GPIO: Input Devices](../../builders/gpio.md#input-devices). """ import functools @@ -75,7 +76,7 @@ def set_rpc_actions(self, action_config) -> None: Set all input device callbacks from :attr:`action_config` :param action_config: Dictionary with one - :ref:`RPC Command ` definition entry for every device callback + [RPC Commands](../../builders/rpc-commands.md) definition entry for every device callback """ pass @@ -233,11 +234,11 @@ class LongPressButton(NameMixin, ButtonBase): """ A Button that runs a single actions only when the button is pressed long enough - :param pull_up: See `Button`_ + :param pull_up: See #Button - :param active_state: See `Button`_ + :param active_state: See #Button - :param bounce_time: See `Button`_ + :param bounce_time: See #Button :param hold_repeat: If :data:`True` repeat the :attr:`on_press` every :attr:`hold_time` seconds. Else only action is run only once independent of the length of time the button is pressed for. @@ -291,11 +292,11 @@ class ShortLongPressButton(NameMixin, ButtonBase): event. Furthermore, if there is a long hold, only the long hold action is executed - the short press action is not run in this case! - :param pull_up: See `Button`_ + :param pull_up: See #Button - :param active_state: See `Button`_ + :param active_state: See #Button - :param bounce_time: See `Button`_ + :param bounce_time: See #Button :param hold_time: The time in seconds to differentiate if it is a short or long press. If the button is released before this time, it is a short press. As soon as the button is held for :attr:`hold_time` it is a long press and the @@ -304,9 +305,9 @@ class ShortLongPressButton(NameMixin, ButtonBase): :param hold_repeat: If :data:`True` repeat the long press action every :attr:`hold_time` seconds after first long press action - :param pin_factory: See `Button`_ + :param pin_factory: See #Button - :param name: See `Button`_ + :param name: See #Button """ def __init__( self, pin=None, *, pull_up=True, active_state=None, bounce_time=None, @@ -370,11 +371,11 @@ class RotaryEncoder(NameMixin): """ A rotary encoder to run one of two actions depending on the rotation direction. - :param bounce_time: See `Button`_ + :param bounce_time: See #Button - :param pin_factory: See `Button`_ + :param pin_factory: See #Button - :param name: See `Button`_ + :param name: See #Button """ def __init__(self, a, b, *, bounce_time=None, pin_factory=None, name=None): super().__init__(name=name) @@ -442,11 +443,11 @@ class TwinButton(NameMixin): It is not necessary to configure all actions. - :param pull_up: See `Button`_ + :param pull_up: See #Button - :param active_state: See `Button`_ + :param active_state: See #Button - :param bounce_time: See `Button`_ + :param bounce_time: See #Button :param hold_time: The time in seconds to differentiate if it is a short or long press. If the button is released before this time, it is a short press. As soon as the button is held for :attr:`hold_time` it is a long press and the @@ -455,9 +456,9 @@ class TwinButton(NameMixin): :param hold_repeat: If :data:`True` repeat the long press action every :attr:`hold_time` seconds after first long press action. A long dual press is never repeated independent of this setting - :param pin_factory: See `Button`_ + :param pin_factory: See #Button - :param name: See `Button`_ + :param name: See #Button """ class StateVar(Enum): diff --git a/src/jukebox/components/gpio/gpioz/core/mock.py b/src/jukebox/components/gpio/gpioz/core/mock.py index bccd5e0e1..ae2e49e15 100644 --- a/src/jukebox/components/gpio/gpioz/core/mock.py +++ b/src/jukebox/components/gpio/gpioz/core/mock.py @@ -19,7 +19,8 @@ def patch_mock_outputs_with_callback(): This targets to represent the state in the TK GUI. Other output devices cannot be represented in the GUI and are silently ignored. - ..note:: Only for developing purposes!""" + > [!NOTE] + > Only for developing purposes!""" gpiozero.LED._write_orig = gpiozero.LED._write gpiozero.LED._write = rewrite gpiozero.LED.on_change_callback = None diff --git a/src/jukebox/components/gpio/gpioz/core/output_devices.py b/src/jukebox/components/gpio/gpioz/core/output_devices.py index 50949f82b..78f1d23da 100644 --- a/src/jukebox/components/gpio/gpioz/core/output_devices.py +++ b/src/jukebox/components/gpio/gpioz/core/output_devices.py @@ -11,7 +11,8 @@ with parameters for this device and optional parameters from another device. Unused/unsupported parameters are silently ignored. This is done to reduce the amount of coding required for connectivity functions. -For examples how to use the devices from the configuration files, see :ref:`userguide/gpioz:Output devices` +For examples how to use the devices from the configuration files, see +[GPIO: Output Devices](../../builders/gpio.md#output-devices). """ from typing import Optional, List diff --git a/src/jukebox/components/gpio/gpioz/plugin/__init__.py b/src/jukebox/components/gpio/gpioz/plugin/__init__.py index 9bc151e55..6fc9ab973 100644 --- a/src/jukebox/components/gpio/gpioz/plugin/__init__.py +++ b/src/jukebox/components/gpio/gpioz/plugin/__init__.py @@ -56,15 +56,15 @@ class ServiceIsRunningCallbacks(CallbackHandler): """ Callbacks are executed when - * Jukebox app started - * Jukebox shuts down + * Jukebox app started + * Jukebox shuts down This is intended to e.g. signal an LED to change state. This is integrated into this module because: - * we need the GPIO to control a LED (it must be available when the status callback comes) - * the plugin callback functions provide all the functionality to control the status of the LED - * which means no need to adapt other modules + * we need the GPIO to control a LED (it must be available when the status callback comes) + * the plugin callback functions provide all the functionality to control the status of the LED + * which means no need to adapt other modules """ def register(self, func: Callable[[int], None]): @@ -76,7 +76,7 @@ def register(self, func: Callable[[int], None]): .. py:function:: func(status: int) :noindex: - :param status: 1 if app started, 0 if app shuts down + :param status: 1 if app started, 0 if app shuts down """ super().register(func) diff --git a/src/jukebox/components/gpio/gpioz/plugin/connectivity.py b/src/jukebox/components/gpio/gpioz/plugin/connectivity.py index 3e5baea2d..abbcb1a32 100644 --- a/src/jukebox/components/gpio/gpioz/plugin/connectivity.py +++ b/src/jukebox/components/gpio/gpioz/plugin/connectivity.py @@ -55,11 +55,11 @@ def register_rfid_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.LED` - - :class:`components.gpio.gpioz.core.output_devices.PWMLED` - - :class:`components.gpio.gpioz.core.output_devices.RGBLED` - - :class:`components.gpio.gpioz.core.output_devices.Buzzer` - - :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + * :class:`components.gpio.gpioz.core.output_devices.LED` + * :class:`components.gpio.gpioz.core.output_devices.PWMLED` + * :class:`components.gpio.gpioz.core.output_devices.RGBLED` + * :class:`components.gpio.gpioz.core.output_devices.Buzzer` + * :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` """ def rfid_callback(card_id: str, state: RfidCardDetectState): @@ -78,9 +78,9 @@ def register_status_led_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.LED` - - :class:`components.gpio.gpioz.core.output_devices.PWMLED` - - :class:`components.gpio.gpioz.core.output_devices.RGBLED` + * :class:`components.gpio.gpioz.core.output_devices.LED` + * :class:`components.gpio.gpioz.core.output_devices.PWMLED` + * :class:`components.gpio.gpioz.core.output_devices.RGBLED` """ def set_status_led(state): @@ -101,8 +101,8 @@ def register_status_buzzer_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.Buzzer` - - :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + * :class:`components.gpio.gpioz.core.output_devices.Buzzer` + * :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` """ def set_status_buzzer(state): @@ -121,7 +121,7 @@ def register_status_tonalbuzzer_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + * :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` """ def set_status_buzzer(state): @@ -143,9 +143,9 @@ def register_audio_sink_change_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.LED` - - :class:`components.gpio.gpioz.core.output_devices.PWMLED` - - :class:`components.gpio.gpioz.core.output_devices.RGBLED` + * :class:`components.gpio.gpioz.core.output_devices.LED` + * :class:`components.gpio.gpioz.core.output_devices.PWMLED` + * :class:`components.gpio.gpioz.core.output_devices.RGBLED` """ def audio_sink_change_callback(alias, sink_name, sink_index, error_state): @@ -167,7 +167,7 @@ def register_volume_led_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.PWMLED` + * :class:`components.gpio.gpioz.core.output_devices.PWMLED` """ def audio_volume_change_callback(volume, is_min, is_max): @@ -191,8 +191,8 @@ def register_volume_buzzer_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.Buzzer` - - :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` + * :class:`components.gpio.gpioz.core.output_devices.Buzzer` + * :class:`components.gpio.gpioz.core.output_devices.TonalBuzzer` """ def set_volume_buzzer(volume, is_min, is_max): @@ -210,7 +210,7 @@ def register_volume_rgbled_callback(device): Compatible devices: - - :class:`components.gpio.gpioz.core.output_devices.RGBLED` + * :class:`components.gpio.gpioz.core.output_devices.RGBLED` """ volume_to_rgb = VolumeToRGB(100, 120, 180) diff --git a/src/jukebox/components/hostif/linux/__init__.py b/src/jukebox/components/hostif/linux/__init__.py index 582074413..32dfded08 100644 --- a/src/jukebox/components/hostif/linux/__init__.py +++ b/src/jukebox/components/hostif/linux/__init__.py @@ -103,7 +103,8 @@ def jukebox_is_service(): def is_any_jukebox_service_active(): """Check if a Jukebox service is running - .. note:: Does not have the be the current app, that is running as a service! + > [!NOTE] + > Does not have the be the current app, that is running as a service! """ ret = subprocess.run(["systemctl", "--user", "show", "jukebox-daemon", "--property", "ActiveState", "--value"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False, diff --git a/src/jukebox/components/jingle/__init__.py b/src/jukebox/components/jingle/__init__.py index 43e55cd74..940015dd5 100644 --- a/src/jukebox/components/jingle/__init__.py +++ b/src/jukebox/components/jingle/__init__.py @@ -56,11 +56,12 @@ def initialize(): def play(filename): """Play the jingle using the configured jingle service - Note: This runs in a separate thread. And this may cause troubles - when changing the volume level before - and after the sound playback: There is nothing to prevent another - thread from changing the volume and sink while playback happens - and afterwards we change the volume back to where it was before! + > [!NOTE] + > This runs in a separate thread. And this may cause troubles + > when changing the volume level before + > and after the sound playback: There is nothing to prevent another + > thread from changing the volume and sink while playback happens + > and afterwards we change the volume back to where it was before! There is no way around this dilemma except for not running the jingle as a separate thread. Currently (as thread) even the RPC is started before the sound diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index a2dbc914a..9073a9b4a 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -367,8 +367,9 @@ def replay_if_stopped(self): """ Re-start playing the last-played folder unless playlist is still playing - .. note:: To me this seems much like the behaviour of play, - but we keep it as it is specifically implemented in box 2.X""" + > [!NOTE] + > To me this seems much like the behaviour of play, + > but we keep it as it is specifically implemented in box 2.X""" with self.mpd_lock: if self.mpd_status['state'] == 'stop': self.play_folder(self.music_player_status['player_status']['last_played_folder']) diff --git a/src/jukebox/components/playermpd/playcontentcallback.py b/src/jukebox/components/playermpd/playcontentcallback.py index a60452a23..ce5a1b8fb 100644 --- a/src/jukebox/components/playermpd/playcontentcallback.py +++ b/src/jukebox/components/playermpd/playcontentcallback.py @@ -27,8 +27,8 @@ def register(self, func: Callable[[str, STATE], None]): .. py:function:: func(folder: str, state: STATE) :noindex: - :param folder: relativ path to folder to play - :param state: indicator of the state inside the calling + :param folder: relativ path to folder to play + :param state: indicator of the state inside the calling """ super().register(func) diff --git a/src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py b/src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py index b33399b0a..a4481efd6 100644 --- a/src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py +++ b/src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py @@ -48,7 +48,7 @@ class ReaderClass(ReaderBaseClass): All the required interfaces are implemented there. Put your code into these functions (see below for more information) - - __init__ + - `__init__` - read_card - cleanup - stop @@ -101,10 +101,11 @@ def stop(self): This function is called before cleanup is called. - .. note: This is usually called from a different thread than the reader's thread! And this is the reason for the - two-step exit strategy. This function works across threads to indicate to the reader that is should stop attempt - to read a card. Once called, the function read_card will not be called again. When the reader thread exits - cleanup is called from the reader thread itself. + > [!NOTE] + > This is usually called from a different thread than the reader's thread! And this is the reason for the + > two-step exit strategy. This function works across threads to indicate to the reader that is should stop attempt + > to read a card. Once called, the function read_card will not be called again. When the reader thread exits + > cleanup is called from the reader thread itself. """ self._keep_running = False diff --git a/src/jukebox/components/rfid/reader/__init__.py b/src/jukebox/components/rfid/reader/__init__.py index db0ccb1da..37d4a363d 100644 --- a/src/jukebox/components/rfid/reader/__init__.py +++ b/src/jukebox/components/rfid/reader/__init__.py @@ -41,8 +41,8 @@ def register(self, func: Callable[[str, RfidCardDetectState], None]): .. py:function:: func(card_id: str, state: int) :noindex: - :param card_id: Card ID - :param state: See :class:`RfidCardDetectState` + :param card_id: Card ID + :param state: See #RfidCardDetectState """ super().register(func) @@ -52,7 +52,7 @@ def run_callbacks(self, card_id: str, state: RfidCardDetectState): #: Callback handler instance for rfid_card_detect_callbacks events. -#: See :class:`RfidCardDetectCallbacks` +#: See #RfidCardDetectCallbacks rfid_card_detect_callbacks: RfidCardDetectCallbacks = RfidCardDetectCallbacks('rfid_card_detect_callbacks', log) diff --git a/src/jukebox/components/rpc_command_alias.py b/src/jukebox/components/rpc_command_alias.py index e56727ff4..f6e238559 100644 --- a/src/jukebox/components/rpc_command_alias.py +++ b/src/jukebox/components/rpc_command_alias.py @@ -1,7 +1,7 @@ """ This file provides definitions for RPC command aliases -See :ref:`userguide/rpc_commands` +See [RPC Commands](../../builders/rpc-commands.md) """ # -------------------------------------------------------------- diff --git a/src/jukebox/components/volume/__init__.py b/src/jukebox/components/volume/__init__.py index 6653baa77..ccc4873d7 100644 --- a/src/jukebox/components/volume/__init__.py +++ b/src/jukebox/components/volume/__init__.py @@ -2,33 +2,35 @@ # Copyright (c) See file LICENSE in project root folder """PulseAudio Volume Control Plugin Package -Features +## Features - * Volume Control - * Two outputs - * Watcher thread on volume / output change +* Volume Control +* Two outputs +* Watcher thread on volume / output change -Publishes +## Publishes - * volume.level - * volume.sink +* volume.level +* volume.sink -PulseAudio References +## PulseAudio References -https://brokkr.net/2018/05/24/down-the-drain-the-elusive-default-pulseaudio-sink/ + Check fallback device (on device de-connect): -$ pacmd list-sinks | grep -e 'name:' -e 'index' + $ pacmd list-sinks | grep -e 'name:' -e 'index' -Integration + +## Integration Pulse Audio runs as a user process. Processes who want to communicate / stream to it must also run as a user process. -This means must also run as user process, as described in :ref:`userguide/system:Music Player Daemon (MPD)` +This means must also run as user process, as described in +[Music Player Daemon](../../builders/system.md#music-player-daemon-mpd). -Misc +## Misc PulseAudio may switch the sink automatically to a connecting bluetooth device depending on the loaded module with name module-switch-on-connect. On RaspianOS Bullseye, this module is not part of the default configuration @@ -36,27 +38,25 @@ If the module gets loaded it conflicts with the toggle on connect and the selected primary / secondary outputs from the Jukebox. Remove it from the configuration! -.. code-block:: text - ### Use hot-plugged devices like Bluetooth or USB automatically (LP: #1702794) ### not available on PI? .ifexists module-switch-on-connect.so load-module module-switch-on-connect .endif -Why PulseAudio? +## Why PulseAudio? The audio configuration of the system is one of those topics, which has a myriad of options and possibilities. Every system is different and PulseAudio unifies these and makes our life easier. Besides, it is only option to support Bluetooth at the moment. -Callbacks: +## Callbacks: The following callbacks are provided. Register callbacks with these adder functions (see their documentation for details): - #. :func:`add_on_connect_callback` - #. :func:`add_on_output_change_callbacks` - #. :func:`add_on_volume_change_callback` +1. :func:`add_on_connect_callback` +2. :func:`add_on_output_change_callbacks` +3. :func:`add_on_volume_change_callback` """ import collections import logging @@ -116,10 +116,10 @@ def register(self, func: Callable[[str, str], None]): .. py:function:: func(card_driver: str, device_name: str) :noindex: - :param card_driver: The PulseAudio card driver module, - e.g. :data:`module-bluez5-device.c` or :data:`module-alsa-card.c` - :param device_name: The sound card device name as reported - in device properties + :param card_driver: The PulseAudio card driver module, + e.g. :data:`module-bluez5-device.c` or :data:`module-alsa-card.c` + :param device_name: The sound card device name as reported + in device properties """ super().register(func) @@ -140,7 +140,7 @@ def __init__(self): # For the callback handler: We use the context lock only explicitly for registering new functions # When the callbacks are run, it happens from inside the pulse_monitor which an already acquired lock #: Callback handler instance for on_connect_callbacks events. - #: See :class:`PulseMonitor.SoundCardConnectCallbacks` + #: See #PulseMonitor.SoundCardConnectCallbacks self.on_connect_callbacks: PulseMonitor.SoundCardConnectCallbacks = PulseMonitor.SoundCardConnectCallbacks( 'on_connect_callback', logger, context=self) @@ -149,10 +149,11 @@ def toggle_on_connect(self): """Returns :data:`True` if the sound card shall be changed when a new card connects/disconnects. Setting this property changes the behavior. - .. note:: A new card is always assumed to be the secondary device from the audio configuration. - At the moment there is no check it actually is the configured device. This means any new - device connection will initiate the toggle. This, however, is no real issue as the RPi's audio - system will be relatively stable once setup + > [!NOTE] + > A new card is always assumed to be the secondary device from the audio configuration. + > At the moment there is no check it actually is the configured device. This means any new + > device connection will initiate the toggle. This, however, is no real issue as the RPi's audio + > system will be relatively stable once setup """ return self._toggle_on_connect @@ -282,8 +283,6 @@ class PulseVolumeControl: When accessing the pulse library, it needs to be put into a special state. Which is ensured by the context manager - .. code-block: python - with pulse_monitor as pulse ... @@ -309,12 +308,12 @@ def register(self, func: Callable[[str, str, int, int], None]): .. py:function:: func(sink_name: str, alias: str, sink_index: int, error_state: int) :noindex: - :param sink_name: PulseAudio's sink name - :param alias: The alias for :attr:`sink_name` - :param sink_index: The index of the sink in the configuration list - :param error_state: 1 if there was an attempt to change the output - but an error occurred. Above parameters always give the now valid sink! - If a sink change is successful, it is 0. + :param sink_name: PulseAudio's sink name + :param alias: The alias for :attr:`sink_name` + :param sink_index: The index of the sink in the configuration list + :param error_state: 1 if there was an attempt to change the output + but an error occurred. Above parameters always give the now valid sink! + If a sink change is successful, it is 0. """ super().register(func) @@ -338,9 +337,9 @@ def register(self, func: Callable[[int, bool, bool], None]): .. py:function:: func(volume: int, is_min: bool, is_max: bool) :noindex: - :param volume: Volume level - :param is_min: 1, if volume level is minimum, else 0 - :param is_max: 1, if volume level is maximum, else 0 + :param volume: Volume level + :param is_min: 1, if volume level is minimum, else 0 + :param is_max: 1, if volume level is maximum, else 0 """ super().register(func) @@ -359,12 +358,12 @@ def __init__(self, sink_list: List[PulseAudioSinkClass]): # When the callbacks are run, it happens from inside the pulse_control which an already acquired lock #: Callback handler instance for on_output_change_callbacks events. - #: See :class:`PulseVolumeControl.OutputChangeCallbackHandler` + #: See #PulseVolumeControl.OutputChangeCallbackHandler self.on_output_change_callbacks = PulseVolumeControl.OutputChangeCallbackHandler( 'on_output_change_callbacks', logger, context=pulse_monitor) #: Callback handler instance for on_output_change_callbacks events. - #: See :class:`PulseVolumeControl.OutputVolumeCallbackHandler` + #: See #PulseVolumeControl.OutputVolumeCallbackHandler self.on_volume_change_callbacks = PulseVolumeControl.OutputVolumeCallbackHandler( 'on_volume_change_callbacks', logger, context=pulse_monitor) diff --git a/src/jukebox/jukebox/cfghandler.py b/src/jukebox/jukebox/cfghandler.py index 3482a1b42..8108f1d33 100644 --- a/src/jukebox/jukebox/cfghandler.py +++ b/src/jukebox/jukebox/cfghandler.py @@ -236,8 +236,9 @@ def is_modified(self) -> bool: """ Check if the data has changed since the last load/store - .. note: This relies on the *__str__* representation of the underlying data structure - In case of ruamel, this ignores comments and only looks at the data + > [!NOTE] + > This relies on the *__str__* representation of the underlying data structure + > In case of ruamel, this ignores comments and only looks at the data """ with self._lock: is_modified_value = self._hash != hashlib.md5(self._data.__str__().encode('utf8')).digest() diff --git a/src/jukebox/jukebox/playlistgenerator.py b/src/jukebox/jukebox/playlistgenerator.py index db64d3eff..b9f0223c6 100755 --- a/src/jukebox/jukebox/playlistgenerator.py +++ b/src/jukebox/jukebox/playlistgenerator.py @@ -12,8 +12,6 @@ An directory may contain a mixed set of files and multiple ``*.txt`` files, e.g. -.. code-block:: bash - 01-livestream.txt 02-livestream.txt music.mp3 diff --git a/src/jukebox/jukebox/plugs.py b/src/jukebox/jukebox/plugs.py index afad5da28..5e4a95f21 100644 --- a/src/jukebox/jukebox/plugs.py +++ b/src/jukebox/jukebox/plugs.py @@ -14,39 +14,39 @@ If you want to provide additional functionality to the same feature (probably even for run-time switching) you can implement a Factory Pattern using this package. Take a look at volume.py as an example. -**Example:** Decorate a function for auto-registering under it's own name:: +**Example:** Decorate a function for auto-registering under it's own name: import jukebox.plugs as plugs @plugs.register def func1(param): pass -**Example:** Decorate a function for auto-registering under a new name:: +**Example:** Decorate a function for auto-registering under a new name: @plugs.register(name='better_name') def func2(param): pass -**Example:** Register a function during run-time under it's own name:: +**Example:** Register a function during run-time under it's own name: def func3(param): pass plugs.register(func3) -**Example:** Register a function during run-time under a new name:: +**Example:** Register a function during run-time under a new name: def func4(param): pass plugs.register(func4, name='other_name', package='other_package') **Example:** Decorate a class for auto registering during initialization, -including all methods (see _register_class for more info):: +including all methods (see _register_class for more info): @plugs.register(auto_tag=True) class MyClass1: pass -**Example:** Register a class instance, from which only report is a callable method through the plugs interface:: +**Example:** Register a class instance, from which only report is a callable method through the plugs interface: class MyClass2: @plugs.tag @@ -57,20 +57,17 @@ def report(self): Naming convention: -package - 1. Either a python package - 2. or a plugin package (which is the python package but probably loaded under a different name inside plugs) - -plugin - 1. An object from the package that can be accessed through the plugs call function (i.e. a function or a class instance) - 2. The string name to above object - -name - The string name of the plugin object for registration - -method - 1. In case the object is a class instance a bound method to call from the class instance - 2. The string name to above object +* package + * Either a python package + * or a plugin package (which is the python package but probably loaded under a different name inside plugs) +* plugin + * An object from the package that can be accessed through the plugs call function (i.e. a function or a class instance) + * The string name to above object +* name + * The string name of the plugin object for registration +* method + * In case the object is a class instance a bound method to call from the class instance + * The string name to above object """ @@ -405,15 +402,13 @@ def register(plugin: Optional[Callable] = None, *, 3. ``@plugs.register(auto_tag=bool)``: decorator for a class with 1 arguments 4. ``@plugs.register(name=name, package=package)``: decorator for a function with 1 or 2 arguments 5. ``plugs.register(plugin, name=name, package=package)``: run-time registration of - * function * bound method * class instance For more documentation see the functions - - * :func:`_register_obj` - * :func:`_register_class` + * :func:`_register_obj` + * :func:`_register_class` See the examples in Module :mod:`plugs` how to use this decorator / function @@ -504,9 +499,10 @@ def atexit(func: Callable[[int], Any]) -> Callable[[int], Any]: """ Decorator for functions that shall be called by the plugs package directly after at exit of program. - .. important:: There is no automatism as in atexit.atexit. The function plugs.shutdown() must be explicitly called - during the shutdown procedure of your program. This is by design, so you can choose the exact situation in your - shutdown handler. + > [!IMPORTANT] + > There is no automatism as in atexit.atexit. The function plugs.shutdown() must be explicitly called + > during the shutdown procedure of your program. This is by design, so you can choose the exact situation in your + > shutdown handler. The atexit-functions are called with a single integer argument, which is passed down from plugin.exit(int) It is intended for passing down the signal number that initiated the program termination @@ -527,11 +523,11 @@ def load(package: str, load_as: Optional[str] = None, prefix: Optional[str] = No """ Loads a python package as plugin package - Executes a regular python package load. That means a potentially existing __init__.py is executed. - Decorator @register can by used to register functions / classes / class istances as plugin callable - Decorator @initializer can be used to tag functions that shall be called after package loading - Decorator @finalizer can be used to tag functions that shall be called after ALL plugin packges have been loaded - Instead of using @initializer, you may of course use __init__.py + Executes a regular python package load. That means a potentially existing `__init__.py` is executed. + Decorator `@register` can by used to register functions / classes / class istances as plugin callable + Decorator `@initializer` can be used to tag functions that shall be called after package loading + Decorator `@finalizer` can be used to tag functions that shall be called after ALL plugin packges have been loaded + Instead of using `@initializer`, you may of course use `__init__.py` Python packages may be loaded under a different plugs package name. Python packages must be unique and the name under which they are loaded as plugin package also. @@ -723,9 +719,9 @@ def call(package: str, plugin: str, method: Optional[str] = None, *, Calls are serialized by a thread lock. The thread lock is shared with call_ignore_errors. - .. note:: - There is no logger in this function as they all belong up-level where the exceptions are handled. - If you want logger messages instead of exceptions, use :func:`call_ignore_errors` + > [!NOTE] + > There is no logger in this function as they all belong up-level where the exceptions are handled. + > If you want logger messages instead of exceptions, use :func:`call_ignore_errors` :param package: Name of the plugin package in which to look for function/class instance :param plugin: Function name or instance name of a class @@ -824,7 +820,9 @@ def loaded_as(module_name: str) -> str: def delete(package: str, plugin: Optional[str] = None, ignore_errors=False): """Delete a plugin object from the registered plugs callables - Note: This does not 'unload' the python module. It merely makes it un-callable via plugs!""" + > [!NOTE] + > This does not 'unload' the python module. It merely makes it un-callable via plugs! + """ with _lock_module: if exists(package, plugin): if plugin is None: @@ -971,13 +969,13 @@ def get_all_loaded_packages() -> Dict[str, str]: def get_all_failed_packages() -> Dict[str, str]: """Report those packages that did not load error free - .. note:: Package could fail to load - - 1. altogether: these package are not registered - 2. partially: during initializer, finalizer functions: The package is loaded, - but the function did not execute error-free - - Partially loaded packages are listed in both _PLUGINS and _PLUGINS_FAILED + > [!NOTE] + > Package could fail to load + > * altogether: these package are not registered + > * partially: during initializer, finalizer functions: The package is loaded, + > but the function did not execute error-free + > + > Partially loaded packages are listed in both _PLUGINS and _PLUGINS_FAILED :return: Dictionary of the form `{loaded_as: loaded_from, ...}`""" with _lock_module: diff --git a/src/jukebox/jukebox/publishing/server.py b/src/jukebox/jukebox/publishing/server.py index 7db5b5846..66729da6e 100644 --- a/src/jukebox/jukebox/publishing/server.py +++ b/src/jukebox/jukebox/publishing/server.py @@ -1,31 +1,28 @@ """ -Publishing Server -******************** +## Publishing Server The common publishing server for the entire Jukebox using ZeroMQ -Structure ----------------- - -.. code-block:: text - - +-----------------------+ - | functional interface | Publisher - | | - functional interface for single Thread - | PUB | - sends data to publisher (and thus across threads) - +-----------------------+ - | (1) - v - +-----------------------+ - | SUB (bind) | PublishServer - | | - Last Value (LV) Cache - | XPUB (bind) | - Subscriber notification and LV resend - +-----------------------+ - independent thread - | (2) - v - -Connection (1): Internal connection - Internal connection only - do not use (no, not even inside this App for you own plugins - always bind to the PublishServer) +### Structure + + +-----------------------+ + | functional interface | Publisher + | | - functional interface for single Thread + | PUB | - sends data to publisher (and thus across threads) + +-----------------------+ + | (1) + v + +-----------------------+ + | SUB (bind) | PublishServer + | | - Last Value (LV) Cache + | XPUB (bind) | - Subscriber notification and LV resend + +-----------------------+ - independent thread + | (2) + v + +#### Connection (1): Internal connection + +Internal connection only - do not use (no, not even inside this App for you own plugins - always bind to the PublishServer) Protocol: Multi-part message @@ -39,10 +36,11 @@ Usually empty, i.e. ``b''``. If not empty the message is treated as command for the PublishServer and the message is not forwarded to the outside. This third part of the message is never forwarded -Connection (2): External connection - Upon connection of a new subscriber, the entire current state is resend from cache to ALL subscribers! - Subscribers must subscribe to topics. Topics are treated as topic trees! Subscribing to a root tree will - also get you all the branch topics. To get everything, subscribe to ``b''`` +#### Connection (2): External connection + +Upon connection of a new subscriber, the entire current state is resend from cache to ALL subscribers! +Subscribers must subscribe to topics. Topics are treated as topic trees! Subscribing to a root tree will +also get you all the branch topics. To get everything, subscribe to ``b''`` Protocol: Multi-part message @@ -52,24 +50,22 @@ Part 2: Payload or Message in json serialization If empty (i.e. b''), it means the subscriber must delete this key locally (not valid anymore) -Why? Why? -------------- +### Why? Why? -Check out the `ZeroMQ Documentation `_ +Check out the [ZeroMQ Documentation](https://zguide.zeromq.org/docs/chapter5) for why you need a proxy in a good design. For use case, we made a few simplifications -Design Rationales -------------------- +### Design Rationales -* "If you need `millions of messages per second `_ +* "If you need [millions of messages per second](https://zguide.zeromq.org/docs/chapter5/#Pros-and-Cons-of-Pub-Sub) sent to thousands of points, - you’ll appreciate pub-sub a lot more than if you need a few messages a second sent to a handful of recipients." + you'll appreciate pub-sub a lot more than if you need a few messages a second sent to a handful of recipients." * "lower-volume network with a few dozen subscribers and a limited number of topics, we can use TCP and then - the `XSUB and XPUB `_" -* "Let’s imagine `our feed has an average of 100,000 100-byte messages a second - `_ [...]. + the [XSUB and XPUB](https://zguide.zeromq.org/docs/chapter5/#Last-Value-Caching)" +* "Let's imagine [our feed has an average of 100,000 100-byte messages a + second](https://zguide.zeromq.org/docs/chapter5/#High-Speed-Subscribers-Black-Box-Pattern) [...]. While 100K messages a second is easy for a ZeroMQ application, ..." **But we have:** @@ -100,8 +96,7 @@ * Publisher plugin is first plugin to be loaded * Due to Publisher - PublisherServer structure no further sequencing required -Plugin interactions and usage ------------------------------- +### Plugin interactions and usage RPC can trigger through function call in components/publishing plugin that @@ -110,23 +105,24 @@ Plugins publishing state information should publish initial state at @plugin.finalize -.. important:: Do not direclty instantiate the Publisher in your plugin module. Only one Publisher is - required per thread. But the publisher instance **must** be thread-local! - Always go through :func:`publishing.get_publisher()`. +> [!IMPORTANT] +> Do not direclty instantiate the Publisher in your plugin module. Only one Publisher is +> required per thread. But the publisher instance **must** be thread-local! +> Always go through :func:`publishing.get_publisher()`. **Sockets** Three sockets are opened: -#. TCP (on a configurable port) -#. Websocket (on a configurable port) -#. Inproc: On ``inproc://PublisherToProxy`` all topics are published app-internally. This can be used for plugin modules +1. TCP (on a configurable port) +2. Websocket (on a configurable port) +3. Inproc: On ``inproc://PublisherToProxy`` all topics are published app-internally. This can be used for plugin modules that want to know about the current state on event based updates. **Further ZeroMQ References:** -* `Working with Messages `_ -* `Multiple Threads `_ +* [Working with Messages](https://zguide.zeromq.org/docs/chapter2/#Working-with-Messages) +* [Multiple Threads](https://zguide.zeromq.org/docs/chapter2/#Multithreading-with-ZeroMQ) """ # Developer's notes: @@ -190,7 +186,7 @@ class PublishServer(threading.Thread): Handles new subscriptions by sending out the entire cached state to **all** subscribers - The code is structures using a `Reactor Pattern `_ + The code is structures using a [Reactor Pattern](https://zguide.zeromq.org/docs/chapter5/#Using-a-Reactor) """ def __init__(self, tcp_port, websocket_port): super().__init__(name='PubServer') @@ -271,9 +267,9 @@ class Publisher: """ The publisher that provides the functional interface to the application - .. note:: - * An instance must not be shared across threads! - * One instance per thread is enough + > [!NOTE] + > * An instance must not be shared across threads! + > * One instance per thread is enough """ def __init__(self, check_thread_owner=True): diff --git a/src/jukebox/jukebox/rpc/server.py b/src/jukebox/jukebox/rpc/server.py index 8615bced7..b7e55b243 100644 --- a/src/jukebox/jukebox/rpc/server.py +++ b/src/jukebox/jukebox/rpc/server.py @@ -1,17 +1,14 @@ # -*- coding: utf-8 -*- """ -Remote Procedure Call Server (RPC) -************************************* +## Remote Procedure Call Server (RPC) Bind to tcp and/or websocket port and translates incoming requests to procedure calls. Avaiable procedures to call are all functions registered with the plugin package. -To protocol is loosely based on `jsonrpc `_ +The protocol is loosely based on [jsonrpc](https://www.jsonrpc.org/specification) But with different elements directly relating to the plugin concept and Python function argument options -.. code-block:: yaml - { 'package' : str # The plugin package loaded from python module 'plugin' : str # The plugin object to be accessed from the package @@ -38,9 +35,9 @@ Three sockets are opened -#. TCP (on a configurable port) -#. Websocket (on a configurable port) -#. Inproc: On ``inproc://JukeBoxRpcServer`` connection from the internal app are accepted. This is indented be +1. TCP (on a configurable port) +2. Websocket (on a configurable port) +3. Inproc: On ``inproc://JukeBoxRpcServer`` connection from the internal app are accepted. This is indented be call arbitrary RPC functions from plugins that provide an interface to the outside world (e.g. GPIO). By also going though the RPC instead of calling function directly we increase thread-safety and provide easy configurability (e.g. which button triggers what action) diff --git a/src/jukebox/jukebox/utils.py b/src/jukebox/jukebox/utils.py index 9be97b6db..dbd647490 100644 --- a/src/jukebox/jukebox/utils.py +++ b/src/jukebox/jukebox/utils.py @@ -17,7 +17,8 @@ def decode_rpc_call(cfg_rpc_call: Dict) -> Optional[Dict]: """Makes sure that the core rpc call parameters have valid default values in cfg_rpc_call. - .. important: Leaves all other parameters in cfg_action untouched or later downstream processing! + > [!IMPORTANT] + > Leaves all other parameters in cfg_action untouched or later downstream processing! :param cfg_rpc_call: RPC command as configuration entry :return: A fully populated deep copy of cfg_rpc_call @@ -41,8 +42,8 @@ def decode_rpc_command(cfg_rpc_cmd: Dict, logger: logging.Logger = log) -> Optio This means - * Decode RPC command alias (if present) - * Ensure all RPC call parameters have valid default values + * Decode RPC command alias (if present) + * Ensure all RPC call parameters have valid default values If the command alias cannot be decoded correctly, the command is mapped to misc.empty_rpc_call which emits a misuse warning when called diff --git a/src/jukebox/misc/loggingext.py b/src/jukebox/misc/loggingext.py index 9328cfea8..36b040339 100644 --- a/src/jukebox/misc/loggingext.py +++ b/src/jukebox/misc/loggingext.py @@ -1,7 +1,6 @@ """ -############## -Logger -############## +## Logger + We use a hierarchical Logger structure based on pythons logging module. It can be finely configured with a yaml file. The top-level logger is called 'jb' (to make it short). In any module you may simple create a child-logger at any hierarchy @@ -9,25 +8,27 @@ Hierarchy separator is the '.'. If the logger already exits, getLogger will return a reference to the same, else it will be created on the spot. -:Example: How to get logger and log away at your heart's content: +Example: How to get logger and log away at your heart's content: + >>> import logging >>> logger = logging.getLogger('jb.awesome_module') >>> logger.info('Started general awesomeness aura') -Example: YAML snippet, setting WARNING as default level everywhere and DEBUG for jb.awesome_module:: -`` -loggers: - jb: - level: WARNING - handlers: [console, debug_file_handler, error_file_handler] - propagate: no - jb.awesome_module: - level: DEBUG -`` - -.. note:: -The name (and hierarchy path) of the logger can be arbitrary and must not necessarily match the module name (still makes sense) -There can be multiple loggers per module, e.g. for special classes, to further control the amount of log output +Example: YAML snippet, setting WARNING as default level everywhere and DEBUG for jb.awesome_module: + + loggers: + jb: + level: WARNING + handlers: [console, debug_file_handler, error_file_handler] + propagate: no + jb.awesome_module: + level: DEBUG + + +> [!NOTE] +> The name (and hierarchy path) of the logger can be arbitrary and must not necessarily match the module name (still makes +> sense). +> There can be multiple loggers per module, e.g. for special classes, to further control the amount of log output """ import sys import logging @@ -80,21 +81,22 @@ def filter(self, record): class PubStream: - """" + """ Stream handler wrapper around the publisher for logging.StreamHandler Allows logging to send all log information (based on logging configuration) to the Publisher. - ATTENTION: This can lead to recursions! - - Recursions come up when - (a) Publish.send / PublishServer.send also emits logs, which cause a another send, which emits a log, - which causes a send, ..... - (b) Publisher initialization emits logs, which need a Publisher instance to send logs + > [!CAUTION] + > This can lead to recursions! + > Recursions come up when + > * Publish.send / PublishServer.send also emits logs, which cause a another send, which emits a log, + > which causes a send, ..... + > * Publisher initialization emits logs, which need a Publisher instance to send logs - IMPORTANT: To avoid endless recursions: The creation of a Publisher MUST NOT generate any log messages! Nor any of the - functions in the send-function stack! + > [!IMPORTANT] + > To avoid endless recursions: The creation of a Publisher MUST NOT generate any log messages! Nor any of the + > functions in the send-function stack! """ def __init__(self): self._topic = 'core.logger' diff --git a/src/jukebox/run_configure_audio.py b/src/jukebox/run_configure_audio.py index 6bb0e70f6..93f0a4c6a 100755 --- a/src/jukebox/run_configure_audio.py +++ b/src/jukebox/run_configure_audio.py @@ -5,7 +5,7 @@ Will also setup equalizer and mono down mixer in the pulseaudio config file. Run this once after installation. Can be re-run at any time to change the settings. -For more information see :ref:`userguide/audio:Audio Configuration`. +For more information see [Audio Configuration](../../builders/audio.md#audio-configuration). """ import os import argparse diff --git a/src/jukebox/run_jukebox.py b/src/jukebox/run_jukebox.py index 0735e2b8e..789e57aca 100755 --- a/src/jukebox/run_jukebox.py +++ b/src/jukebox/run_jukebox.py @@ -5,11 +5,11 @@ Usually this runs as a service, which is started automatically after boot-up. At times, it may be necessary to restart the service. For example after a configuration change. Not all configuration changes can be applied on-the-fly. -See :ref:`userguide/configuration:Jukebox Configuration`. +See [Jukebox Configuration](../../builders/configuration.md#jukebox-configuration). For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. -See :ref:`userguide/troubleshooting:Troubleshooting`. +See [Troubleshooting](../../builders/troubleshooting.md). """ import os.path import argparse diff --git a/src/jukebox/run_register_rfid_reader.py b/src/jukebox/run_register_rfid_reader.py index 3aa69735e..18a1614d8 100755 --- a/src/jukebox/run_register_rfid_reader.py +++ b/src/jukebox/run_register_rfid_reader.py @@ -3,10 +3,11 @@ Setup tool to configure the RFID Readers. Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change -the settings. For more information see :ref:`rfid/rfid:RFID Readers`. +the settings. For more information see [RFID Readers](../rfid/README.md). -.. note:: This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). - Any manual modifications to the settings will have to be re-applied +> [!NOTE] +> This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). +> Any manual modifications to the settings will have to be re-applied """ import os diff --git a/src/jukebox/run_rpc_tool.py b/src/jukebox/run_rpc_tool.py index 1593d7796..4bd834e12 100755 --- a/src/jukebox/run_rpc_tool.py +++ b/src/jukebox/run_rpc_tool.py @@ -11,7 +11,7 @@ The list of available commands is fetched from the running Jukebox service. .. todo: - - kwargs support + - kwargs support """ From 6f0976443980d787473085fc9d8feedd659280b4 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Thu, 18 Jan 2024 09:19:32 +0100 Subject: [PATCH 072/121] deactivate kioskmode installation option on armv6 devices (#2217) * deactivate kiosk mode in armv6 devices suggestion from Issue 2209 * added "disabling" information to message * change message to be more clear --- installation/routines/customize_options.sh | 38 +++++++++++++++------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index 3e94b07dc..c903df189 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -208,25 +208,39 @@ Would you like to install the Web App? [Y/n]" } _option_kiosk_mode() { - # ENABLE_KIOSK_MODE - clear_c - print_c "----------------------- KIOSK MODE ---------------------- + # ENABLE_KIOSK_MODE + clear_c + print_c "----------------------- KIOSK MODE ----------------------" + if [[ $(get_architecture) == "armv6" ]]; then + + print_c " +Due to limited resources the kiosk mode is not supported +on Raspberry Pi 1 or Zero 1 ('ARMv6' models). +Kiosk mode will not be installed. + +Press enter to continue." + read + ENABLE_KIOSK_MODE=false + else + print_c " If you have a screen attached to your RPi, this will launch the Web App right after boot. It will only install the necessary xserver dependencies and not the entire RPi desktop environment. Would you like to enable the Kiosk Mode? [y/N]" - read -r response - case "$response" in - [yY][eE][sS]|[yY]) - ENABLE_KIOSK_MODE=true - ;; - *) - ;; - esac - log "ENABLE_KIOSK_MODE=${ENABLE_KIOSK_MODE}" + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + ENABLE_KIOSK_MODE=true + ;; + *) + ;; + esac + fi + + log "ENABLE_KIOSK_MODE=${ENABLE_KIOSK_MODE}" } _options_update_raspi_os() { From f6db16098f1f41b18842f5af15b38d2911737e6d Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:12:45 +0100 Subject: [PATCH 073/121] Update docs (#2216) * minor fixes * add "Pre-install preparation / workarounds" section add "Workaround for network related features on bookworm" * add "Workaround for 64-bit Kernels" harmonize and update description / message * minor fix * Update warning block as alerts are not supported inside of "details" * minor fix * fix Raspberry Pi OS name Co-authored-by: s-martin * fix Raspberry Pi OS name * fix typo * added link to installation * updates from reviews * fix file for synchronisation doc. moved to components * add docs for samba * Apply suggestions from code review Co-authored-by: s-martin * restructure builders readme --------- Co-authored-by: s-martin --- documentation/README.md | 3 +- documentation/builders/README.md | 22 ++++++---- .../synchronisation/rfidcards.md} | 10 ++--- documentation/builders/configuration.md | 6 +-- documentation/builders/installation.md | 44 +++++++++++++++++-- documentation/builders/samba.md | 18 ++++++++ documentation/builders/update.md | 3 +- documentation/developers/README.md | 2 +- documentation/developers/docstring/README.md | 2 +- installation/includes/02_helpers.sh | 4 +- installation/install-jukebox.sh | 10 ++--- .../hardware/fake_reader_gui/requirements.txt | 2 +- src/jukebox/components/volume/__init__.py | 4 +- 13 files changed, 93 insertions(+), 37 deletions(-) rename documentation/builders/{rfid.md => components/synchronisation/rfidcards.md} (97%) create mode 100644 documentation/builders/samba.md diff --git a/documentation/README.md b/documentation/README.md index b10941a55..bb11dd6f4 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -42,7 +42,8 @@ project check out the [documentation of Version 2](https://github.com/MiczFlor/R Version 3 has reached a mature state and will soon be the default version. However, some features may still be missing. Please check the [Feature Status](./developers/status.md), if YOUR feature is already implemented. -> ![NOTE] If version 3 has all the features you need, we recommend using Version 3. +> [!NOTE] +> If version 3 has all the features you need, we recommend using Version 3. If there is a feature missing, please open an issue. diff --git a/documentation/builders/README.md b/documentation/builders/README.md index 8ea5e0648..5909596aa 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -6,24 +6,28 @@ * [Update](./update.md) * [Configuring Phoniebox](./configuration.md) -## Configuration +## Features -* [Audio](./audio.md) -* [RFID](./rfid.md) +* Audio + * [Audio Output](./audio.md) + * [Bluetooth audio buttons](./bluetooth-audio-buttons.md) * [GPIO Recipes](./gpio.md) * [Card Database](./card-database.md) -* [Troubleshooting](./troubleshooting.md) + * [RFID Cards synchronisation](./components/synchronisation/rfidcards.md) +* [Auto Hotspot](./autohotspot.md) +* File Management + * [Network share / Samba](./samba.md) + +## Hardware Components -## Components * [Power](./components/power/) * [OnOff SHIM for safe power on/off](./components/power/onoff-shim.md) * [Soundcards](./components/soundcards/) * [HiFiBerry Boards](./components/soundcards/hifiberry.md) - +* [RFID Readers](./../developers/rfid/README.md) + ## Advanced - -* [Bluetooth (and audio buttons)](./bluetooth-audio-buttons.md) -* [Auto Hotspot](./autohotspot.md) +* [Troubleshooting](./troubleshooting.md) * [Concepts](./concepts.md) * [System](./system.md) * [RPC Commands](./rpc-commands.md) diff --git a/documentation/builders/rfid.md b/documentation/builders/components/synchronisation/rfidcards.md similarity index 97% rename from documentation/builders/rfid.md rename to documentation/builders/components/synchronisation/rfidcards.md index f8826f68f..a4c974b4b 100644 --- a/documentation/builders/rfid.md +++ b/documentation/builders/components/synchronisation/rfidcards.md @@ -1,6 +1,4 @@ -# RFID - -## Syncronisation RFID Cards +# Synchronisation RFID Cards This component handles the synchronisation of RFID cards (audiofolder and card database entries). @@ -15,7 +13,7 @@ RFID card to the command. For the \"RFID scan sync\" feature, activate the option in the configuration or bind a RFID card to the command for dynamic activation or deactivation. -### Synchronisation +## Synchronisation The synchronisation will be FROM a server TO the Phoniebox, overriding existing files. A local configuration will be lost after the @@ -26,7 +24,7 @@ To access the files on the server, 2 modes are supported: SSH or MOUNT. Please make sure you have the correct access rights to the source and use key-based authentication for SSH. -#### RFID scan sync +### RFID scan sync If the feature \"RFID scan sync\" is activated, there will be a check on every RFID scan against the server if a matching card entry and audiofolder is available. If so, changes will be synced. The playback @@ -40,7 +38,7 @@ deleted on remote. This is also true for changed card entries (the old audiofolder / -files will remain). To remove not existing items us a \"sync-all\". -### Configuration +## Configuration Set the corresponding setting in `shared\settings\jukebox.yaml` to activate this feature. diff --git a/documentation/builders/configuration.md b/documentation/builders/configuration.md index dba2da7dc..2e1c4ff23 100644 --- a/documentation/builders/configuration.md +++ b/documentation/builders/configuration.md @@ -7,11 +7,7 @@ The majority of configuration options is only available by editing the config fi *when the service is not running!* Don't fear (overly), they contain commentaries. -For several aspects we have [configuration tools](../developers/coreapps.md#configuration-tools) and detailed guides: - -* [Audio Configuration](./audio.md#audio-configuration) -* [RFID Reader Configuration](../developers/rfid/basics.md#reader-configuration) -* [GPIO Recipes](./gpio.md) +For several aspects we have [configuration tools](../developers/coreapps.md#configuration-tools) and [detailed guides](./README.md#features). Even after running the tools, certain aspects can only be changed by modifying the configuration files directly. diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index 7ce0e59c0..1d4b18470 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -10,7 +10,9 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr 1. Connect a Micro SD card to your computer (preferable an SD card with high read throughput) 2. Download the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) and run it 3. Click on "Raspberry Pi Device" and select "No filtering" -4. Select **Raspberry Pi OS Lite (32-bit)** (without desktop environment) as the operating system. `future3` does not support 64bit kernels (`aarch64`). +4. As operating system select **Raspberry Pi OS (other)** and then **Raspberry Pi OS Lite (Legacy, 32-bit)** (no desktop environment). *64-bit is currently not supported*. + * Bookworm support is partly broken, see [here](#workaround-for-network-related-features-on-bookworm). + * For Pi 4 and newer also check [this](#workaround-for-64-bit-kernels-pi-4-and-newer). 5. Select your Micro SD card (your card will be formatted) 6. After you click `Next`, a prompt will ask you if you like to customize the OS settings * Click `Edit Settings` @@ -26,12 +28,12 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr 8. Confirm the next warning about erasing the SD card with `Yes` 9. Wait for the imaging process to be finished (it'll take a few minutes) + +### Pre-boot preparation

In case you forgot to customize the OS settings, follow these instructions after RPi OS has been written to the SD card. -### Pre-boot preparation - You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to proceed with the next steps. 1. Open a terminal of your choice. @@ -77,6 +79,42 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to
+### Pre-install preparation / workarounds + +#### Workaround for network related features on Bookworm +
+With Bookworm the network settings have changed. Now "NetworkManager" is used instead of "dhcpcd". +This breaks breaks network related features like "Static IP", "Wifi Setup" and "Autohotspot". +Before running the installation, the network config has to be changed via raspi-config, to use the "old" dhcpcd network settings. + +:warning: +If the settings are changed, your network will reset and Wifi will not be configured, so you lose ssh access via wireless connection. +So make sure you use a wired connection or perform the following steps in a local terminal with a connected monitor and keyboard. + +Change network config +* run `sudo raspi-config` +* select `6 - Advanced Options` +* select `AA - Network Config` +* select `dhcpcd` + +If you need Wifi, add the information now +* select `1 - System Options` +* select `1 - Wireless LAN` +* enter Wifi information +
+ +#### Workaround for 64-bit Kernels (Pi 4 and newer) +
+ +The installation process checks if a 32-bit OS is running, as 64-bit is currently not supported. +This check also fails if the kernel is running in 64-bit mode. This is the default for Raspberry Pi models 4 and newer. + +To be able to run the installation, you have to switch to the 32-bit mode by modifying the `config.txt` and add/change the line `arm_64bit=0`. +Up to Bullseye, the `config.txt` file is located at `/boot/`. Since Bookworm, the location changed to `/boot/firmware/` ([see here](https://www.raspberrypi.com/documentation/computers/config_txt.html)). + +Reboot before you proceed. +
+ ## Install Phoniebox software Choose a version, run the corresponding install command in your SSH terminal and follow the instructions. diff --git a/documentation/builders/samba.md b/documentation/builders/samba.md new file mode 100644 index 000000000..ac9a93bbc --- /dev/null +++ b/documentation/builders/samba.md @@ -0,0 +1,18 @@ +# Samba + +To conveniently copy files to your Phoniebox via network `samba` can be configured during the installation. The folder `./shared/` will be exposed as network share `phoniebox`, giving you access to the audio and config folders. + +## Connect + +To access the share open your OS network environment and select your Phoniebox device. +Alternatively directly access it via url with the file explorer (e.g. Windows `\\`, MacOS `smb://`). + +See also +* [MacOS](https://support.apple.com/lt-lt/guide/mac-help/mchlp1140/mac) + +## User name / Password + +As login credentials use the same username you used to run the installation with. The password is `raspberry`. +You can change the password anytime using the command `sudo smbpasswd -a ""`. + + diff --git a/documentation/builders/update.md b/documentation/builders/update.md index 94baf0a93..e84655e7c 100644 --- a/documentation/builders/update.md +++ b/documentation/builders/update.md @@ -35,7 +35,8 @@ $ ./run_rebuild.sh -u ## Migration Path from Version 2 There is no update path coming from Version 2.x of the Jukebox. -You need to do a fresh install of Version 3 on a fresh Raspian Bullseye image. +You need to do a fresh install of Version 3 on a fresh Raspberry Pi OS image. +See [Installing Phoniebox future3](./installation.md). > [!IMPORTANT] > Do start with a fresh SD card image! diff --git a/documentation/developers/README.md b/documentation/developers/README.md index f6ec65dc4..5598f34d7 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -9,7 +9,7 @@ * [Jukebox Apps](./coreapps.md) * [Web App](./webapp.md) -* [RFID Readers](./rfid) +* [RFID Readers](./rfid/README.md) * [Docstring API Docs (from py files)](./docstring/README.md) * [Plugin Reference](./docstring/README.md#jukeboxplugs) * [Feature Status](./status.md) diff --git a/documentation/developers/docstring/README.md b/documentation/developers/docstring/README.md index dde85b224..aa7cefecc 100644 --- a/documentation/developers/docstring/README.md +++ b/documentation/developers/docstring/README.md @@ -1245,7 +1245,7 @@ This means must also run as user process, as described in ## Misc PulseAudio may switch the sink automatically to a connecting bluetooth device depending on the loaded module -with name module-switch-on-connect. On RaspianOS Bullseye, this module is not part of the default configuration +with name module-switch-on-connect. On Raspberry Pi OS Bullseye, this module is not part of the default configuration in ``/usr/pulse/default.pa``. So, we don't need to worry about it. If the module gets loaded it conflicts with the toggle on connect and the selected primary / secondary outputs from the Jukebox. Remove it from the configuration! diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index f2b60313b..9cd51c824 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -75,7 +75,7 @@ get_architecture() { echo $arch } -is_raspian() { +is_raspbian() { if [[ $( . /etc/os-release; printf '%s\n' "$ID"; ) == *"raspbian"* ]]; then echo true else @@ -89,7 +89,7 @@ get_debian_version_number() { } get_boot_config_path() { - if [ "$(is_raspian)" = true ]; then + if [ "$(is_raspbian)" = true ]; then local debian_version_number=$(get_debian_version_number) # Bullseye and lower diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 2df7ab528..1ec3f9ad5 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -86,9 +86,9 @@ Check install log for details:" exit 1 } -# Check if current distro is a 32 bit version -# Support for 64 bit Distros has not been checked (or precisely: is known not to work) -# All RaspianOS versions report as machine "armv6l" or "armv7l", if 32 bit (even the ARMv8 cores!) +# Check if current distro is a 32-bit version +# Support for 64-bit Distros has not been checked (or precisely: is known not to work) +# All Raspberry Pi OS versions report as machine "armv6l" or "armv7l", if 32-bit (even the ARMv8 cores!) _check_os_type() { local os_type=$(uname -m) @@ -97,8 +97,8 @@ _check_os_type() { if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then print_lc " ... OK!\n" else - print_lc "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" - print_lc "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" + print_lc "ERROR: Only 32-bit operating systems are supported. Please use a 32-bit version of Raspberry Pi OS!" + print_lc "For Pi 4 models or newer running a 64-bit kernels, also see this: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" exit 1 fi } diff --git a/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt b/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt index 937256e86..b539a2478 100644 --- a/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt +++ b/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt @@ -1,6 +1,6 @@ # This GUI-based mock reader also requires: tkinter # tkinter is a standard Python package and needs not be installed separately -# It is available on most Unix systems (but not on headless Raspbian RPi where running a GUI is difficult anyway) +# It is available on most Unix systems (but not on headless Raspberry Pi OS where running a GUI is difficult anyway) # You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` ttkthemes diff --git a/src/jukebox/components/volume/__init__.py b/src/jukebox/components/volume/__init__.py index ccc4873d7..4782581ca 100644 --- a/src/jukebox/components/volume/__init__.py +++ b/src/jukebox/components/volume/__init__.py @@ -33,7 +33,7 @@ ## Misc PulseAudio may switch the sink automatically to a connecting bluetooth device depending on the loaded module -with name module-switch-on-connect. On RaspianOS Bullseye, this module is not part of the default configuration +with name module-switch-on-connect. On Raspberry Pi OS Bullseye, this module is not part of the default configuration in ``/usr/pulse/default.pa``. So, we don't need to worry about it. If the module gets loaded it conflicts with the toggle on connect and the selected primary / secondary outputs from the Jukebox. Remove it from the configuration! @@ -635,7 +635,7 @@ def finalize(): global pulse_control # Set default output and start-up volume # Note: PulseAudio may switch the sink automatically to a connecting bluetooth device depending on the loaded module - # with name module-switch-on-connect. On RaspianOS Bullseye, this module is not part of the default configuration. + # with name module-switch-on-connect. On Raspberry Pi OS Bullseye, this module is not part of the default configuration. # So, we shouldn't need to worry about it. Still, set output and startup volume close to each other # to minimize bluetooth connection in between global pulse_control From 1c0afa36d499c91bfca95309a98992c67ef4662c Mon Sep 17 00:00:00 2001 From: s-martin Date: Mon, 29 Jan 2024 08:37:00 +0100 Subject: [PATCH 074/121] fix the messageboxes (#2223) --- documentation/builders/rpc-commands.md | 3 ++- documentation/builders/system.md | 6 ++++-- documentation/builders/troubleshooting.md | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/documentation/builders/rpc-commands.md b/documentation/builders/rpc-commands.md index 181034f4a..a45e0b643 100644 --- a/documentation/builders/rpc-commands.md +++ b/documentation/builders/rpc-commands.md @@ -97,7 +97,8 @@ kwargs: recursive: True ``` -> [!IMPORTANT] *args* must be a **list** of arguments to be passed! Even if only a single argument is passed. +> [!IMPORTANT] +> *args* must be a **list** of arguments to be passed! Even if only a single argument is passed. > So, use *args: [value]*. We try catch mis-uses but that might not always work. You will find some more examples the configuration of the [Card Database](card-database.md) diff --git a/documentation/builders/system.md b/documentation/builders/system.md index f6eeb7ba1..ab9311d9f 100644 --- a/documentation/builders/system.md +++ b/documentation/builders/system.md @@ -10,7 +10,8 @@ The system consists of 4. [Web App](system.md#web-app-ui) as User Interface (UI) for a web browser 5. A set of [Configuration Tools](../developers/coreapps.md#configuration-tools) and a set of [Developer Tools](../developers/coreapps.md#developer-tools) -> [!NOTE] The default install puts everything into the users home folder `~/RPi-Jukebox-RFID`. +> [!NOTE] +> The default install puts everything into the users home folder `~/RPi-Jukebox-RFID`. > Another folder might work, but is certainly not tested. ## Music Player Daemon (MPD) @@ -34,7 +35,8 @@ $ systemctl --user start mpd $ systemctl --user stop mpd ``` -> [!IMPORTANT] Never start or enable the system-wide MPD service with `sudo systemctl start mpd`! +> [!IMPORTANT] +> Never start or enable the system-wide MPD service with `sudo systemctl start mpd`! To check if MPD is running or has issues, use diff --git a/documentation/builders/troubleshooting.md b/documentation/builders/troubleshooting.md index 2f34af083..ffc5189dc 100644 --- a/documentation/builders/troubleshooting.md +++ b/documentation/builders/troubleshooting.md @@ -29,7 +29,8 @@ The logs are also available via the Web Server: http://ip.of.your.box/logs ``` -> [!IMPORTANT] Always check the time modification date or the beginning of the log file to ensure you are not looking at an old log file! +> [!IMPORTANT] +> Always check the time modification date or the beginning of the log file to ensure you are not looking at an old log file! ## The long answer: A few more details From 0aad2a93e8d04428628ce6a9cc94bbe5a3e6eecc Mon Sep 17 00:00:00 2001 From: SKHSKHilber <47213735+SKHSKHilber@users.noreply.github.com> Date: Tue, 30 Jan 2024 08:36:58 +0100 Subject: [PATCH 075/121] Improve Bluetooth docs (#2174) * Update audio.md * Update audio.md * Update audio.md * Update audio.md --------- Co-authored-by: s-martin Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --- documentation/builders/audio.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/builders/audio.md b/documentation/builders/audio.md index 54bfe72bf..93c46dd54 100644 --- a/documentation/builders/audio.md +++ b/documentation/builders/audio.md @@ -86,6 +86,7 @@ Pairing successful .... [PowerLocus Buddy]# exit ``` +If `bluetoothctl` has trouble to execute due to permission issue, try `sudo bluetoothctl`. Wait for a few seconds and then with `$ pactl list sinks short`, check wether the Bluetooth device shows up as an output. Its name usually looks like this: `bluez_sink.C4_FB_20_63_CO_FE.a2dp_sink`. From c306719a4b76ad829c79284df144e26ec17852b8 Mon Sep 17 00:00:00 2001 From: Alexander Lochmann Date: Tue, 30 Jan 2024 08:40:48 +0100 Subject: [PATCH 076/121] Playlists, Livestreams, Podcasts (#2200) * first attempt * Introduce harmonize_mpd_url * Revert "first attempt" This reverts commit c99f2ed884fd17987d7eee1c69caf93e212fc0c5. * feat: add docs about using playlists * feat: add docs about using livestreams * feat: add docs about using podcasts * Apply suggestions from code review Co-authored-by: s-martin * fix: diverify examples * fix: better folder display * fix: addressing comments * fix: addressing comments * fix: update wrong links and fix some wording --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> Co-authored-by: s-martin --- documentation/builders/README.md | 7 +- .../webapp/playlists-livestreams-podcasts.md | 142 ++++++++++++++++++ src/jukebox/components/playermpd/__init__.py | 13 +- 3 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 documentation/builders/webapp/playlists-livestreams-podcasts.md diff --git a/documentation/builders/README.md b/documentation/builders/README.md index 5909596aa..6d9e67bff 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -25,7 +25,12 @@ * [Soundcards](./components/soundcards/) * [HiFiBerry Boards](./components/soundcards/hifiberry.md) * [RFID Readers](./../developers/rfid/README.md) - + +## Web Application + +* Music + * [Playlists, Livestreams and Podcasts](./webapp/playlists-livestreams-podcasts.md) + ## Advanced * [Troubleshooting](./troubleshooting.md) * [Concepts](./concepts.md) diff --git a/documentation/builders/webapp/playlists-livestreams-podcasts.md b/documentation/builders/webapp/playlists-livestreams-podcasts.md new file mode 100644 index 000000000..2b8be79a1 --- /dev/null +++ b/documentation/builders/webapp/playlists-livestreams-podcasts.md @@ -0,0 +1,142 @@ +# Playlists, Livestreams and Podcasts + +By default, the Jukebox represents music based on its metadata like album name, artist or song name. The hierarchy and order of songs is determined by their original definition, e.g. order of songs within an album. If you prefer a specific list of songs to be played, you can use playlists (files ending with `*.m3u`). Jukebox also supports livestreams and podcasts (if connected to the internet) through playlists. + +## Playlists +If you like the Jukebox to play songs in a pre-defined order, you can use .m3u playlists. + +A .m3u playlist is a plain text file that contains a list of file paths or URLs to multimedia files. Each entry in the playlist represents a single song, and they are listed in the order in which they should be played. + +### Structure of a .m3u playlist +A .m3u playlist is a simple text document with each song file listed on a separate line. Each entry is optionally preceded by a comment line that starts with a '#' symbol. The actual file paths or URLs of the media files come after the comment. + +### Creating a .m3u playlist + +1. You can create a .m3u playlist using a plain text editor. +1. Open a new text file +1. [Optional] Start by adding a comment line to provide a description or notes about the playlist. +1. On the following lines, list the file paths or URLs of the media files you want to include in the playlist, one per line. They must refer to true files paths on your Jukebox. They can be relative or absolute paths. +1. Save the file with the .m3u extension, e.g. `my_playlist.m3u`. + +``` +# Absolute +/home//RPi-Jukebox-RFID/shared/audiofolders/Simone Sommerland/Die 30 besten Kindergartenlieder/08 - Pitsch, patsch, Pinguin.mp3 +/home//RPi-Jukebox-RFID/shared/audiofolders/Simone Sommerland/Die 30 besten Spiel- Und Bewegungslieder/12 - Das rote Pferd.mp3 +# Relative +Bibi und Tina/bibi-tina-jetzt-in-echt-kinofilm-soundtrack/bibi-tina-jetzt-in-echt-kinofilm-soundtrack-7-ordinary-girl.mp3 +``` + +### Using .m3u playlists in Jukebox + +The Jukebox Web App handles the playlists in a way that it allows you to browse its content just like other songs. This means, you won't see the m3u playlist itself and instead, the individual items of the playlist. They also become actionable and you can select individual songs from it to play, or play the entire playlist. + +> [!NOTE] +> Files ending with `.m3u` are treated as folder playlist. Regular folder processing is suspended and the playlist is built solely from the `.m3u` content. Only the alphabetically first `.m3u` file is processed, others are ignored. + +Based on the note above, we suggest to use m3u playlists like this, especially if you like to manage multiple playlists. + +1. In the `audiofolders` directory (or any sub-directory), create a new folder. +1. In this new folder, copy your .m3u playlist. Make sure the links to the respective songs are correct. +1. Open the Web App. Under `Library`, select the `Folder` view and browse to the new folder you created. +1. You should now be able to browse and play the content of the playlist. + +#### Example folder structure + +``` +└── audiofolders + ├── wake-up-songs + │ └── playlist.m3u + └── lullabies-sleep-well + └── playlist.m3u +``` + +### Assigning a .m3u playlist to a card + +In the Jukebox Web App, .m3u playlists do not show up as individual files. In order to assign a playlist to a card, do the following: + +1. [Follow the steps above](#using-m3u-playlists-in-jukebox) to add a playlist to your Jukebox (make sure you have created individual folders). +1. Open the `Cards` tab in the Web App and click on the `+` button to add a new card. +1. As a Jukebox action, select "Play music", then select "Select music". +1. In the `Library` view, select the `Folder` view located in the top right corner. +1. Browse to the folder you created (representing your playlist) and click on it. + +You are essentially assigning a folder (just like any other conventional folder) to your card representing the content of your playlist. + +## Livestreams + +In order to play radio livestreams on your Jukebox, you use playlists to register your livestream and make it accessible. + +### Using livestream.txt playlist in Jukebox + +1. [Follow the steps above](#using-m3u-playlists-in-jukebox) to add a playlist to your Jukebox (make sure you have created individual folders). +1. When creating the playlist file, make sure it's called or at least ends with `livestream.txt` instead of `.m3u` (Examples: `awesome-livestream.txt`, `livestream.txt`). +1. Add URLs of your livestreams just like you would add songs in `.m3u` playlists. + +You can now assign livestreams to cards [following the example](#assigning-a-m3u-playlist-to-a-card) of playlists. + +#### Example folder structure and playlist names + +``` +└── audiofolders + ├── wdr-kids + │ └── wdr-kids-livestream.txt + ├── energy + │ └── cool-livestream.txt + └── classic + └── livestream.txt +``` + +#### Example of livestream.txt + +```txt +https://wdr-diemaus-live.icecastssl.wdr.de/wdr/diemaus/live/mp3/128/stream.mp3 +http://channels.webradio.antenne.de/hits-fuer-kids +``` + +## Podcasts + +Just like you add livestreams to the Jukebox, you can also add individual Podcasts or entire Podcast feeds to the Jukebox. + +You have 3 options to play Podcasts + +1. Create a playlist and reference individual direct URLs to Podcast episodes (just like [livestreams](#livestreams)) +1. Provide a Podcast RSS feed +1. Download the MP3 and add them like normal songs to your Jukebox. This also makes them available offline. + +We will explain options 1 and 2 more closely. + +### Using podcast.txt playlist in Jukebox + +1. [Follow the steps above](#using-m3u-playlists-in-jukebox) to add a playlist to your Jukebox (make sure you have created individual folders). +1. When creating the playlist file, make sure it's called or at least ends with `podcasts.txt` instead of `.m3u`. (Examples: `awesome-podcast.txt`, `podcast.txt`). +1. Add links to your individual podcast episodes just like you would with songs in .m3u playlists +1. As an alternative, you can provide a single RSS feed (XML). Jukebox will expand the file and refer to all episodes listed within this file. + +#### Example folder structure and playlist names + +``` +└── audiofolders + ├── die-maus + │ └── die-maus-podcast.txt + ├── miras-welt + │ └── cool-podcast.txt + └── kakadu + └── podcast.txt +``` + +#### Example of podcast.txt for individual episodes + +```txt +https://podcastb11277.podigee.io/94-ich-ware-gerne-beliebt-wie-geht-das +https://podcastb11277.podigee.io/91-wieso-kann-ich-nicht-den-ganzen-tag-fernsehen +``` + +#### Example of podcast.txt for RSS feeds (XML) + +```txt +https://kinder.wdr.de/radio/diemaus/audio/diemaus-60/diemaus-60-106.podcast +``` + +```txt +http://www.kakadu.de/podcast-kakadu.2730.de.podcast.xml +``` diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index 9073a9b4a..65c6e7267 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -280,6 +280,14 @@ def _mpd_status_poll(self): pass publishing.get_publisher().send('playerstatus', self.mpd_status) + # MPD can play absolute paths but can find songs in its database only by relative path + # This function aims to prepare the song_url accordingly + def harmonize_mpd_url(self, song_url): + _music_library_path_absolute = os.path.expanduser(components.player.get_music_library_path()) + song_url = song_url.replace(f'{_music_library_path_absolute}/', '') + + return song_url + @plugs.tag def get_player_type_and_version(self): with self.mpd_lock: @@ -667,10 +675,7 @@ def list_songs_by_artist_and_album(self, albumartist, album): @plugs.tag def get_song_by_url(self, song_url): - # MPD can play absolute paths but can find songs in its database only by relative path - # In certain situations, `song_url` can be an absolute path. Then, it will be trimed to be relative - _music_library_path_absolute = os.path.expanduser(components.player.get_music_library_path()) - song_url = song_url.replace(f'{_music_library_path_absolute}/', '') + song_url = self.harmonize_mpd_url(song_url) with self.mpd_lock: song = self.mpd_retry_with_mutex(self.mpd_client.find, 'file', song_url) From 85587a686f04bbbcf47679a264696d5a71d07e69 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:56:38 +0100 Subject: [PATCH 077/121] Future3 add login motd with note about venv (#2225) * add ssh welcome script * changed message * moved login message setup * fix sudo on copy * update message * fix typo in filename --- documentation/developers/README.md | 2 +- .../developers/{pyhton.md => python.md} | 0 installation/routines/install.sh | 1 + installation/routines/set_raspi_config.sh | 4 ++++ resources/system/99-rpi-jukebox-rfid-welcome | 16 ++++++++++++++++ 5 files changed, 22 insertions(+), 1 deletion(-) rename documentation/developers/{pyhton.md => python.md} (100%) create mode 100644 resources/system/99-rpi-jukebox-rfid-welcome diff --git a/documentation/developers/README.md b/documentation/developers/README.md index 5598f34d7..6addeaff1 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -3,7 +3,7 @@ ## Getting started * [Development Environment](./development-environment.md) -* [Python Development Notes](pyhton.md) +* [Python Development Notes](python.md) ## Reference diff --git a/documentation/developers/pyhton.md b/documentation/developers/python.md similarity index 100% rename from documentation/developers/pyhton.md rename to documentation/developers/python.md diff --git a/installation/routines/install.sh b/installation/routines/install.sh index f1f2a5f80..66a65203c 100644 --- a/installation/routines/install.sh +++ b/installation/routines/install.sh @@ -15,5 +15,6 @@ install() { setup_rfid_reader optimize_boot_time setup_autohotspot + setup_login_message cleanup } diff --git a/installation/routines/set_raspi_config.sh b/installation/routines/set_raspi_config.sh index 7f39a0ba5..6bfa8e725 100644 --- a/installation/routines/set_raspi_config.sh +++ b/installation/routines/set_raspi_config.sh @@ -31,3 +31,7 @@ _run_set_raspi_config() { set_raspi_config() { run_with_log_frame _run_set_raspi_config "Set default raspi-config" } + +setup_login_message() { + sudo cp -f "${INSTALLATION_PATH}/resources/system/99-rpi-jukebox-rfid-welcome" "/etc/update-motd.d/99-rpi-jukebox-rfid-welcome" +} diff --git a/resources/system/99-rpi-jukebox-rfid-welcome b/resources/system/99-rpi-jukebox-rfid-welcome new file mode 100644 index 000000000..281b1a152 --- /dev/null +++ b/resources/system/99-rpi-jukebox-rfid-welcome @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +echo " +######################################################### + ___ __ ______ _ __________ ____ __ _ _ + / _ \/ // / __ \/ |/ / _/ __/( _ \ / \( \/ ) + / ___/ _ / /_/ / // // _/ ) _ (( O )) ( +/_/ /_//_/\____/_/|_/___/____/ (____/ \__/(_/\_) +future3 + +If you want to run a python script from the project +activate the venv before with `source .venv/bin/activate` +See also https://github.com/MiczFlor/RPi-Jukebox-RFID/ +blob/future3/main/documentation/developers/python.md + +#########################################################" From 2bb93679a68ff06497b492f2e0191bd0ce37b2fb Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Fri, 2 Feb 2024 00:06:52 +0100 Subject: [PATCH 078/121] Minor fixes (#2234) * fix motd * fix error message for generic usb reader --- resources/system/99-rpi-jukebox-rfid-welcome | 2 +- src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/system/99-rpi-jukebox-rfid-welcome b/resources/system/99-rpi-jukebox-rfid-welcome index 281b1a152..1b9904f96 100644 --- a/resources/system/99-rpi-jukebox-rfid-welcome +++ b/resources/system/99-rpi-jukebox-rfid-welcome @@ -9,7 +9,7 @@ echo " future3 If you want to run a python script from the project -activate the venv before with `source .venv/bin/activate` +activate the venv before with 'source .venv/bin/activate' See also https://github.com/MiczFlor/RPi-Jukebox-RFID/ blob/future3/main/documentation/developers/python.md diff --git a/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py b/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py index 287bd6b9e..f6d0a5db5 100755 --- a/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py +++ b/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py @@ -82,7 +82,7 @@ def query_customization() -> dict: print(f" {Colors.lightgreen}ID{Colors.reset}: " f"{Colors.lightgreen}isKey{Colors.reset}: {Colors.lightcyan}Name{Colors.reset}") if len(devices) == 0: - logger.error("USB device list is empty. Make sure USB RFID reader is connected. Then re-run register_reader.py") + logger.error("USB device list is empty. Make sure USB RFID reader is connected. Then re-run reader registration") return {'device_name': '__error_empty_device_list__'} for idx, (dev, key) in enumerate(zip(devices, devices_is_key)): print(f" {Colors.lightgreen}{idx:2d}{Colors.reset}:" From e1b46b61d8ba185bd64b3d9422a08e44f11827a3 Mon Sep 17 00:00:00 2001 From: powertomato Date: Sun, 4 Feb 2024 16:52:18 +0100 Subject: [PATCH 079/121] Add NFCPy support (#2190) * Add NFCPy support * Fixed PR comments * Improve documentation, add an example * Fix setup.inc.sh execution rights * Fix PR comments * Fix PR comments * Fix PR comments --- .../developers/rfid/generic_nfcpy.md | 36 +++++ .../rfid/hardware/generic_nfcpy/README.md | 2 + .../hardware/generic_nfcpy/description.py | 3 + .../hardware/generic_nfcpy/generic_nfcpy.py | 128 ++++++++++++++++++ .../hardware/generic_nfcpy/requirements.txt | 4 + .../rfid/hardware/generic_nfcpy/setup.inc.sh | 42 ++++++ 6 files changed, 215 insertions(+) create mode 100644 documentation/developers/rfid/generic_nfcpy.md create mode 100644 src/jukebox/components/rfid/hardware/generic_nfcpy/README.md create mode 100644 src/jukebox/components/rfid/hardware/generic_nfcpy/description.py create mode 100644 src/jukebox/components/rfid/hardware/generic_nfcpy/generic_nfcpy.py create mode 100644 src/jukebox/components/rfid/hardware/generic_nfcpy/requirements.txt create mode 100755 src/jukebox/components/rfid/hardware/generic_nfcpy/setup.inc.sh diff --git a/documentation/developers/rfid/generic_nfcpy.md b/documentation/developers/rfid/generic_nfcpy.md new file mode 100644 index 000000000..bea7b302a --- /dev/null +++ b/documentation/developers/rfid/generic_nfcpy.md @@ -0,0 +1,36 @@ +# Generic NFCPy Reader + +This module is based on the user space NFC reader library [nfcpy](https://nfcpy.readthedocs.io/en/latest/overview.html) ([on github](https://github.com/nfcpy/nfcpy)). +The link above also contains a list of [supported devices](https://nfcpy.readthedocs.io/en/latest/overview.html#supported-devices). + +The goal of this module is to handle USB NFC devices, that don't have a HID-keyboard +driver, and thus cannot be used with the [genericusb](genericusb.md) module. + +> [!NOTE] +> Since nfcpy is a user-space library, it is required to supress the kernel from loading its driver. +> The setup will do this automatically, so make sure the device is connected +> before running the [RFID reader configuration tool](../coreapps.md#RFID-Reader). + +# Configuration + +The installation script will scan for compatible devices and will assist in configuration. +By setting `rfid > readers > generic_nfcpy > config > device_path` in `shared/settings/rfid.yaml` you can override the +device location. By specifying an explicit device location it is possible to use multiple readers compatible with NFCpy. + +Example configuration for a usb-device with vendor ID 072f and product ID 2200: +```yaml +rfid: + readers: + read_00: + module: generic_nfcpy + config: + device_path: usb:072f:2200 + same_id_delay: 1.0 + log_ignored_cards: false + place_not_swipe: + enabled: false + card_removal_action: + alias: pause +``` + +For possible values see the `path` parameter in this [nfcpy documentation](https://nfcpy.readthedocs.io/en/latest/modules/clf.html#nfc.clf.ContactlessFrontend.open) \ No newline at end of file diff --git a/src/jukebox/components/rfid/hardware/generic_nfcpy/README.md b/src/jukebox/components/rfid/hardware/generic_nfcpy/README.md new file mode 100644 index 000000000..f1c92ded0 --- /dev/null +++ b/src/jukebox/components/rfid/hardware/generic_nfcpy/README.md @@ -0,0 +1,2 @@ + +For documentation see [documentation/developers/rfid/generic_nfcpy.md](../../../../../../documentation/developers/rfid/generic_nfcpy.md). diff --git a/src/jukebox/components/rfid/hardware/generic_nfcpy/description.py b/src/jukebox/components/rfid/hardware/generic_nfcpy/description.py new file mode 100644 index 000000000..eea9438eb --- /dev/null +++ b/src/jukebox/components/rfid/hardware/generic_nfcpy/description.py @@ -0,0 +1,3 @@ +""" List of supported devices https://nfcpy.readthedocs.io/en/latest/overview.html""" +# 40 chars: '========================================' +DESCRIPTION = 'Generic NFCPY NFC Reader Module' diff --git a/src/jukebox/components/rfid/hardware/generic_nfcpy/generic_nfcpy.py b/src/jukebox/components/rfid/hardware/generic_nfcpy/generic_nfcpy.py new file mode 100644 index 000000000..4b6ac0051 --- /dev/null +++ b/src/jukebox/components/rfid/hardware/generic_nfcpy/generic_nfcpy.py @@ -0,0 +1,128 @@ +# Standard imports from python packages +import logging + +import nfc +import glob +from nfc.clf import RemoteTarget +import nfc.clf.device + +# Import the ReaderBaseClass for common API. Leave as this line as it is! +from components.rfid import ReaderBaseClass +import jukebox.cfghandler +import misc.inputminus as pyil +from misc.simplecolors import Colors + +# Also import the description string into this module, to make everything available in a single module w/o code duplication +# Leave this line as is! +from .description import DESCRIPTION + +# Create logger. +logger = logging.getLogger('jb.rfid.nfcpy') +# Get the global handler to the RFID config +cfg = jukebox.cfghandler.get_handler('rfid') + + +def query_customization() -> dict: + # filter all log records from nfc.clf + loggerNfcClf = logging.getLogger('nfc.clf') + loggerNfcClf.filter = lambda record: 0 + + devices = [] + clf = nfc.ContactlessFrontend() + + # find usb devices + for vid_pid_pair in nfc.clf.device.usb_device_map.keys(): + device_id = "usb:%04x:%04x" % vid_pid_pair + if clf.open(device_id): + devices.append({'id': device_id, 'vendor': clf.device.vendor_name, 'name': clf.device.product_name}) + clf.close() + + # find tty device + matching_files = glob.glob("/dev/ttyUSB[0-9]*") + matching_files += glob.glob("/dev/ttyAMA[0-9]*") + for file_path in matching_files: + for driver in nfc.clf.device.tty_driver_list: + device_id = f'{file_path}:{driver}' + if clf.open(device_id): + devices.append({'id': device_id, 'vendor': clf.device.vendor_name, 'name': clf.device.product_name}) + clf.close() + + print("\nChoose RFID device from USB device list:\n") + logger.debug(f"USB devices: {[x['name'] for x in devices]}") + if len(devices) == 0: + logger.error("USB device list is empty. Make sure USB RFID reader is connected. Then re-run reader registration") + return {'device_path': None} + + for idx, dev in enumerate(devices): + print(f" {Colors.lightgreen}{idx:2d}{Colors.reset}:" + f"{Colors.lightcyan}{Colors.bold}{dev['vendor']} {dev['name']}{Colors.reset}") + + dev_id = pyil.input_int("Device number?", min=0, max=len(devices) - 1, prompt_color=Colors.lightgreen, prompt_hint=True) + device_path = devices[dev_id]['id'] + + return {'device_path': device_path} + + +class ReaderClass(ReaderBaseClass): + """ + The reader class for nfcpy supported NFC card readers. + """ + def __init__(self, reader_cfg_key): + # Create a per-instance logger, just in case the reader will run multiple times in various threads + self._logger = logging.getLogger(f'jb.rfid.nfcpy({reader_cfg_key})') + # Initialize the super-class. Don't change anything here + super().__init__(reader_cfg_key=reader_cfg_key, description=DESCRIPTION, logger=self._logger) + + # Get the configuration from the rfid.yaml: + # Lock config around the access + with cfg: + # Get a reference to the actual reader-specific config + config = cfg.getn('rfid', 'readers', reader_cfg_key, 'config', default=None) + if config is None: + self._logger.error("Configuration may not be empty!!") + raise KeyError("configuration may not be empty!!") + + device_path = config.setdefault('device_path', None) + self.clf = nfc.ContactlessFrontend() + self.clf.open(device_path) + + self._keep_running = True + + def cleanup(self): + """ + The cleanup function: free and release all resources used by this card reader (if any). + """ + self.clf.close() + + def stop(self): + """ + This function is called to tell the reader to exit its reading function. + """ + self._keep_running = False + + def read_card(self) -> str: + """ + Blocking or non-blocking function that waits for a new card to appear and return the card's UID as string + """ + self._logger.debug("Wait for card") + while self._keep_running: + target = self.clf.sense(RemoteTarget('106A'), + RemoteTarget('106B'), + RemoteTarget('212F'), + interval=0.1, + iterations=1) + if not target: + continue + + tag = nfc.tag.activate(self.clf, target) + if not tag: + continue + + id = '' + for char in tag.identifier: + id += '%02X' % char + + self._logger.debug(f'Found card with ID: "{id}"') + return id + self._logger.debug("NFC read stopped") + return '' diff --git a/src/jukebox/components/rfid/hardware/generic_nfcpy/requirements.txt b/src/jukebox/components/rfid/hardware/generic_nfcpy/requirements.txt new file mode 100644 index 000000000..8fb09c9db --- /dev/null +++ b/src/jukebox/components/rfid/hardware/generic_nfcpy/requirements.txt @@ -0,0 +1,4 @@ +# NFCPy related requirements +# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` + +nfcpy diff --git a/src/jukebox/components/rfid/hardware/generic_nfcpy/setup.inc.sh b/src/jukebox/components/rfid/hardware/generic_nfcpy/setup.inc.sh new file mode 100755 index 000000000..d831b3de9 --- /dev/null +++ b/src/jukebox/components/rfid/hardware/generic_nfcpy/setup.inc.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +CURRENT_USER="${SUDO_USER:-$(whoami)}" + +modprobe_file="/etc/modprobe.d/disable_driver_jukebox_nfcpy.conf" + +if [ -e "$modprobe_file" ]; then + sudo rm -f "$modprobe_file" +fi +if lsmod | grep "pn533_usb"; then + sudo rmmod pn533_usb 2>/dev/null + sudo sh -c "echo 'install pn533_usb /bin/true' >> $modprobe_file" +fi + +if lsmod | grep "port100"; then + sudo rmmod port100 2>/dev/null + sudo sh -c "echo 'install port100 /bin/true' >> $modprobe_file" +fi + +udev_file="/etc/udev/rules.d/50-usb-nfc-rule.rules" + +usb_devices=$(lsusb | sed -e 's/.*ID \([a-f0-9]\+:[a-f0-9]\+\).*/\1/g') + +valid_device_ids=($(python -c "import nfc.clf.device; [print('%04x:%04x' % x) for x in nfc.clf.device.usb_device_map.keys()]")) + +if [ -e "$udev_file" ]; then + sudo rm -f "$udev_file" +fi +for dev in $usb_devices; do + if echo ${valid_device_ids[@]} | grep -woq $dev; then + #$dev is in valid id array + VID="${dev%%:*}" + PID="${dev#*:}" + sudo sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"$VID\", ATTRS{idProduct}==\"$PID\", GROUP=\"plugdev\"' >> $udev_file" + fi +done +sudo udevadm control --reload-rules +sudo udevadm trigger + +sudo gpasswd -a $CURRENT_USER plugdev +sudo gpasswd -a $CURRENT_USER dialout +sudo gpasswd -a $CURRENT_USER tty \ No newline at end of file From b2a6517b306d5bba9247038be126786afd1aef1a Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sun, 4 Feb 2024 17:03:07 +0100 Subject: [PATCH 080/121] included venv activation for python scripts (#2233) * add setup script for rfid reader. moved python script * add check for venv activation * fix * fix cd fail behavior (dont start subprocess) * add script for run jukebox for venv. updated docs. * add setup script for audio sink. moved python script * add script for rpc tool. moved python script * add script for sniffer tool. moved python script * fixed parameter passing * fix paths * fix flake8 * fix comments and docs * fixed parameter passing * some fixes * fixed execution rights * some fixes * move scripts to python root again (failing import paths). fixed absolute path in caller scripts * fix relative paths * update notes about config file paths * update caller scripts path handling * update comments * add venv activation to tests * explicit installation of python3-venv not needed * renamed audio setup file. updated docs path. * Update configuration.md * Update coreapps.md * update motd message * fix execution rights for motd file. refactored into own postinstall script * fix execution right for audio setup --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --- .githooks/post-merge | 1 + .github/workflows/codeql-analysis_v3.yml | 3 ++ .github/workflows/pythonpackage_future3.yml | 4 +- documentation/builders/configuration.md | 20 +++++----- documentation/builders/troubleshooting.md | 9 ++--- documentation/developers/coreapps.md | 37 +++++++++---------- documentation/developers/known-issues.md | 2 +- .../components/setup_configure_audio.sh | 16 ++++++++ installation/components/setup_rfid_reader.sh | 16 ++++++++ installation/routines/install.sh | 2 +- installation/routines/set_raspi_config.sh | 4 -- installation/routines/setup_postinstall.sh | 13 +++++++ installation/routines/setup_rfid_reader.sh | 4 +- .../default-services/jukebox-daemon.service | 4 +- .../default-settings/jukebox.default.yaml | 2 +- .../default-settings/pulseaudio.default.pa | 2 +- resources/system/99-rpi-jukebox-rfid-welcome | 12 ++---- run_docgeneration.sh | 10 +++-- run_flake8.sh | 11 ++++-- run_jukebox.sh | 16 ++++++++ run_pytest.sh | 11 ++++-- src/jukebox/components/volume/__init__.py | 2 +- src/jukebox/run_configure_audio.py | 0 src/jukebox/run_publicity_sniffer.py | 0 src/jukebox/run_register_rfid_reader.py | 3 +- src/jukebox/run_rpc_tool.py | 0 tools/run_publicity_sniffer.sh | 16 ++++++++ tools/run_rpc_tool.sh | 16 ++++++++ 28 files changed, 166 insertions(+), 70 deletions(-) create mode 100755 installation/components/setup_configure_audio.sh create mode 100755 installation/components/setup_rfid_reader.sh create mode 100644 installation/routines/setup_postinstall.sh create mode 100755 run_jukebox.sh mode change 100755 => 100644 src/jukebox/run_configure_audio.py mode change 100755 => 100644 src/jukebox/run_publicity_sniffer.py mode change 100755 => 100644 src/jukebox/run_register_rfid_reader.py mode change 100755 => 100644 src/jukebox/run_rpc_tool.py create mode 100755 tools/run_publicity_sniffer.sh create mode 100755 tools/run_rpc_tool.sh diff --git a/.githooks/post-merge b/.githooks/post-merge index 6fc6e4f54..07b81db41 100755 --- a/.githooks/post-merge +++ b/.githooks/post-merge @@ -43,6 +43,7 @@ warn_python_requirements() { echo "ATTENTION: Python requirements have changed since last pull!" echo "" echo "To update python requirements on the RPi run" + echo "$ source .venv/bin/activate" echo "$ python -m pip install --upgrade -r requirements.txt" echo "************************************************************" echo -e "\n" diff --git a/.github/workflows/codeql-analysis_v3.yml b/.github/workflows/codeql-analysis_v3.yml index 1c1c68ba3..89693df2a 100644 --- a/.github/workflows/codeql-analysis_v3.yml +++ b/.github/workflows/codeql-analysis_v3.yml @@ -45,6 +45,9 @@ jobs: run: | # Install necessary packages sudo apt-get install libasound2-dev pulseaudio + python3 -m venv .venv + source ".venv/bin/activate" + python -m pip install --upgrade pip pip install -r requirements.txt # Set the `CODEQL-PYTHON` environment variable to the Python executable diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index f2174d0c0..2236a5a47 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -31,6 +31,9 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libasound2-dev pulseaudio + python3 -m venv .venv + source ".venv/bin/activate" + python3 -m pip install --upgrade pip pip3 install -r requirements.txt # For operation of the Jukebox, ZMQ must be compiled from sources due to Websocket support @@ -51,7 +54,6 @@ jobs: parallel: true - name: Lint with flake8 run: | - pip3 install flake8 # Stop the build if linting fails ./run_flake8.sh diff --git a/documentation/builders/configuration.md b/documentation/builders/configuration.md index 2e1c4ff23..dd4c1fd53 100644 --- a/documentation/builders/configuration.md +++ b/documentation/builders/configuration.md @@ -1,15 +1,15 @@ # Jukebox Configuration The Jukebox configuration is managed by a set of files located in `shared/settings`. -Some configuration changes can be made through the WebUI and take immediate effect. +Some configuration changes can be made through the Web App and take immediate effect. -The majority of configuration options is only available by editing the config files - +The majority of configuration options are only available by editing the config files - *when the service is not running!* Don't fear (overly), they contain commentaries. -For several aspects we have [configuration tools](../developers/coreapps.md#configuration-tools) and [detailed guides](./README.md#features). +For several aspects, we have [configuration tools](../developers/coreapps.md#configuration-tools) and [detailed guides](./README.md#features). -Even after running the tools, certain aspects can only be changed by modifying the configuration files directly. +Even after using the tools, certain aspects can only be changed by directly modifying the configuration files. ## Best practice procedure @@ -21,19 +21,17 @@ $ systemctl --user stop jukebox-daemon $ nano ./shared/settings/jukebox.yaml # Start Jukebox in console and check the log output (optional) -$ cd src/jukebox -$ ./run_jukebox.py +$ ./run_jukebox.sh # and if OK, press Ctrl-C and restart the service # Restart the service $ systemctl --user start jukebox-daemon ``` - -To try different configurations, you can start the Jukebox with a custom config file. +To try different configurations, you can start the Jukebox with a custom config file. This could be useful if you want your Jukebox to only allow a lower volume when started -at night time when there is time to go to bed :-) +at nighttime, signaling it's time to go to bed. :-) +The path to the custom config file must be either absolute or relative to the folder `src/jukebox/`. ```bash -$ cd src/jukebox -$ ./run_jukebox.py --conf path/to/custom/config.yaml +$ ./run_jukebox.sh --conf /absolute/path/to/custom/config.yaml ``` diff --git a/documentation/builders/troubleshooting.md b/documentation/builders/troubleshooting.md index ffc5189dc..5b4061aa8 100644 --- a/documentation/builders/troubleshooting.md +++ b/documentation/builders/troubleshooting.md @@ -64,12 +64,10 @@ on the console log. $ systemctl --user stop jukebox-daemon # Start the Jukebox in debug mode: -$ cd src/jukebox - # with default logger: -$ ./run_jukebox.py +$ ./run_jukebox.sh # or with custom logger configuration: -$ ./run_jukebox.py --logger path/to/custom/logger.yaml +$ ./run_jukebox.sh --logger path/to/custom/logger.yaml ``` ### Fallback configuration @@ -79,8 +77,7 @@ Attention: This only emits messages to the console and does not write to the log This is more a fallback features: ```bash -$ cd src/jukebox -$ ./run_jukebox.py -vv +$ ./run_jukebox.sh -vv ``` ### Extreme cases diff --git a/documentation/developers/coreapps.md b/documentation/developers/coreapps.md index f1402683d..80b3c043a 100644 --- a/documentation/developers/coreapps.md +++ b/documentation/developers/coreapps.md @@ -1,7 +1,6 @@ # Jukebox Apps -The Jukebox\'s core apps are located in `src/jukebox`. Run the following -command to learn more about each app and its parameters: +The Jukebox's core apps are located in `src/jukebox`. To learn more about each app and its parameters, run the following command: ``` bash $ cd src/jukebox @@ -10,13 +9,13 @@ $ ./ -h ## Jukebox Core -**Scriptname:** [run_jukebox.py](../../src/jukebox/run_jukebox.py) +**Scriptname:** [run_jukebox.sh](../../run_jukebox.sh) -This is the main app and starts the Jukebox Core. +This is the main app. It starts the Jukebox Core. -Usually this runs as a service, which is started automatically after boot-up. At times, it may be necessary to restart the service. For example after a configuration change. Not all configuration changes can be applied on-the-fly. See [Jukebox Configuration](../builders/configuration.md#jukebox-configuration). +This runs as a service, which starts automatically after boot-up. At times, it may be necessary to restart the service, for example, after a configuration change. Not all configuration changes can be applied on-the-fly. See [Jukebox Configuration](../builders/configuration.md#jukebox-configuration). -For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. See [Troubleshooting](../builders/troubleshooting.md). +For debugging, it's best to run Jukebox directly from the console rather than as a service, as this provides direct logging information in the console and allows for changing command line parameters. See [Troubleshooting](../builders/troubleshooting.md). ## Configuration Tools @@ -25,41 +24,39 @@ See [Best practice procedure](../builders/configuration.md#best-practice-procedu ### Audio -**Scriptname:** [run_configure_audio.py](../../src/jukebox/run_configure_audio.py) +**Scriptname:** [setup_configure_audio.sh](../../installation/components/setup_configure_audio.sh) -Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. +A setup tool to register the PulseAudio sinks as primary and secondary audio outputs. -Will also setup equalizer and mono down mixer in the pulseaudio config file. Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). +This will also set up an equalizer and mono downmixer in the PulseAudio configuration file. Run this once after installation. It can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). ### RFID Reader -**Scriptname:** [run_register_rfid_reader.py](../../src/jukebox/run_register_rfid_reader.py) +**Scriptname:** [setup_rfid_reader.sh](../../installation/components/setup_rfid_reader.sh) Setup tool to configure the RFID Readers. -Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change the settings. For more information see [RFID Readers](./rfid/README.md). +Run this once to register and configure the RFID readers with Jukebox. It can be re-run at any time to change the settings. For more information see [RFID Readers](./rfid/README.md). > [!NOTE] -> This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). Any manual modifications to the settings will have to be re-applied +> This tool will always create a new configuration file, thereby overwriting the old one (after confirming with the user). Any manual modifications to the settings will need to be reapplied. ## Developer Tools ### RPC -**Scriptname:** [run_rpc_tool.py](../../src/jukebox/run_rpc_tool.py) +**Scriptname:** [run_rpc_tool.sh](../../tools/run_rpc_tool.sh) Command Line Interface to the Jukebox RPC Server. -A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging. Use `./run_rpc_tool.py` to start the tool in interactive mode. +A command-line tool for sending RPC commands to the running Jukebox app, utilizing the same interface as the Web App, provides additional control or debugging capabilities. Start the tool in interactive mode with `./run_rpc_tool.sh`. -The tool features auto-completion and command history. +Features include auto-completion and command history, with available commands fetched from the running Jukebox service. -The list of available commands is fetched from the running Jukebox service. - -The tool can also be used to send commands directly, when passing a `-c` argument, e.g. `./run_rpc_tool.py -c host.shutdown`. +For direct command execution, use the `-c` argument, e.g., `./run_rpc_tool.sh -c host.shutdown`. ### Publicity Sniffer -**Scriptname:** [run_publicity_sniffer.py](../../src/jukebox/run_publicity_sniffer.py) +**Scriptname:** [run_publicity_sniffer.sh](../../tools/run_publicity_sniffer.sh) -A command line tool that monitors all messages being sent out from the Jukebox via the publishing interface. Received messages are printed in the console. Mainly used for debugging. +This command-line tool monitors all messages sent from Jukebox through the publishing interface, printing received messages in the console. It is primarily used for debugging. diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index db3429bcc..74823a16a 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -21,6 +21,6 @@ RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ ## Configuration In `jukebox.yaml` (and all other config files): -Always use relative path from settingsfile `../../`, but do not use relative paths with `~/`. +Always use relative path from folder `src/jukebox` (`../../`), but do not use relative paths with `~/`. **Sole** exception is in `playermpd.mpd_conf`. diff --git a/installation/components/setup_configure_audio.sh b/installation/components/setup_configure_audio.sh new file mode 100755 index 000000000..dc1313e1c --- /dev/null +++ b/installation/components/setup_configure_audio.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Runner script to ensure +# - correct venv activation +# - independent from working directory + +# Change working directory to project root +SOURCE=${BASH_SOURCE[0]} +SCRIPT_DIR="$(dirname "$SOURCE")" +PROJECT_ROOT="$SCRIPT_DIR"/../.. +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } + +cd src/jukebox || { echo "Could not change directory"; exit 1; } +python run_configure_audio.py $@ diff --git a/installation/components/setup_rfid_reader.sh b/installation/components/setup_rfid_reader.sh new file mode 100755 index 000000000..ebabcf12b --- /dev/null +++ b/installation/components/setup_rfid_reader.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Runner script to ensure +# - correct venv activation +# - independent from working directory + +# Change working directory to project root +SOURCE=${BASH_SOURCE[0]} +SCRIPT_DIR="$(dirname "$SOURCE")" +PROJECT_ROOT="$SCRIPT_DIR"/../.. +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } + +cd src/jukebox || { echo "Could not change directory"; exit 1; } +python run_register_rfid_reader.py $@ diff --git a/installation/routines/install.sh b/installation/routines/install.sh index 66a65203c..4ff3e1ac9 100644 --- a/installation/routines/install.sh +++ b/installation/routines/install.sh @@ -15,6 +15,6 @@ install() { setup_rfid_reader optimize_boot_time setup_autohotspot - setup_login_message + setup_postinstall cleanup } diff --git a/installation/routines/set_raspi_config.sh b/installation/routines/set_raspi_config.sh index 6bfa8e725..7f39a0ba5 100644 --- a/installation/routines/set_raspi_config.sh +++ b/installation/routines/set_raspi_config.sh @@ -31,7 +31,3 @@ _run_set_raspi_config() { set_raspi_config() { run_with_log_frame _run_set_raspi_config "Set default raspi-config" } - -setup_login_message() { - sudo cp -f "${INSTALLATION_PATH}/resources/system/99-rpi-jukebox-rfid-welcome" "/etc/update-motd.d/99-rpi-jukebox-rfid-welcome" -} diff --git a/installation/routines/setup_postinstall.sh b/installation/routines/setup_postinstall.sh new file mode 100644 index 000000000..f448635f4 --- /dev/null +++ b/installation/routines/setup_postinstall.sh @@ -0,0 +1,13 @@ +_setup_login_message() { + local login_message_welcome_file="/etc/update-motd.d/99-rpi-jukebox-rfid-welcome" + sudo cp -f "${INSTALLATION_PATH}/resources/system/99-rpi-jukebox-rfid-welcome" "$login_message_welcome_file" + sudo chmod +x "$login_message_welcome_file" +} + +_run_setup_postinstall() { + _setup_login_message +} + +setup_postinstall() { + run_with_log_frame _run_setup_postinstall "Post install" +} diff --git a/installation/routines/setup_rfid_reader.sh b/installation/routines/setup_rfid_reader.sh index 3003d79a4..c7f3b7120 100644 --- a/installation/routines/setup_rfid_reader.sh +++ b/installation/routines/setup_rfid_reader.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash _run_setup_rfid_reader() { - run_and_print_lc python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" + local script="${INSTALLATION_PATH}"/installation/components/setup_rfid_reader.sh + sudo chmod +x "$script" + run_and_print_lc "$script" } setup_rfid_reader() { diff --git a/resources/default-services/jukebox-daemon.service b/resources/default-services/jukebox-daemon.service index 050897e14..7898088e2 100644 --- a/resources/default-services/jukebox-daemon.service +++ b/resources/default-services/jukebox-daemon.service @@ -10,8 +10,8 @@ After=network.target sound.target mpd.service pulseaudio.service Requires=mpd.service pulseaudio.service [Service] -WorkingDirectory=%%INSTALLATION_PATH%%/src/jukebox -ExecStart=/bin/bash -c 'source %%INSTALLATION_PATH%%/.venv/bin/activate && python run_jukebox.py' +WorkingDirectory=%%INSTALLATION_PATH%% +ExecStart=/bin/bash -c '%%INSTALLATION_PATH%%/run_jukebox.sh' StandardOutput=inherit StandardError=inherit Restart=always diff --git a/resources/default-settings/jukebox.default.yaml b/resources/default-settings/jukebox.default.yaml index 5c37f178a..c087cc024 100644 --- a/resources/default-settings/jukebox.default.yaml +++ b/resources/default-settings/jukebox.default.yaml @@ -1,5 +1,5 @@ # IMPORTANT: -# Always use relative path from settingsfile `../../`, but do not use relative paths with `~/`. +# Always use relative path from folder `src/jukebox` (`../../`), but do not use relative paths with `~/`. # Sole (!) exception is in playermpd.mpd_conf system: box_name: Jukebox diff --git a/resources/default-settings/pulseaudio.default.pa b/resources/default-settings/pulseaudio.default.pa index ee91ff9bf..e4d20ec53 100644 --- a/resources/default-settings/pulseaudio.default.pa +++ b/resources/default-settings/pulseaudio.default.pa @@ -147,5 +147,5 @@ load-module module-filter-apply #set-default-source input ### Configuration by Jukebox's Tool may come below -# Run ./run_configure_audio.py for configuration +# Run ./installation/components/setup_configure_audio.sh for configuration diff --git a/resources/system/99-rpi-jukebox-rfid-welcome b/resources/system/99-rpi-jukebox-rfid-welcome index 1b9904f96..50cc71cc7 100644 --- a/resources/system/99-rpi-jukebox-rfid-welcome +++ b/resources/system/99-rpi-jukebox-rfid-welcome @@ -1,16 +1,12 @@ #!/usr/bin/env bash echo " -######################################################### +################################################## ___ __ ______ _ __________ ____ __ _ _ / _ \/ // / __ \/ |/ / _/ __/( _ \ / \( \/ ) / ___/ _ / /_/ / // // _/ ) _ (( O )) ( /_/ /_//_/\____/_/|_/___/____/ (____/ \__/(_/\_) -future3 -If you want to run a python script from the project -activate the venv before with 'source .venv/bin/activate' -See also https://github.com/MiczFlor/RPi-Jukebox-RFID/ -blob/future3/main/documentation/developers/python.md - -#########################################################" + Welcome to your Phoniebox +################################################## +" diff --git a/run_docgeneration.sh b/run_docgeneration.sh index 22ab8bc14..4fb5f1dc9 100755 --- a/run_docgeneration.sh +++ b/run_docgeneration.sh @@ -1,12 +1,16 @@ #!/usr/bin/env bash -# Runner script for pydoc-markdown to ensure +# Runner script to ensure +# - correct venv activation # - independent from working directory -# Change working directory to location of script +# Change working directory to project root SOURCE=${BASH_SOURCE[0]} SCRIPT_DIR="$(dirname "$SOURCE")" -cd "$SCRIPT_DIR" || (echo "Could not change to top-level project directory" && exit 1) +PROJECT_ROOT="$SCRIPT_DIR" +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } # Run pydoc-markdown # make sure, directory exists diff --git a/run_flake8.sh b/run_flake8.sh index a9ec73285..f6603dc0a 100755 --- a/run_flake8.sh +++ b/run_flake8.sh @@ -1,13 +1,16 @@ #!/usr/bin/env bash -# Runner script for flak8 to ensure -# - correct config file +# Runner script to ensure +# - correct venv activation # - independent from working directory -# Change working directory to location of script +# Change working directory to project root SOURCE=${BASH_SOURCE[0]} SCRIPT_DIR="$(dirname "$SOURCE")" -cd "$SCRIPT_DIR" || (echo "Could not change to top-level project directory" && exit 1) +PROJECT_ROOT="$SCRIPT_DIR" +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } # Run flake8 flake8 --config .flake8 "$@" diff --git a/run_jukebox.sh b/run_jukebox.sh new file mode 100755 index 000000000..48e8aa7ba --- /dev/null +++ b/run_jukebox.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Runner script to ensure +# - correct venv activation +# - independent from working directory + +# Change working directory to project root +SOURCE=${BASH_SOURCE[0]} +SCRIPT_DIR="$(dirname "$SOURCE")" +PROJECT_ROOT="$SCRIPT_DIR" +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } + +cd src/jukebox || { echo "Could not change directory"; exit 1; } +python run_jukebox.py $@ diff --git a/run_pytest.sh b/run_pytest.sh index 766f05182..c944419ae 100755 --- a/run_pytest.sh +++ b/run_pytest.sh @@ -1,13 +1,16 @@ #!/usr/bin/env bash -# Runner script for pytest to ensure -# - correct config file +# Runner script to ensure +# - correct venv activation # - independent from working directory -# Change working directory to location of script +# Change working directory to project root SOURCE=${BASH_SOURCE[0]} SCRIPT_DIR="$(dirname "$SOURCE")" -cd "$SCRIPT_DIR" || (echo "Could not change to top-level project directory" && exit 1) +PROJECT_ROOT="$SCRIPT_DIR" +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } # Run pytest pytest -c pytest.ini $@ diff --git a/src/jukebox/components/volume/__init__.py b/src/jukebox/components/volume/__init__.py index 4782581ca..9dd827e4a 100644 --- a/src/jukebox/components/volume/__init__.py +++ b/src/jukebox/components/volume/__init__.py @@ -603,7 +603,7 @@ def parse_config() -> List[PulseAudioSinkClass]: logger.error(f"Configured sink '{pulse_sink_name}' not available sinks '{all_sinks}!\n" f"Using default sink '{default_sink_name}' as fallback\n" f"Things like audio sink toggle and volume limit will not work as expected!\n" - f"Please run audio config tool: ./run_configure_audio.py") + f"Please run audio config tool: ./installation/components/setup_configure_audio.sh") sink_list.append(PulseAudioSinkClass(alias, pulse_sink_name, volume_limit)) key = 'secondary' diff --git a/src/jukebox/run_configure_audio.py b/src/jukebox/run_configure_audio.py old mode 100755 new mode 100644 diff --git a/src/jukebox/run_publicity_sniffer.py b/src/jukebox/run_publicity_sniffer.py old mode 100755 new mode 100644 diff --git a/src/jukebox/run_register_rfid_reader.py b/src/jukebox/run_register_rfid_reader.py old mode 100755 new mode 100644 index 18a1614d8..91d363157 --- a/src/jukebox/run_register_rfid_reader.py +++ b/src/jukebox/run_register_rfid_reader.py @@ -33,7 +33,8 @@ def main(): # The default config file relative to this files location and independent of working directory - cfg_file_default = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/../../shared/settings/rfid.yaml') + script_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + cfg_file_default = os.path.abspath(os.path.join(script_path, '../../shared/settings/rfid.yaml')) parser = argparse.ArgumentParser() parser.add_argument("-f", "--force", diff --git a/src/jukebox/run_rpc_tool.py b/src/jukebox/run_rpc_tool.py old mode 100755 new mode 100644 diff --git a/tools/run_publicity_sniffer.sh b/tools/run_publicity_sniffer.sh new file mode 100755 index 000000000..2ea47b48b --- /dev/null +++ b/tools/run_publicity_sniffer.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Runner script to ensure +# - correct venv activation +# - independent from working directory + +# Change working directory to project root +SOURCE=${BASH_SOURCE[0]} +SCRIPT_DIR="$(dirname "$SOURCE")" +PROJECT_ROOT="$SCRIPT_DIR"/.. +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } + +cd src/jukebox || { echo "Could not change directory"; exit 1; } +python run_publicity_sniffer.py $@ diff --git a/tools/run_rpc_tool.sh b/tools/run_rpc_tool.sh new file mode 100755 index 000000000..dad895c56 --- /dev/null +++ b/tools/run_rpc_tool.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Runner script to ensure +# - correct venv activation +# - independent from working directory + +# Change working directory to project root +SOURCE=${BASH_SOURCE[0]} +SCRIPT_DIR="$(dirname "$SOURCE")" +PROJECT_ROOT="$SCRIPT_DIR"/.. +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +source .venv/bin/activate || { echo "ERROR: Failed to activate virtual environment for python"; exit 1; } + +cd src/jukebox || { echo "Could not change directory"; exit 1; } +python run_rpc_tool.py $@ From 1e1ae258f0837026c944cb5be93111e894fc2f5e Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sun, 4 Feb 2024 17:06:16 +0100 Subject: [PATCH 081/121] prevent installer rerun and remove update path (#2235) * fix doc * prevent run with existing installation * update docs for update path * Update documentation/builders/update.md Co-authored-by: s-martin --------- Co-authored-by: s-martin --- .githooks/post-merge | 17 ++++++++++++++++- documentation/builders/gpio.md | 3 +-- documentation/builders/update.md | 30 +++++------------------------- installation/install-jukebox.sh | 15 ++++++++++++++- 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/.githooks/post-merge b/.githooks/post-merge index 07b81db41..59e132b7c 100755 --- a/.githooks/post-merge +++ b/.githooks/post-merge @@ -58,7 +58,17 @@ warn_githooks() { echo "$ cp .githooks/* .git/hooks/." echo "************************************************************" echo -e "\n" +} +warn_installer() { + echo -e "\n" + echo "************************************************************" + echo "ATTENTION: Installer sources have changed since last pull!" + echo "" + echo "Rerun the installer to apply changes" + echo "$ ./installation/install-jukebox.sh" + echo "************************************************************" + echo -e "\n" } # files_changed="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)" @@ -66,6 +76,7 @@ webapp_changed="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD src webapp_dep_changed="$(git diff --name-only --no-commit-id ORIG_HEAD HEAD src/webapp/package.json)" python_req_changed="$(git diff --name-only --no-commit-id ORIG_HEAD HEAD requirements.txt)" githooks_changed="$(git diff --name-only --no-commit-id ORIG_HEAD HEAD .githooks)" +installer_changed="$(git diff --name-only --no-commit-id ORIG_HEAD HEAD installation)" if [[ -n $python_req_changed ]]; then warn_python_requirements @@ -81,5 +92,9 @@ if [[ -n $githooks_changed ]]; then warn_githooks fi +if [[ -n $installer_changed ]]; then + warn_installer +fi + echo -e "\nTo see a summary of what happened since your last pull, do:" -echo -e "git show --oneline -s ORIG_HEAD..HEAD\n" \ No newline at end of file +echo -e "git show --oneline -s ORIG_HEAD..HEAD\n" diff --git a/documentation/builders/gpio.md b/documentation/builders/gpio.md index 37622edc0..9d496c6b7 100644 --- a/documentation/builders/gpio.md +++ b/documentation/builders/gpio.md @@ -2,8 +2,7 @@ ## Enabling GPIO -The GPIO module needs to be enabled in your main configuration file ``shared/settings/jukebox.yaml``. Look for the -this entry and modify it accordingly: +The GPIO module needs to be enabled in your main configuration file ``shared/settings/jukebox.yaml``. Look for this entry and modify it accordingly: ```yml gpioz: diff --git a/documentation/builders/update.md b/documentation/builders/update.md index e84655e7c..cb919492a 100644 --- a/documentation/builders/update.md +++ b/documentation/builders/update.md @@ -2,34 +2,14 @@ ## Updating your Jukebox Version 3 -### Update from v3.2.1 and prior +### Update from v3.5.0 and prior -As there are some significant changes in the installation, a new setup on a fresh image is required. +As there are some significant changes in the Jukebox installation, no updates can be performed with the installer. +Please backup your './shared' folder and changed files and run a new installation on a fresh image. +Restore your old files after the new installation was successful and check if new mandatory settings have been added. -### General - -Things on Version 3 are moving fast and you may want to keep up with recent changes. Since we are in Alpha Release stage, -a fair number of fixes are expected to be committed in the near future. - -You will need to do three things to update your version from develop (or the next release candidate version) - -1. Pull the newest code base from Github -2. Check for new entries in the configuration -3. Re-build the WebUI - -```bash -# Switch to develop (if desired) -$ git checkout future3/develop - -# Get latest code -$ git pull - -# Check if new (mandatory) options appeared in jukebox.yaml -# with your favourite diff tool and merge them +``` bash $ diff shared/settings/jukebox.yaml resources/default-settings/jukebox.default.yaml - -$ cd src/webapp -$ ./run_rebuild.sh -u ``` ## Migration Path from Version 2 diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 1ec3f9ad5..1ab96f3a1 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -103,6 +103,18 @@ _check_os_type() { fi } +_check_existing_installation() { + if [[ -e "${INSTALLATION_PATH}" ]]; then + print_lc " +############## EXISTING INSTALLATION FOUND ############## +Rerunning the installer over an existing installation is +currently not supported (overwrites settings, etc). +Please backup your 'shared' folder and manually changed +files and run the installation on a fresh image." + exit 1 + fi +} + _download_jukebox_source() { log "#########################################################" print_c "Downloading Phoniebox software from Github ..." @@ -122,7 +134,7 @@ _download_jukebox_source() { if [[ -z "${GIT_HASH}" ]]; then exit_on_error "ERROR: Couldn't determine git hash from download." fi - mv "$git_repo_download" "$GIT_REPO_NAME" + mv "$git_repo_download" "$GIT_REPO_NAME" || exit_on_error "ERROR: Can't overwrite existing installation." log "\nDONE: Downloading Phoniebox software from Github" log "#########################################################" } @@ -143,6 +155,7 @@ _setup_logging ### CHECK PREREQUISITE _check_os_type +_check_existing_installation ### RUN INSTALLATION log "Current User: $CURRENT_USER" From 32d57f5d73e57365859e8545394ae3b002a1d4a4 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sun, 4 Feb 2024 20:07:18 +0100 Subject: [PATCH 082/121] Bump to v3.5.0 --- src/jukebox/jukebox/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index 37382cee7..65ccc87c0 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -2,7 +2,7 @@ VERSION_MAJOR = 3 VERSION_MINOR = 5 VERSION_PATCH = 0 -VERSION_EXTRA = "alpha" +VERSION_EXTRA = "" # build a version string in compliance with the SemVer specification # https://semver.org/#semantic-versioning-specification-semver From 2fe705507f4241c50bff882e31b0dff6121094e0 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sun, 4 Feb 2024 20:37:32 +0100 Subject: [PATCH 083/121] NetworkManager Support (#2218) * reworked autohotspot configuration option * updated autohotspot creation (copied changes from v2) * fix placeholder prefix/suffix (%%) * moved files in subdir for dhcpcd * renamed setup_autohotspot file * added switch for network settings * added autohotspot based on NetworkManager * update function names * fixed duplicate variables * fix var check * handle CI run for autohotspot setup * changed network management check for ci * fixed timer service name * renamed script. delete obsolete file * fix syntax * fix timer service unit definition * move is is_service_enabled to helper class * extracted konstant and fixed formatting * fix indentation * added shebang again * some refactorings in autohotspot script * fix checks * fix for local vars and arrays. refactor nw_profiles. * refactored var value and log output * refactor device and ssid checks * refactore force_hotspot option * refactor is_active_ap check and bugfixes * update log output * made timer reenablement configurable * phoniebox header added * moved check to helper. removed obsolete code * removed ip configuration. changed ip * add note for static ip conf with autohotspot * harmonize logging and opts * changed dhcpcd timer from cron to systemd. add seperate daemon service for wpa-supplicant handling * fix wifi after restart if autohot spot is deactivated * add hostname to default hotspot ssid * updated autohotspot docs * fix static ip for NetworkManager. fix ipv6 disablement * fix boot file paths * some fixes * pull config file backup to helpers * combine service is-enabled calls * update network related docs * Update autohotspot.md Aligned some language and typos * Update installation.md Aligning wording and removing typos --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --- ci/ci-debian.Dockerfile | 4 +- ci/installation/run_install_common.sh | 2 +- ci/installation/run_install_faststartup.sh | 2 + ci/installation/run_install_libzmq_local.sh | 1 + .../run_install_webapp_download.sh | 2 + ci/installation/run_install_webapp_local.sh | 1 + documentation/builders/autohotspot.md | 114 ++---- documentation/builders/installation.md | 15 +- installation/includes/00_constants.sh | 3 +- installation/includes/01_default_config.sh | 6 +- installation/includes/02_helpers.sh | 93 ++++- installation/routines/customize_options.sh | 124 ++++-- installation/routines/optimize_boot_time.sh | 140 ++++--- installation/routines/set_raspi_config.sh | 12 +- installation/routines/setup_autohotspot.sh | 117 ++---- .../setup_autohotspot_NetworkManager.sh | 98 +++++ .../routines/setup_autohotspot_dhcpcd.sh | 185 +++++++++ .../autohotspot/NetworkManager/autohotspot | 353 ++++++++++++++++++ .../NetworkManager/autohotspot.service | 11 + .../NetworkManager/autohotspot.timer | 10 + resources/autohotspot/autohotspot | 193 ---------- resources/autohotspot/autohotspot.service | 11 - resources/autohotspot/autohotspot.timer | 3 - resources/autohotspot/dhcpcd/autohotspot | 228 +++++++++++ .../dhcpcd/autohotspot-daemon.service | 10 + .../autohotspot/dhcpcd/autohotspot.service | 11 + .../autohotspot/dhcpcd/autohotspot.timer | 10 + resources/autohotspot/dhcpcd/dnsmasq.conf | 7 + resources/autohotspot/dhcpcd/hostapd | 20 + .../autohotspot/{ => dhcpcd}/hostapd.conf | 8 +- src/cli_client/pbc.c | 32 +- .../components/hostif/linux/__init__.py | 52 ++- 32 files changed, 1327 insertions(+), 551 deletions(-) create mode 100644 installation/routines/setup_autohotspot_NetworkManager.sh create mode 100644 installation/routines/setup_autohotspot_dhcpcd.sh create mode 100644 resources/autohotspot/NetworkManager/autohotspot create mode 100644 resources/autohotspot/NetworkManager/autohotspot.service create mode 100644 resources/autohotspot/NetworkManager/autohotspot.timer delete mode 100644 resources/autohotspot/autohotspot delete mode 100644 resources/autohotspot/autohotspot.service delete mode 100644 resources/autohotspot/autohotspot.timer create mode 100644 resources/autohotspot/dhcpcd/autohotspot create mode 100644 resources/autohotspot/dhcpcd/autohotspot-daemon.service create mode 100644 resources/autohotspot/dhcpcd/autohotspot.service create mode 100644 resources/autohotspot/dhcpcd/autohotspot.timer create mode 100644 resources/autohotspot/dhcpcd/dnsmasq.conf create mode 100644 resources/autohotspot/dhcpcd/hostapd rename resources/autohotspot/{ => dhcpcd}/hostapd.conf (66%) diff --git a/ci/ci-debian.Dockerfile b/ci/ci-debian.Dockerfile index 5228d83d5..d4348ecdf 100644 --- a/ci/ci-debian.Dockerfile +++ b/ci/ci-debian.Dockerfile @@ -8,8 +8,8 @@ ENV TERM=xterm DEBIAN_FRONTEND=noninteractive ENV CI_RUNNING=true # create RPi configs to test installation -RUN touch /boot/config.txt -RUN echo "logo.nologo" > /boot/cmdline.txt +RUN mkdir -p /boot && touch /boot/config.txt && echo "logo.nologo" > /boot/cmdline.txt +RUN mkdir -p /boot/firmware && touch /boot/firmware/config.txt && echo "logo.nologo" > /boot/firmware/cmdline.txt RUN echo "--- install packages (1) ---" \ && apt-get update \ diff --git a/ci/installation/run_install_common.sh b/ci/installation/run_install_common.sh index b8c641580..0b73ae791 100644 --- a/ci/installation/run_install_common.sh +++ b/ci/installation/run_install_common.sh @@ -17,7 +17,7 @@ export ENABLE_WEBAPP_PROD_DOWNLOAD=true # n - use static ip # n - deactivate ipv6 # y - setup autohotspot -# n - use custom password +# n - change default configuration # n - deactivate bluetooth # n - disable on-chip audio # - - mpd overwrite config (only with existing installation) diff --git a/ci/installation/run_install_faststartup.sh b/ci/installation/run_install_faststartup.sh index 134aeca71..2a98a1869 100644 --- a/ci/installation/run_install_faststartup.sh +++ b/ci/installation/run_install_faststartup.sh @@ -11,11 +11,13 @@ SCRIPT_DIR="$(dirname "$SOURCE")" LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + # Run installation (in interactive mode) # y - start setup # y - use static ip # y - deactivate ipv6 # n - setup autohotspot +# - - change default configuration (only with autohotspot = y) # y - deactivate bluetooth # y - disable on-chip audio # - - mpd overwrite config (only with existing installation) diff --git a/ci/installation/run_install_libzmq_local.sh b/ci/installation/run_install_libzmq_local.sh index 335cb24a1..7ce6e14ac 100644 --- a/ci/installation/run_install_libzmq_local.sh +++ b/ci/installation/run_install_libzmq_local.sh @@ -17,6 +17,7 @@ export BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=true # n - use static ip # n - deactivate ipv6 # n - setup autohotspot +# - - change default configuration (only with autohotspot = y) # n - deactivate bluetooth # n - disable on-chip audio # - - mpd overwrite config (only with existing installation) diff --git a/ci/installation/run_install_webapp_download.sh b/ci/installation/run_install_webapp_download.sh index ded27ec54..ee72588ef 100644 --- a/ci/installation/run_install_webapp_download.sh +++ b/ci/installation/run_install_webapp_download.sh @@ -11,11 +11,13 @@ SCRIPT_DIR="$(dirname "$SOURCE")" LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + # Run installation (in interactive mode) # y - start setup # n - use static ip # n - deactivate ipv6 # n - setup autohotspot +# - - change default configuration (only with autohotspot = y) # n - deactivate bluetooth # n - disable on-chip audio # - - mpd overwrite config (only with existing installation) diff --git a/ci/installation/run_install_webapp_local.sh b/ci/installation/run_install_webapp_local.sh index 7af16df3a..bd7ce8def 100644 --- a/ci/installation/run_install_webapp_local.sh +++ b/ci/installation/run_install_webapp_local.sh @@ -17,6 +17,7 @@ export ENABLE_WEBAPP_PROD_DOWNLOAD=false # n - use static ip # n - deactivate ipv6 # n - setup autohotspot +# - - change default configuration (only with autohotspot = y) # n - deactivate bluetooth # n - disable on-chip audio # - - mpd overwrite config (only with existing installation) diff --git a/documentation/builders/autohotspot.md b/documentation/builders/autohotspot.md index ecf996f81..5a62a37ca 100644 --- a/documentation/builders/autohotspot.md +++ b/documentation/builders/autohotspot.md @@ -1,107 +1,63 @@ # Auto-Hotspot -The Auto-Hotspot function allows the Jukebox to switch between its -connection between a known WiFi and an automatically generated hotspot -so that you can still access via SSH or Web App. +The Auto-Hotspot function enables the Jukebox to switch its connection between a known WiFi network and an automatically generated hotspot, allowing access via SSH or Web App. > [!IMPORTANT] -> Please configure the WiFi connection to your home access point before enabling these feature! - -To create a hotspot and allow clients to connect -[hostapd](http://w1.fi/hostapd/) and [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html). +> Please configure the WiFi connection to your home access point before enabling this feature! ## How to connect -When the Jukebox is not able to connect to a known WiFi it will create a -hotspot named `Phoniebox_Hotspot`. You will be able to connect to this -hotspot using the given password in the installation or the default -password: `PlayItLoud!` - -### Web App - -After connecting to the `Phoniebox_Hotspot` you are able to connect to -the Web App accessing the website [10.0.0.5](http://10.0.0.5/). +When the Jukebox cannot connect to a known WiFi, it will automatically create a hotspot. +You can connect to this hotspot using the password set during installation. +Afterwards, you can access the Web App or connect via SSH as before, using the IP from the configuration. -### ssh - -After connecting to the `Phoniebox_Hotspot` you are able to connect via -ssh to your Jukebox - -``` bash -ssh @10.0.0.5 -``` - -## Changing basic configuration of the hotspot - -The whole hotspot configuration can be found at -`/etc/hostapd/hostapd.conf`. - -The following parameters are relevant: - -- `ssid` for the displayed hotspot name -- `wpa_passphrase` for the password of the hotspot -- `country_code` the country you are currently in - -``` bash -$ cat /etc/hostapd/hostapd.conf - -#2.4GHz setup wifi 80211 b,g,n -interface=wlan0 -driver=nl80211 -ssid=Phoniebox_Hotspot -hw_mode=g -channel=8 -wmm_enabled=0 -macaddr_acl=0 -auth_algs=1 -ignore_broadcast_ssid=0 -wpa=2 -wpa_passphrase==PlayItLoud! -wpa_key_mgmt=WPA-PSK -wpa_pairwise=CCMP TKIP -rsn_pairwise=CCMP - -#80211n - Change GB to your WiFi country code -country_code=DE -ieee80211n=1 -ieee80211d=1 +The default configuration is +``` text +* SSID : Phoniebox_Hotspot_ +* Password : PlayItLoud! +* WiFi Country Code : DE +* IP : 10.0.0.1 ``` ## Disabling automatism -Auto-Hotspot can be enabled or disabled using the Web App. +Auto-Hotspot can be enabled or disabled using the Web App or RPC Commands. + +> [!NOTE] +> Disabling the Auto-Hotspot will run the WiFi check again and maintain the last connection state until reboot. > [!IMPORTANT] -> Disabling or enabling will keep the last state. +> If you disable this feature, you will lose access to the Jukebox if you are not near a known WiFi after reboot! ## Troubleshooting -### Phoniebox is not connecting to the known WiFi - -The script will fall back to the hotspot so you still have some type of -connection. - -Check your password in `/etc/wpa_supplicant/wpa_supplicant.conf`. - ### AutoHotspot functionality is not working -You can check the output of the script by running the following script: +Check the `autohotspot.service` status +``` bash +sudo systemctl status autohotspot.service +``` +and logs ``` bash -$ sudo /usr/bin/autohotspot +sudo journalctl -u autohotspot.service -n 50 ``` -### You need to add a new wifi network to the Raspberry Pi +### Jukebox is not connecting to the known WiFi + +The script will fall back to the hotspot, ensuring you still have some type of connection. + +Check your WiFi configuration. -Because it is in Auto-Hotspot mode, you won\'t be able to scan for new -wifi signals. +### You need to add a new WiFi network to the Raspberry Pi -You will need to add a new network to -`/etc/wpa_supplicant/wpa_supplicant.conf` manually. Enter the following -details replacing mySSID and myPassword with your details. If your WiFi -has a hidden SSID then include the line `scan_ssid=1`. +#### Using the command line +Connect to the hotspot and open a terminal. Use the [raspi-config](https://www.raspberrypi.com/documentation/computers/configuration.html#wireless-lan) tool to add the new WiFi. ## Resources -[Raspberry Pi - Auto WiFi Hotspot Switch - Direct -Connection](https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection) +* [Raspberry Connect - Auto WiFi Hotspot Switch](https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection) +* [Raspberry Pi - Configuring networking](https://www.raspberrypi.com/documentation/computers/configuration.html#using-the-command-line) +* [dhcpcd / wpa_supplicant]() + * [hostapd](http://w1.fi/hostapd/) + * [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index 1d4b18470..7cd321b7a 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -11,7 +11,6 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr 2. Download the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) and run it 3. Click on "Raspberry Pi Device" and select "No filtering" 4. As operating system select **Raspberry Pi OS (other)** and then **Raspberry Pi OS Lite (Legacy, 32-bit)** (no desktop environment). *64-bit is currently not supported*. - * Bookworm support is partly broken, see [here](#workaround-for-network-related-features-on-bookworm). * For Pi 4 and newer also check [this](#workaround-for-64-bit-kernels-pi-4-and-newer). 5. Select your Micro SD card (your card will be formatted) 6. After you click `Next`, a prompt will ask you if you like to customize the OS settings @@ -81,21 +80,21 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to ### Pre-install preparation / workarounds -#### Workaround for network related features on Bookworm +#### Network management since Bookworm
-With Bookworm the network settings have changed. Now "NetworkManager" is used instead of "dhcpcd". -This breaks breaks network related features like "Static IP", "Wifi Setup" and "Autohotspot". -Before running the installation, the network config has to be changed via raspi-config, to use the "old" dhcpcd network settings. +With Bookworm, network management has changed. Now, "NetworkManager" is used instead of "dhcpcd". +Both methods are supported during installation, but "NetworkManager" is recommended as it is simpler to set up and use. +For Bullseye, this can also be activated, though it requires a manual process before running the installation. :warning: -If the settings are changed, your network will reset and Wifi will not be configured, so you lose ssh access via wireless connection. -So make sure you use a wired connection or perform the following steps in a local terminal with a connected monitor and keyboard. +If the settings are changed, your network will reset, and WiFi will not be configured, causing you to lose SSH access via wireless connection. +Therefore, make sure you use a wired connection or perform the following steps in a local terminal with a connected monitor and keyboard. Change network config * run `sudo raspi-config` * select `6 - Advanced Options` * select `AA - Network Config` -* select `dhcpcd` +* select `NetworkManager` If you need Wifi, add the information now * select `1 - System Options` diff --git a/installation/includes/00_constants.sh b/installation/includes/00_constants.sh index 89299989c..574febed3 100644 --- a/installation/includes/00_constants.sh +++ b/installation/includes/00_constants.sh @@ -1,7 +1,6 @@ -RPI_BOOT_CONFIG_FILE="/boot/config.txt" -RPI_BOOT_CMDLINE_FILE="/boot/cmdline.txt" SHARED_PATH="${INSTALLATION_PATH}/shared" SETTINGS_PATH="${SHARED_PATH}/settings" +SYSTEMD_PATH="/etc/systemd/system" SYSTEMD_USR_PATH="/usr/lib/systemd/user" VIRTUAL_ENV="${INSTALLATION_PATH}/.venv" # Do not change this directory! It must match MPDs expectation where to find the user configuration diff --git a/installation/includes/01_default_config.sh b/installation/includes/01_default_config.sh index ec7b67b66..a3115fee6 100644 --- a/installation/includes/01_default_config.sh +++ b/installation/includes/01_default_config.sh @@ -4,8 +4,11 @@ BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=${BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE:-"false" ENABLE_STATIC_IP=true DISABLE_IPv6=true ENABLE_AUTOHOTSPOT=false -AUTOHOTSPOT_CHANGE_PASSWORD=false +AUTOHOTSPOT_PROFILE="Phoniebox_Hotspot" +AUTOHOTSPOT_SSID="$AUTOHOTSPOT_PROFILE" AUTOHOTSPOT_PASSWORD="PlayItLoud!" +AUTOHOTSPOT_IP="10.0.0.1" +AUTOHOTSPOT_COUNTRYCODE="DE" DISABLE_BLUETOOTH=true DISABLE_SSH_QOS=true DISABLE_BOOT_SCREEN=true @@ -18,7 +21,6 @@ ENABLE_SAMBA=true ENABLE_WEBAPP=true ENABLE_KIOSK_MODE=false DISABLE_ONBOARD_AUDIO=false -DISABLE_ONBOARD_AUDIO_BACKUP="${RPI_BOOT_CONFIG_FILE}.backup.audio_on_$(date +%d.%m.%y_%H.%M.%S)" # Always try to use GIT with SSH first, and on failure drop down to HTTPS GIT_USE_SSH=${GIT_USE_SSH:-"true"} diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 9cd51c824..be496b1b9 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -88,16 +88,17 @@ get_debian_version_number() { echo "$VERSION_ID" } -get_boot_config_path() { +_get_boot_file_path() { + local filename="$1" if [ "$(is_raspbian)" = true ]; then local debian_version_number=$(get_debian_version_number) # Bullseye and lower if [ "$debian_version_number" -le 11 ]; then - echo "/boot/config.txt" + echo "/boot/${filename}" # Bookworm and higher elif [ "$debian_version_number" -ge 12 ]; then - echo "/boot/firmware/config.txt" + echo "/boot/firmware/${filename}" else echo "unknown" fi @@ -106,6 +107,14 @@ get_boot_config_path() { fi } +get_boot_config_path() { + echo $(_get_boot_file_path "config.txt") +} + +get_boot_cmdline_path() { + echo $(_get_boot_file_path "cmdline.txt") +} + validate_url() { local url=$1 wget --spider ${url} >/dev/null 2>&1 @@ -119,6 +128,70 @@ download_from_url() { return $? } +get_string_length() { + local string="$1" + # "-n" option is needed otherwise an additional linebreak char is added by echo + echo -n ${string} | wc -m +} + +_get_service_enablement() { + local service="$1" + local option="${2:+$2 }" # optional, dont't quote in 'systemctl' call! + + if [[ -z "${service}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local actual_enablement=$(systemctl is-enabled ${option}${service} 2>/dev/null) + + echo "$actual_enablement" +} + +is_service_enabled() { + local service="$1" + local option="$2" + local actual_enablement=$(_get_service_enablement $service $option) + + if [[ "$actual_enablement" == "enabled" ]]; then + echo true + else + echo false + fi +} + +is_dhcpcd_enabled() { + echo $(is_service_enabled "dhcpcd.service") +} + +is_NetworkManager_enabled() { + echo $(is_service_enabled "NetworkManager.service") +} + +# create flag file if files does no exist (*.remove) or copy present conf to backup file (*.orig) +# to correctly handling de-/activation of corresponding feature +config_file_backup() { + local config_file="$1" + local config_flag_file="${config_file}.remove" + local config_orig_file="${config_file}.orig" + if [ ! -f "${config_file}" ]; then + sudo touch "${config_flag_file}" + elif [ ! -f "${config_orig_file}" ] && [ ! -f "${config_flag_file}" ]; then + sudo cp "${config_file}" "${config_orig_file}" + fi +} + +# revert config files backed up with `config_file_backup` +config_file_revert() { + local config_file="$1" + local config_flag_file="${config_file}.remove" + local config_orig_file="${config_file}.orig" + if [ -f "${config_flag_file}" ]; then + sudo rm "${config_flag_file}" "${config_file}" + elif [ -f "${config_orig_file}" ]; then + sudo mv "${config_orig_file}" "${config_file}" + fi +} + ### Verify helpers print_verify_installation() { log "\n @@ -220,7 +293,7 @@ verify_file_contains_string() { exit_on_error "ERROR: at least one parameter value is missing!" fi - if [[ ! $(grep -iw "${string}" "${file}") ]]; then + if [[ ! $(sudo grep -iw "${string}" "${file}") ]]; then exit_on_error "ERROR: '${string}' not found in '${file}'" fi log " CHECK" @@ -235,7 +308,7 @@ verify_file_contains_string_once() { exit_on_error "ERROR: at least one parameter value is missing!" fi - local file_contains_string_count=$(grep -oiw "${string}" "${file}" | wc -l) + local file_contains_string_count=$(sudo grep -oiw "${string}" "${file}" | wc -l) if [ "$file_contains_string_count" -lt 1 ]; then exit_on_error "ERROR: '${string}' not found in '${file}'" elif [ "$file_contains_string_count" -gt 1 ]; then @@ -247,7 +320,7 @@ verify_file_contains_string_once() { verify_service_state() { local service="$1" local desired_state="$2" - local option="${3:+$3 }" # optional, dont't quote in next call! + local option="${3:+$3 }" # optional, dont't quote in next call! log " Verify service '${option}${service}' is '${desired_state}'" if [[ -z "${service}" || -z "${desired_state}" ]]; then @@ -264,14 +337,14 @@ verify_service_state() { verify_service_enablement() { local service="$1" local desired_enablement="$2" - local option="${3:+$3 }" # optional, dont't quote in next call! + local option="$3" log " Verify service ${option}${service} is ${desired_enablement}" if [[ -z "${service}" || -z "${desired_enablement}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" fi - local actual_enablement=$(systemctl is-enabled ${option}${service}) + local actual_enablement=$(_get_service_enablement $service $option) if [[ ! "${actual_enablement}" == "${desired_enablement}" ]]; then exit_on_error "ERROR: service ${option}${service} is not ${desired_enablement} (state: ${actual_enablement})." fi @@ -281,14 +354,14 @@ verify_service_enablement() { verify_optional_service_enablement() { local service="$1" local desired_enablement="$2" - local option="${3:+$3 }" # optional, dont't quote in next call! + local option="$3" log " Verify service ${option}${service} is ${desired_enablement}" if [[ -z "${service}" || -z "${desired_enablement}" ]]; then exit_on_error "ERROR: at least one parameter value is missing!" fi - local actual_enablement=$(systemctl is-enabled ${option}${service}) 2>/dev/null + local actual_enablement=$(_get_service_enablement $service $option) if [[ -z "${actual_enablement}" ]]; then log " INFO: optional service ${option}${service} is not installed." elif [[ "${actual_enablement}" == "static" ]]; then diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index c903df189..de1dd5a19 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -49,51 +49,98 @@ Do you want to disable IPv6? [Y/n]" } _option_autohotspot() { - # ENABLE_AUTOHOTSPOT - clear_c - print_c "---------------------- AUTOHOTSPOT ---------------------- + # ENABLE_AUTOHOTSPOT + clear_c + print_c "---------------------- AUTOHOTSPOT ---------------------- When enabled, this service spins up a WiFi hotspot when the Phoniebox is unable to connect to a known WiFi. This way you can still access it. -Do you want to enable an Autohotpot? [y/N]" - read -r response - case "$response" in - [yY][eE][sS]|[yY]) - ENABLE_AUTOHOTSPOT=true - ;; - *) - ;; - esac +Note: +Static IP configuration cannot be enabled with +WiFi hotspot and will be disabled, if selected before. - if [ "$ENABLE_AUTOHOTSPOT" = true ]; then - print_c "Do you want to set a custom Password? (default: ${AUTOHOTSPOT_PASSWORD}) [y/N] " - read -r response_pw_q - case "$response_pw_q" in +Do you want to enable an Autohotspot? [y/N]" + read -r response + case "$response" in [yY][eE][sS]|[yY]) - while [ $(echo ${response_pw}|wc -m) -lt 8 ] - do - print_c "Please type the new password (at least 8 character)." - read -r response_pw - done - AUTOHOTSPOT_PASSWORD="${response_pw}" - ;; + ENABLE_AUTOHOTSPOT=true + ;; *) - ;; - esac + ;; + esac + + if [ "$ENABLE_AUTOHOTSPOT" = true ]; then + #add hostname to default SSID to prevent collision + local local_hostname=$(hostname) + AUTOHOTSPOT_SSID="${AUTOHOTSPOT_SSID}_${local_hostname}" + AUTOHOTSPOT_SSID="${AUTOHOTSPOT_SSID:0:32}" + + local response_autohotspot + while [[ $response_autohotspot != "n" ]] + do + print_c " +--- Current configuration for Autohotpot +SSID : $AUTOHOTSPOT_SSID +Password : $AUTOHOTSPOT_PASSWORD +WiFi Country Code : $AUTOHOTSPOT_COUNTRYCODE +IP : $AUTOHOTSPOT_IP +Do you want to change this values? [y/N]" + read -r response_autohotspot + case "$response_autohotspot" in + [yY][eE][sS]|[yY]) + local response_ssid="" + local response_ssid_length=0 + while [[ $response_ssid_length -lt 1 || $response_ssid_length -gt 32 ]] + do + print_c "Please type the hotspot ssid (must be between 1 and 32 characters long):" + read -r response_ssid + response_ssid_length=$(get_string_length ${response_ssid}) + done + + local response_pw="" + local response_pw_length=0 + while [[ $response_pw_length -lt 8 || $response_pw_length -gt 63 ]] + do + print_c "Please type the new password (must be between 8 and 63 characters long):" + read -r response_pw + response_pw_length=$(get_string_length ${response_pw}) + done + + local response_country_code="" + local response_country_code_length=0 + while [[ $response_country_code_length -ne 2 ]] + do + print_c "Please type the WiFi country code (e.g. DE, GB, CZ or US):" + read -r response_country_code + response_country_code="${response_country_code^^}" # to Uppercase + response_country_code_length=$(get_string_length ${response_country_code}) + done + + AUTOHOTSPOT_SSID="${response_ssid}" + AUTOHOTSPOT_PASSWORD="${response_pw}" + AUTOHOTSPOT_COUNTRYCODE="${response_country_code}" + ;; + *) + response_autohotspot=n + ;; + esac + done - if [ "$ENABLE_STATIC_IP" = true ]; then - print_c "Wifi hotspot cannot be enabled with static IP. Disabling static IP configuration." - ENABLE_STATIC_IP=false - log "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" - fi - fi + if [ "$ENABLE_STATIC_IP" = true ]; then + ENABLE_STATIC_IP=false + echo "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" + fi + fi - log "ENABLE_AUTOHOTSPOT=${ENABLE_AUTOHOTSPOT}" - if [ "$ENABLE_AUTOHOTSPOT" = true ]; then - log "AUTOHOTSPOT_PASSWORD=${AUTOHOTSPOT_PASSWORD}" - fi + echo "ENABLE_AUTOHOTSPOT=${ENABLE_AUTOHOTSPOT}" + if [ "$ENABLE_AUTOHOTSPOT" = true ]; then + echo "AUTOHOTSPOT_SSID=${AUTOHOTSPOT_SSID}" + echo "AUTOHOTSPOT_PASSWORD=${AUTOHOTSPOT_PASSWORD}" + echo "AUTOHOTSPOT_COUNTRYCODE=${AUTOHOTSPOT_COUNTRYCODE}" + echo "AUTOHOTSPOT_IP=${AUTOHOTSPOT_IP}" + fi } _option_bluetooth() { @@ -275,11 +322,10 @@ the on-chip audio. It will make the ALSA sound configuration easier. If you are planning to only use Bluetooth speakers, leave the on-chip audio enabled! -(This will touch your boot configuration in -${RPI_BOOT_CONFIG_FILE}. +(This will touch your boot configuration file. We will do our best not to mess anything up. However, -a backup copy will be written to -${DISABLE_ONBOARD_AUDIO_BACKUP} ) +a backup copy will be written. Please check the install +log after for further details.) Disable Pi's on-chip audio (headphone / jack output)? [y/N]" read -r response diff --git a/installation/routines/optimize_boot_time.sh b/installation/routines/optimize_boot_time.sh index bb6f71902..e3761c31d 100644 --- a/installation/routines/optimize_boot_time.sh +++ b/installation/routines/optimize_boot_time.sh @@ -4,8 +4,8 @@ OPTIMIZE_DHCP_CONF="/etc/dhcpcd.conf" OPTIMIZE_BOOT_CMDLINE_OPTIONS="consoleblank=1 logo.nologo quiet loglevel=0 plymouth.enable=0 vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fastboot noatime nodiratime noram" +OPTIMIZE_BOOT_CMDLINE_OPTIONS_IPV6="ipv6.disable=1" OPTIMIZE_DHCP_CONF_HEADER="## Jukebox DHCP Config" -OPTIMIZE_IPV6_CONF_HEADER="## Jukebox IPV6 Config" OPTIMIZE_BOOT_CONF_HEADER="## Jukebox Boot Config" _optimize_disable_irrelevant_services() { @@ -26,6 +26,24 @@ _optimize_disable_irrelevant_services() { sudo systemctl disable apt-daily-upgrade.timer } +_add_options_to_cmdline() { + local options="$1" + + local cmdlineFile=$(get_boot_cmdline_path) + if [ ! -s "${cmdlineFile}" ];then + sudo tee "${cmdlineFile}" <<-EOF +${options} +EOF + else + for option in $options + do + if ! grep -qiw "$option" "${cmdlineFile}" ; then + sudo sed -i "s/$/ $option/" "${cmdlineFile}" + fi + done + fi +} + # TODO: If false, actually make sure bluetooth is enabled _optimize_handle_bluetooth() { if [ "$DISABLE_BLUETOOTH" = true ] ; then @@ -37,58 +55,52 @@ _optimize_handle_bluetooth() { # TODO: Allow options to enable/disable wifi, Dynamic/Static IP etc. _optimize_static_ip() { - # Static IP Address and DHCP optimizations - if [ "$ENABLE_STATIC_IP" = true ] ; then - print_lc " Set static IP address" - if grep -q "${OPTIMIZE_DHCP_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then - log " Skipping. Already set up!" - else - # DHCP has not been configured - log " ${CURRENT_INTERFACE} is the default network interface" - log " ${CURRENT_GATEWAY} is the Router Gateway address" - log " Using ${CURRENT_IP_ADDRESS} as the static IP for now" - - sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF + # Static IP Address and DHCP optimizations + if [[ $(is_dhcpcd_enabled) == true ]]; then + if [ "$ENABLE_STATIC_IP" = true ] ; then + print_lc " Set static IP address" + if grep -q "${OPTIMIZE_DHCP_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then + log " Skipping. Already set up!" + else + # DHCP has not been configured + log " ${CURRENT_INTERFACE} is the default network interface" + log " ${CURRENT_GATEWAY} is the Router Gateway address" + log " Using ${CURRENT_IP_ADDRESS} as the static IP for now" + + sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF ${OPTIMIZE_DHCP_CONF_HEADER} interface ${CURRENT_INTERFACE} static ip_address=${CURRENT_IP_ADDRESS}/24 static routers=${CURRENT_GATEWAY} static domain_name_servers=${CURRENT_GATEWAY} +noarp EOF + fi + fi fi - fi } # TODO: Allow both Enable and Disable +# Disable ipv6 thoroughly on the system with kernel parameter _optimize_ipv6_arp() { - if [ "$DISABLE_IPv6" = true ] ; then - print_lc " Disabling IPV6" - if grep -q "${OPTIMIZE_IPV6_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then - log " Skipping. Already set up!" - else - sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF - -${OPTIMIZE_IPV6_CONF_HEADER} -noarp -ipv4only -noipv6 - -EOF + if [ "$DISABLE_IPv6" = true ] ; then + print_lc " Disabling IPV6" + _add_options_to_cmdline "${OPTIMIZE_BOOT_CMDLINE_OPTIONS_IPV6}" fi - fi } # TODO: Allow both Enable and Disable _optimize_handle_boot_screen() { + local configFile=$(get_boot_config_path) if [ "$DISABLE_BOOT_SCREEN" = true ] ; then log " Disable RPi rainbow screen" - if grep -q "${OPTIMIZE_BOOT_CONF_HEADER}" "$RPI_BOOT_CONFIG_FILE"; then + if grep -q "${OPTIMIZE_BOOT_CONF_HEADER}" "$configFile"; then log " Skipping. Already set up!" else - sudo tee -a $RPI_BOOT_CONFIG_FILE <<-EOF + sudo tee -a $configFile <<-EOF ${OPTIMIZE_BOOT_CONF_HEADER} disable_splash=1 @@ -103,25 +115,40 @@ _optimize_handle_boot_logs() { if [ "$DISABLE_BOOT_LOGS_PRINT" = true ] ; then log " Disable boot logs" - if [ ! -s "${RPI_BOOT_CMDLINE_FILE}" ];then - sudo tee "${RPI_BOOT_CMDLINE_FILE}" <<-EOF -${OPTIMIZE_BOOT_CMDLINE_OPTIONS} -EOF - else - for option in $OPTIMIZE_BOOT_CMDLINE_OPTIONS - do - if ! grep -qiw "$option" "${RPI_BOOT_CMDLINE_FILE}" ; then - sudo sed -i "s/$/ $option/" "${RPI_BOOT_CMDLINE_FILE}" - fi - done - fi + _add_options_to_cmdline "${OPTIMIZE_BOOT_CMDLINE_OPTIONS}" fi } +get_nm_active_profile() +{ + local active_profile=$(nmcli -g DEVICE,CONNECTION device status | grep "^${CURRENT_INTERFACE}" | cut -d':' -f2) + echo "$active_profile" +} + +_optimize_static_ip_NetworkManager() { + if [[ $(is_NetworkManager_enabled) == true ]]; then + if [ "$ENABLE_STATIC_IP" = true ] ; then + print_lc " Set static IP address" + log " ${CURRENT_INTERFACE} is the default network interface" + log " ${CURRENT_GATEWAY} is the Router Gateway address" + log " Using ${CURRENT_IP_ADDRESS} as the static IP for now" + local active_profile=$(get_nm_active_profile) + sudo nmcli connection modify "$active_profile" ipv4.method manual ipv4.address "${CURRENT_IP_ADDRESS}/24" ipv4.gateway "$CURRENT_GATEWAY" ipv4.dns "$CURRENT_GATEWAY" + #else + # for future deactivation + #sudo nmcli connection modify "$active_profile" ipv4.method auto ipv4.address "" ipv4.gateway "" ipv4.dns "" + fi + fi +} + _optimize_check() { print_verify_installation + local cmdlineFile=$(get_boot_cmdline_path) + local configFile=$(get_boot_config_path) + + verify_optional_service_enablement keyboard-setup.service disabled verify_optional_service_enablement triggerhappy.service disabled verify_optional_service_enablement triggerhappy.socket disabled @@ -137,33 +164,44 @@ _optimize_check() { fi if [ "$ENABLE_STATIC_IP" = true ] ; then - verify_file_contains_string_once "${OPTIMIZE_DHCP_CONF_HEADER}" "${OPTIMIZE_DHCP_CONF}" - verify_file_contains_string "${CURRENT_INTERFACE}" "${OPTIMIZE_DHCP_CONF}" - verify_file_contains_string "${CURRENT_IP_ADDRESS}" "${OPTIMIZE_DHCP_CONF}" - verify_file_contains_string "${CURRENT_GATEWAY}" "${OPTIMIZE_DHCP_CONF}" + if [[ $(is_dhcpcd_enabled) == true ]]; then + verify_file_contains_string_once "${OPTIMIZE_DHCP_CONF_HEADER}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_INTERFACE}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_IP_ADDRESS}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_GATEWAY}" "${OPTIMIZE_DHCP_CONF}" + fi + + if [[ $(is_NetworkManager_enabled) == true ]]; then + local active_profile=$(get_nm_active_profile) + local active_profile_path="/etc/NetworkManager/system-connections/${active_profile}.nmconnection" + verify_files_exists "${active_profile_path}" + verify_file_contains_string "${CURRENT_IP_ADDRESS}" "${active_profile_path}" + verify_file_contains_string "${CURRENT_GATEWAY}" "${active_profile_path}" + fi fi if [ "$DISABLE_IPv6" = true ] ; then - verify_file_contains_string_once "${OPTIMIZE_IPV6_CONF_HEADER}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string_once "${OPTIMIZE_BOOT_CMDLINE_OPTIONS_IPV6}" "${cmdlineFile}" fi if [ "$DISABLE_BOOT_SCREEN" = true ] ; then - verify_file_contains_string_once "${OPTIMIZE_BOOT_CONF_HEADER}" "${RPI_BOOT_CONFIG_FILE}" + verify_file_contains_string_once "${OPTIMIZE_BOOT_CONF_HEADER}" "${configFile}" fi if [ "$DISABLE_BOOT_LOGS_PRINT" = true ] ; then for option in $OPTIMIZE_BOOT_CMDLINE_OPTIONS do - verify_file_contains_string_once $option "${RPI_BOOT_CMDLINE_FILE}" + verify_file_contains_string_once $option "${cmdlineFile}" done fi } _run_optimize_boot_time() { _optimize_disable_irrelevant_services + _optimize_handle_boot_screen + _optimize_handle_boot_logs _optimize_handle_bluetooth _optimize_static_ip + _optimize_static_ip_NetworkManager _optimize_ipv6_arp - _optimize_handle_boot_screen - _optimize_handle_boot_logs _optimize_check } diff --git a/installation/routines/set_raspi_config.sh b/installation/routines/set_raspi_config.sh index 7f39a0ba5..ef4707c91 100644 --- a/installation/routines/set_raspi_config.sh +++ b/installation/routines/set_raspi_config.sh @@ -17,13 +17,15 @@ _run_set_raspi_config() { # On-board audio if [ "$DISABLE_ONBOARD_AUDIO" == true ]; then + local configFile=$(get_boot_config_path) log " Disable on-chip BCM audio" - if grep -q -E "^dtparam=([^,]*,)*audio=(on|true|yes|1).*" "${RPI_BOOT_CONFIG_FILE}" ; then - log " Backup ${RPI_BOOT_CONFIG_FILE} --> ${DISABLE_ONBOARD_AUDIO_BACKUP}" - sudo cp "${RPI_BOOT_CONFIG_FILE}" "${DISABLE_ONBOARD_AUDIO_BACKUP}" - sudo sed -i "s/^\(dtparam=\([^,]*,\)*\)audio=\(on\|true\|yes\|1\)\(.*\)/\1audio=off\4/g" "${RPI_BOOT_CONFIG_FILE}" + if grep -q -E "^dtparam=([^,]*,)*audio=(on|true|yes|1).*" "${configFile}" ; then + local configFile_backup="${configFile}.backup.audio_on_$(date +%d.%m.%y_%H.%M.%S)" + log " Backup ${configFile} --> ${configFile_backup}" + sudo cp "${configFile}" "${configFile_backup}" + sudo sed -i "s/^\(dtparam=\([^,]*,\)*\)audio=\(on\|true\|yes\|1\)\(.*\)/\1audio=off\4/g" "${configFile}" else - log " On board audio seems to be off already. Not touching ${RPI_BOOT_CONFIG_FILE}" + log " On board audio seems to be off already. Not touching ${configFile}" fi fi } diff --git a/installation/routines/setup_autohotspot.sh b/installation/routines/setup_autohotspot.sh index a083b3fcf..47b72757a 100644 --- a/installation/routines/setup_autohotspot.sh +++ b/installation/routines/setup_autohotspot.sh @@ -1,119 +1,46 @@ #!/usr/bin/env bash -# inspired by -# https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection - - -AUTOHOTSPOT_HOSTAPD_CONF_FILE="/etc/hostapd/hostapd.conf" -AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE="/etc/default/hostapd" -AUTOHOTSPOT_DNSMASQ_CONF_FILE="/etc/dnsmasq.conf" -AUTOHOTSPOT_DHCPD_CONF_FILE="/etc/dhcpcd.conf" - +AUTOHOTSPOT_INTERFACES_CONF_FILE="/etc/network/interfaces" AUTOHOTSPOT_TARGET_PATH="/usr/bin/autohotspot" +AUTOHOTSPOT_SERVICE="autohotspot.service" +AUTOHOTSPOT_SERVICE_PATH="${SYSTEMD_PATH}/${AUTOHOTSPOT_SERVICE}" +AUTOHOTSPOT_TIMER="autohotspot.timer" +AUTOHOTSPOT_TIMER_PATH="${SYSTEMD_PATH}/${AUTOHOTSPOT_TIMER}" _get_interface() { # interfaces may vary WIFI_INTERFACE=$(iw dev | grep "Interface"| awk '{ print $2 }') - WIFI_REGION=$(iw reg get | grep country | head -n 1 | awk '{ print $2}' | cut -d: -f1) # fix for CI runs on docker if [ "${CI_RUNNING}" == "true" ]; then if [ -z "${WIFI_INTERFACE}" ]; then WIFI_INTERFACE="CI TEST INTERFACE" fi - if [ -z "${WIFI_REGION}" ]; then - WIFI_REGION="CI TEST REGION" - fi fi } -_install_packages() { - sudo apt-get -y install hostapd dnsmasq iw - - # disable services. We want to start them manually - sudo systemctl unmask hostapd - sudo systemctl disable hostapd - sudo systemctl disable dnsmasq -} - -_configure_hostapd() { - local HOSTAPD_CUSTOM_FILE="${INSTALLATION_PATH}"/resources/autohotspot/hostapd.conf - - sed -i "s/WIFI_INTERFACE/${WIFI_INTERFACE}/g" "${HOSTAPD_CUSTOM_FILE}" - sed -i "s/AUTOHOTSPOT_PASSWORD/${AUTOHOTSPOT_PASSWORD}/g" "${HOSTAPD_CUSTOM_FILE}" - sed -i "s/WIFI_REGION/${WIFI_REGION}/g" "${HOSTAPD_CUSTOM_FILE}" - sudo cp "${HOSTAPD_CUSTOM_FILE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" - - sudo sed -i "s@^#DAEMON_CONF=.*@DAEMON_CONF=\"${AUTOHOTSPOT_HOSTAPD_CONF_FILE}\"@g" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" -} - -_configure_dnsmasq() { - sudo tee -a "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" <<-EOF -#AutoHotspot Config -#stop DNSmasq from using resolv.conf -no-resolv -#Interface to use -interface=${WIFI_INTERFACE} -bind-interfaces -dhcp-range=10.0.0.50,10.0.0.150,12h -EOF -} - -_other_configuration() { - sudo mv /etc/network/interfaces /etc/network/interfaces.bak - sudo touch /etc/network/interfaces - echo nohook wpa_supplicant | sudo tee -a "${AUTOHOTSPOT_DHCPD_CONF_FILE}" -} - -_install_service_and_timer() { - sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.service /etc/systemd/system/autohotspot.service - sudo systemctl enable autohotspot.service - local cron_autohotspot_file="/etc/cron.d/autohotspot" - sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.timer "${cron_autohotspot_file}" - sudo sed -i "s|%%USER%%|${CURRENT_USER}|g" "${cron_autohotspot_file}" +_get_last_ip_segment() { + local ip="$1" + echo $ip | cut -d'.' -f1-3 } -_install_autohotspot_script() { - sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot "${AUTOHOTSPOT_TARGET_PATH}" - sudo chmod +x "${AUTOHOTSPOT_TARGET_PATH}" -} - - -_autohotspot_check() { - print_verify_installation - - verify_apt_packages hostapd dnsmasq iw - - verify_service_enablement hostapd.service disabled - verify_service_enablement dnsmasq.service disabled - verify_service_enablement autohotspot.service enabled - - verify_files_exists "/etc/cron.d/autohotspot" - verify_files_exists "${AUTOHOTSPOT_TARGET_PATH}" - - verify_file_contains_string "${WIFI_INTERFACE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" - verify_file_contains_string "${AUTOHOTSPOT_PASSWORD}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" - verify_file_contains_string "${WIFI_REGION}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" - verify_file_contains_string "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" - - verify_file_contains_string "${WIFI_INTERFACE}" "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" - verify_file_contains_string "nohook wpa_supplicant" "${AUTOHOTSPOT_DHCPD_CONF_FILE}" -} - -_run_setup_autohotspot() { - _install_packages - _get_interface - _configure_hostapd - _configure_dnsmasq - _other_configuration - _install_autohotspot_script - _install_service_and_timer - _autohotspot_check -} setup_autohotspot() { if [ "$ENABLE_AUTOHOTSPOT" == true ] ; then - run_with_log_frame _run_setup_autohotspot "Install AutoHotspot" + local installed=false + if [[ $(is_dhcpcd_enabled) == true || "${CI_RUNNING}" == "true" ]]; then + run_with_log_frame _run_setup_autohotspot_dhcpcd "Install AutoHotspot" + installed=true + fi + + if [[ $(is_NetworkManager_enabled) == true || "${CI_RUNNING}" == "true" ]]; then + run_with_log_frame _run_setup_autohotspot_NetworkManager "Install AutoHotspot" + installed=true + fi + + if [[ "$installed" != true ]]; then + exit_on_error "ERROR: No network service available" + fi fi } diff --git a/installation/routines/setup_autohotspot_NetworkManager.sh b/installation/routines/setup_autohotspot_NetworkManager.sh new file mode 100644 index 000000000..66e777f1b --- /dev/null +++ b/installation/routines/setup_autohotspot_NetworkManager.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +AUTOHOTSPOT_NETWORKMANAGER_RESOURCES_PATH="${INSTALLATION_PATH}/resources/autohotspot/NetworkManager" +AUTOHOTSPOT_NETWORKMANAGER_CONNECTIONS_PATH="/etc/NetworkManager/system-connections" + +_install_packages_NetworkManager() { + sudo apt-get -y install iw +} + +_install_autohotspot_NetworkManager() { + # configure interface conf + config_file_backup "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + sudo rm "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + sudo touch "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + + # create service to trigger hotspot + local ip_without_last_segment=$(_get_last_ip_segment $AUTOHOTSPOT_IP) + sudo cp "${AUTOHOTSPOT_NETWORKMANAGER_RESOURCES_PATH}"/autohotspot "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%WIFI_INTERFACE%%|${WIFI_INTERFACE}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_PROFILE%%|${AUTOHOTSPOT_PROFILE}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_SSID%%|${AUTOHOTSPOT_SSID}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_PASSWORD%%|${AUTOHOTSPOT_PASSWORD}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_IP%%|${AUTOHOTSPOT_IP}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%IP_WITHOUT_LAST_SEGMENT%%|${ip_without_last_segment}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_TIMER_NAME%%|${AUTOHOTSPOT_TIMER}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo chmod +x "${AUTOHOTSPOT_TARGET_PATH}" + + sudo cp "${AUTOHOTSPOT_NETWORKMANAGER_RESOURCES_PATH}"/autohotspot.service "${AUTOHOTSPOT_SERVICE_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_SCRIPT%%|${AUTOHOTSPOT_TARGET_PATH}|g" "${AUTOHOTSPOT_SERVICE_PATH}" + + sudo cp "${AUTOHOTSPOT_NETWORKMANAGER_RESOURCES_PATH}"/autohotspot.timer "${AUTOHOTSPOT_TIMER_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_SERVICE%%|${AUTOHOTSPOT_SERVICE}|g" "${AUTOHOTSPOT_TIMER_PATH}" + + + sudo systemctl unmask "${AUTOHOTSPOT_SERVICE}" + sudo systemctl unmask "${AUTOHOTSPOT_TIMER}" + sudo systemctl disable "${AUTOHOTSPOT_SERVICE}" + sudo systemctl enable "${AUTOHOTSPOT_TIMER}" +} + +_uninstall_autohotspot_NetworkManager() { + # clear autohotspot configurations made from past installation + + # stop services and clear services + if systemctl list-unit-files "${AUTOHOTSPOT_SERVICE}" >/dev/null 2>&1 ; then + sudo systemctl stop "${AUTOHOTSPOT_TIMER}" + sudo systemctl disable "${AUTOHOTSPOT_TIMER}" + sudo systemctl stop "${AUTOHOTSPOT_SERVICE}" + sudo systemctl disable "${AUTOHOTSPOT_SERVICE}" + sudo rm "${AUTOHOTSPOT_SERVICE_PATH}" + sudo rm "${AUTOHOTSPOT_TIMER_PATH}" + fi + + if [ -f "${AUTOHOTSPOT_TARGET_PATH}" ]; then + sudo rm "${AUTOHOTSPOT_TARGET_PATH}" + fi + + sudo rm -f "${AUTOHOTSPOT_NETWORKMANAGER_CONNECTIONS_PATH}/${AUTOHOTSPOT_PROFILE}*" + + # remove config files + config_file_revert "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" +} + +_autohotspot_check_NetworkManager() { + print_verify_installation + + verify_apt_packages iw + + verify_service_enablement "${AUTOHOTSPOT_SERVICE}" disabled + verify_service_enablement "${AUTOHOTSPOT_TIMER}" enabled + + verify_files_exists "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + + local ip_without_last_segment=$(_get_last_ip_segment $AUTOHOTSPOT_IP) + verify_files_exists "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "wdev0='${WIFI_INTERFACE}'" "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "ap_profile_name='${AUTOHOTSPOT_PROFILE}'" "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "ap_ssid='${AUTOHOTSPOT_SSID}'" "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "ap_pw='${AUTOHOTSPOT_PASSWORD}'" "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "ap_ip='${AUTOHOTSPOT_IP}" "${AUTOHOTSPOT_TARGET_PATH}" #intentional "open end" + verify_file_contains_string "ap_gate='${ip_without_last_segment}" "${AUTOHOTSPOT_TARGET_PATH}" #intentional "open end" + verify_file_contains_string "timer_service_name='${AUTOHOTSPOT_TIMER}'" "${AUTOHOTSPOT_TARGET_PATH}" + + verify_files_exists "${AUTOHOTSPOT_SERVICE_PATH}" + verify_file_contains_string "ExecStart=${AUTOHOTSPOT_TARGET_PATH}" "${AUTOHOTSPOT_SERVICE_PATH}" + + verify_files_exists "${AUTOHOTSPOT_TIMER_PATH}" + verify_file_contains_string "Unit=${AUTOHOTSPOT_SERVICE}" "${AUTOHOTSPOT_TIMER_PATH}" +} + +_run_setup_autohotspot_NetworkManager() { + log "Install AutoHotspot NetworkManager" + _install_packages_NetworkManager + _get_interface + _uninstall_autohotspot_NetworkManager + _install_autohotspot_NetworkManager + _autohotspot_check_NetworkManager +} diff --git a/installation/routines/setup_autohotspot_dhcpcd.sh b/installation/routines/setup_autohotspot_dhcpcd.sh new file mode 100644 index 000000000..9ca92016c --- /dev/null +++ b/installation/routines/setup_autohotspot_dhcpcd.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash + +# inspired by +# https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection + +AUTOHOTSPOT_HOSTAPD_CONF_FILE="/etc/hostapd/hostapd.conf" +AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE="/etc/default/hostapd" +AUTOHOTSPOT_DNSMASQ_CONF_FILE="/etc/dnsmasq.conf" +AUTOHOTSPOT_DHCPCD_CONF_FILE="/etc/dhcpcd.conf" +AUTOHOTSPOT_DHCPCD_CONF_NOHOOK_WPA_SUPPLICANT="nohook wpa_supplicant" + +AUTOHOTSPOT_SERVICE_DAEMON="autohotspot-daemon.service" +AUTOHOTSPOT_SERVICE_DAEMON_PATH="${SYSTEMD_PATH}/${AUTOHOTSPOT_SERVICE_DAEMON}" + +AUTOHOTSPOT_DHCPCD_RESOURCES_PATH="${INSTALLATION_PATH}/resources/autohotspot/dhcpcd" + +_install_packages_dhcpcd() { + sudo apt-get -y install hostapd dnsmasq iw + + # disable services. We want to start them manually + sudo systemctl unmask hostapd + sudo systemctl disable hostapd + sudo systemctl stop hostapd + sudo systemctl unmask dnsmasq + sudo systemctl disable dnsmasq + sudo systemctl stop dnsmasq +} + +_install_autohotspot_dhcpcd() { + # configure interface conf + config_file_backup "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + sudo rm "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + sudo touch "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + + + # configure DNS + config_file_backup "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + + local ip_without_last_segment=$(_get_last_ip_segment $AUTOHOTSPOT_IP) + sudo cp "${AUTOHOTSPOT_DHCPCD_RESOURCES_PATH}"/dnsmasq.conf "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + sudo sed -i "s|%%WIFI_INTERFACE%%|${WIFI_INTERFACE}|g" "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + sudo sed -i "s|%%IP_WITHOUT_LAST_SEGMENT%%|${ip_without_last_segment}|g" "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + + + # configure hostapd conf + config_file_backup "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + + sudo cp "${AUTOHOTSPOT_DHCPCD_RESOURCES_PATH}"/hostapd.conf "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + sudo sed -i "s|%%WIFI_INTERFACE%%|${WIFI_INTERFACE}|g" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + sudo sed -i "s|%%AUTOHOTSPOT_SSID%%|${AUTOHOTSPOT_SSID}|g" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + sudo sed -i "s|%%AUTOHOTSPOT_PASSWORD%%|${AUTOHOTSPOT_PASSWORD}|g" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + sudo sed -i "s|%%AUTOHOTSPOT_COUNTRYCODE%%|${AUTOHOTSPOT_COUNTRYCODE}|g" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + + + # configure hostapd daemon + config_file_backup "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + + sudo cp "${AUTOHOTSPOT_DHCPCD_RESOURCES_PATH}"/hostapd "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + sudo sed -i "s|%%HOSTAPD_CONF%%|${AUTOHOTSPOT_HOSTAPD_CONF_FILE}|g" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + + + # configure dhcpcd conf + config_file_backup "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + if [ ! -f "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" ]; then + sudo touch "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + sudo chown root:netdev "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + sudo chmod 664 "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + fi + + if [[ ! $(grep -w "${AUTOHOTSPOT_DHCPCD_CONF_NOHOOK_WPA_SUPPLICANT}" ${AUTOHOTSPOT_DHCPCD_CONF_FILE}) ]]; then + sudo bash -c "echo ${AUTOHOTSPOT_DHCPCD_CONF_NOHOOK_WPA_SUPPLICANT} >> ${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + fi + + # create service to trigger hotspot + sudo cp "${AUTOHOTSPOT_DHCPCD_RESOURCES_PATH}"/autohotspot "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%WIFI_INTERFACE%%|${WIFI_INTERFACE}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_IP%%|${AUTOHOTSPOT_IP}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_SERVICE_DAEMON%%|${AUTOHOTSPOT_SERVICE_DAEMON}|g" "${AUTOHOTSPOT_TARGET_PATH}" + sudo chmod +x "${AUTOHOTSPOT_TARGET_PATH}" + + sudo cp "${AUTOHOTSPOT_DHCPCD_RESOURCES_PATH}"/autohotspot-daemon.service "${AUTOHOTSPOT_SERVICE_DAEMON_PATH}" + sudo sed -i "s|%%WIFI_INTERFACE%%|${WIFI_INTERFACE}|g" "${AUTOHOTSPOT_SERVICE_DAEMON_PATH}" + + sudo cp "${AUTOHOTSPOT_DHCPCD_RESOURCES_PATH}"/autohotspot.service "${AUTOHOTSPOT_SERVICE_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_SCRIPT%%|${AUTOHOTSPOT_TARGET_PATH}|g" "${AUTOHOTSPOT_SERVICE_PATH}" + + sudo cp "${AUTOHOTSPOT_DHCPCD_RESOURCES_PATH}"/autohotspot.timer "${AUTOHOTSPOT_TIMER_PATH}" + sudo sed -i "s|%%AUTOHOTSPOT_SERVICE%%|${AUTOHOTSPOT_SERVICE}|g" "${AUTOHOTSPOT_TIMER_PATH}" + + sudo systemctl enable "${AUTOHOTSPOT_SERVICE_DAEMON}" + sudo systemctl disable "${AUTOHOTSPOT_SERVICE}" + sudo systemctl enable "${AUTOHOTSPOT_TIMER}" +} + + +_uninstall_autohotspot_dhcpcd() { + # clear autohotspot configurations made from past installation + + # remove old crontab entries from previous versions + local cron_autohotspot_file="/etc/cron.d/autohotspot" + if [ -f "${cron_autohotspot_file}" ]; then + sudo rm -f "${cron_autohotspot_file}" + fi + + # stop and clear services + if systemctl list-unit-files "${AUTOHOTSPOT_SERVICE}" >/dev/null 2>&1 ; then + sudo systemctl stop hostapd + sudo systemctl stop dnsmasq + sudo systemctl stop "${AUTOHOTSPOT_TIMER}" + sudo systemctl disable "${AUTOHOTSPOT_TIMER}" + sudo systemctl stop "${AUTOHOTSPOT_SERVICE}" + sudo systemctl disable "${AUTOHOTSPOT_SERVICE}" + sudo systemctl disable "${AUTOHOTSPOT_SERVICE_DAEMON}" + sudo rm "${AUTOHOTSPOT_SERVICE_PATH}" + sudo rm "${AUTOHOTSPOT_TIMER_PATH}" + sudo rm "${AUTOHOTSPOT_SERVICE_DAEMON_PATH}" + fi + + if [ -f "${AUTOHOTSPOT_TARGET_PATH}" ]; then + sudo rm "${AUTOHOTSPOT_TARGET_PATH}" + fi + + # remove config files + config_file_revert "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + config_file_revert "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + config_file_revert "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + config_file_revert "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + config_file_revert "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" +} + + +_autohotspot_check_dhcpcd() { + print_verify_installation + + verify_apt_packages hostapd dnsmasq iw + + verify_service_enablement hostapd.service disabled + verify_service_enablement dnsmasq.service disabled + verify_service_enablement "${AUTOHOTSPOT_SERVICE_DAEMON}" enabled + verify_service_enablement "${AUTOHOTSPOT_SERVICE}" disabled + verify_service_enablement "${AUTOHOTSPOT_TIMER}" enabled + + verify_files_exists "${AUTOHOTSPOT_INTERFACES_CONF_FILE}" + + local ip_without_last_segment=$(_get_last_ip_segment $AUTOHOTSPOT_IP) + verify_files_exists "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + verify_file_contains_string "${WIFI_INTERFACE}" "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + verify_file_contains_string "dhcp-range=${ip_without_last_segment}" "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + + verify_files_exists "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "interface=${WIFI_INTERFACE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "ssid=${AUTOHOTSPOT_SSID}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "wpa_passphrase=${AUTOHOTSPOT_PASSWORD}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "country_code=${AUTOHOTSPOT_COUNTRYCODE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + + verify_files_exists "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + verify_file_contains_string "DAEMON_CONF=\"${AUTOHOTSPOT_HOSTAPD_CONF_FILE}\"" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + + verify_files_exists "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + verify_file_contains_string "${AUTOHOTSPOT_DHCPCD_CONF_NOHOOK_WPA_SUPPLICANT}" "${AUTOHOTSPOT_DHCPCD_CONF_FILE}" + + verify_files_exists "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "wifidev=\"${WIFI_INTERFACE}\"" "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "hotspot_ip=${AUTOHOTSPOT_IP}" "${AUTOHOTSPOT_TARGET_PATH}" + verify_file_contains_string "daemon_service=\"${AUTOHOTSPOT_SERVICE_DAEMON}\"" "${AUTOHOTSPOT_TARGET_PATH}" + + + verify_files_exists "${AUTOHOTSPOT_SERVICE_DAEMON_PATH}" + verify_file_contains_string "\-i \"${WIFI_INTERFACE}\"" "${AUTOHOTSPOT_SERVICE_DAEMON_PATH}" + + verify_files_exists "${AUTOHOTSPOT_SERVICE_PATH}" + verify_file_contains_string "ExecStart=${AUTOHOTSPOT_TARGET_PATH}" "${AUTOHOTSPOT_SERVICE_PATH}" + + verify_files_exists "${AUTOHOTSPOT_TIMER_PATH}" + verify_file_contains_string "Unit=${AUTOHOTSPOT_SERVICE}" "${AUTOHOTSPOT_TIMER_PATH}" +} + +_run_setup_autohotspot_dhcpcd() { + log "Install AutoHotspot dhcpcd" + _install_packages_dhcpcd + _get_interface + _uninstall_autohotspot_dhcpcd + _install_autohotspot_dhcpcd + _autohotspot_check_dhcpcd +} diff --git a/resources/autohotspot/NetworkManager/autohotspot b/resources/autohotspot/NetworkManager/autohotspot new file mode 100644 index 000000000..992190376 --- /dev/null +++ b/resources/autohotspot/NetworkManager/autohotspot @@ -0,0 +1,353 @@ +#!/bin/bash +#version 0.8 +#date 12 Dec 2023 +#Copyright Graeme Richards - RaspberryConnect.com +#Released under the GPL3 Licence (https://www.gnu.org/licenses/gpl-3.0.en.html) + +#Script to automatically switch to an Access Point when no Wifi connection is available +#Developed on a Raspberry Pi PiOS Bookworm for use with Network Manager + +#Additions where made for the Phoniebox project +#https://github.com/MiczFlor/RPi-Jukebox-RFID + +#Device Names +wdev0='%%WIFI_INTERFACE%%' #wifi device that AP will work on + +#AP setup +ap_profile_name='%%AUTOHOTSPOT_PROFILE%%' +ap_ssid='%%AUTOHOTSPOT_SSID%%' +ap_pw='%%AUTOHOTSPOT_PASSWORD%%' +ap_ip='%%AUTOHOTSPOT_IP%%/24' +ap_gate='%%IP_WITHOUT_LAST_SEGMENT%%.254' + +timer_service_name='%%AUTOHOTSPOT_TIMER_NAME%%' + +#If wifi is disabled in Network Manager, then enable it automatically. +#if set to 'false' then wifi will stay off and no AccessPoint will be generated or Network Connection will be available. +re_enable_wifi=false + +#If true, check if timer is active. Will have been disabled if arg -a used. +re_enable_timer=false + +#************************************* +#*****No user editable lines below**** + +NO_SSID='NoSSid' + +profiles=() #Currently Available Profiles +active="" #The active connection +active_ap=false #is the active profile an AP y/n +nw_profile=() #saved NW Profiles +ap_profile=() #The saved AP profiles +ssidChk=("$NO_SSID") +force_hotspot=false + +#Function is NM installed and running +check_prerequisite() { + if systemctl -all list-unit-files NetworkManager.service | grep "could not be found" >/dev/null 2>&1 ;then + if systemctl -all list-unit-files dhcpcd.service | grep "(running)" >/dev/null 2>&1 ;then + echo "This script is not compatible with the network setup." + echo "Please use the dhcpcd version" + else + echo "Network Manager is not managing the Wifi on this device" + echo "Unable to continue." + fi + exit 1 + else + local isnm="$( systemctl status NetworkManager | grep '(running)' )" + if echo "$isnm" | grep -v "(running)" ;then >/dev/null 2>&1; #NM not running + echo "Network Manager is required but is not the active system network service" + echo "Unable to continue." + exit 1 + fi + fi +} + +#Function get all wifi profiles +saved_profiles() +{ + ap_profile=() + nw_profile=() + local n="$(nmcli -t -f TYPE,NAME,AUTOCONNECT-PRIORITY con show)" #Capture Output + n="$(awk 1 ORS=':' <(echo "$n"))" #Replaces LF with : Delimeter + local profiles=() + readarray -d ':' -t profiles < <(printf "%s" "$n") #Change to array output + if [ ! -z "$profiles" ]; then + for (( c=0; c<=${#profiles[@]}; c+=3 )) #array of profiles + do + if [ ! -z "${profiles[$c+1]}" ] ; then + local conn="$(nmcli con show "${profiles[$c+1]}" | grep 'wireless.mode')" #show mode infurstructure, AP + local mode=() + readarray -d ':' -t mode < <(printf "%s" "$conn") + local mode2="$(echo "${mode[1]}" | sed 's/[[:blank:]]//g')" + if [ "$mode2" = "ap" ]; then + ap_profile+=("${profiles[$c+1]}") + echo "AP Profile: ${profiles[$c+1]}" + elif [ "$mode2" = "infrastructure" ]; then + nw_profile+=("${profiles[$c+1]}") + echo "NW Profile: ${profiles[$c+1]}" + fi + fi + done + fi +} + +#Function what is the current active wifi +active_wifi() +{ + local act="$(nmcli -t -f TYPE,NAME,DEVICE con show --active | grep "$wdev0")" #List of active devices + act="$(awk 1 ORS=':' <(echo "$act"))" #Replaces LF with : Delimeter + local active_name=() + readarray -d ':' -t active_name < <(printf "%s" "$act") #Change to array output + if [ ! -z "$active_name" ]; then + active="${active_name[1]}" + else + active="" + fi +} + +#Function is the current Connection an AP +is_active_ap() +{ + active_ap=false + if [ ! -z "$active" ] ; then + for i in "${ap_profile[@]}" + do + if [[ $i == "$active" ]]; then + active_ap=true + break + fi + done + fi +} + +#Function IW SSID scan +nearby_ssids_iw() +{ + if [ ${#nw_profile[@]} -eq 0 ]; then #only scan if NW profiles exists# + return + fi + + #Check to see what SSID's and MAC addresses are in range + echo "SSID availability check" + local i=0; j=0 + while [ $i -eq 0 ] + do + local scanreply=$(iw dev "$wdev0" scan ap-force 2>&1) + local ssidreply=$(echo "$scanreply" | egrep "^BSS|SSID:") + if [ $j -ge 5 ]; then + ssidreply="" + i=1 + elif [ -z "$ssidreply" ] ; then + echo "Error scan SSID's, try $j: $scanreply" + j=$((j + 1)) + sleep 2 + else + #success + i=1 + fi + done + + ssidChk=() + for profile in "${nw_profile[@]}" + do + echo "Assessing profile: ${profile}" + local idssid=$(nmcli -t con show "${profile}" | grep "wireless.ssid") + if (echo "$ssidreply" | grep -F -- "${idssid:21}" ) >/dev/null 2>&1 + then + echo "Valid SSID detected, assessing Wifi status" + ssidChk+="${profile}" + fi + done + + if [ "${#ssidChk[@]}" -eq 0 ]; then + echo "No Valid SSID detected" + ssidChk+="$NO_SSID" + fi +} + +check_device() +{ + echo "Device availability check" + local j=0 + while [ true ] #wait for wifi if busy, usb wifi is slower. + do + if [ $j -ge 5 ]; then + echo "No wifi device '$wdev0' connected" + exit 1 + elif (nmcli device show "$wdev0" 2>&1 >/dev/null) ; then + echo "Wifi device '$wdev0' available" + if (rfkill list wifi -rno HARD,SOFT | grep -i "unblocked.*unblocked") >/dev/null 2>&1 ; then + return + else + if [[ $re_enable_wifi = true ]] ; then + nmcli radio wifi on + echo "Wifi has been re-activated" + sleep 10 #delay to allow wifi to initialise + return + else + echo "Wifi is deactivated" + exit 0 + fi + fi + else + j=$((j + 1)) + sleep 2 + fi + done +} + +#Activate AP profile +start_ap() +{ + local ex=0 + for i in "${ap_profile[@]}" + do + if [[ $i == "$ap_profile_name" ]]; then + ex=1 #known saved AP profile is available + break + fi + done + if [ $ex -eq 0 ];then + ap_new_profile #if known AP profile not found, create it + fi + nmcli con up "$ap_profile_name" >/dev/null 2>&1 + sleep 3 #give time for ap to be setup + active_wifi + is_active_ap + if [ "$active_ap" = true ]; then + echo "Access Point started" + local curip="$(nmcli -t con show $active | grep IP4.ADDRESS)" + readarray -d ':' -t ipid < <(printf "%s" "$curip") + local showip="$(echo "${ipid[1]}" | sed 's/[[:blank:]]//g')" + if [ ! -z $showip ]; then + echo "AP on IP Address ${showip::-3}" + fi + else + echo "AP failed to be created." + fi +} + +#Activate NW profile +start_nw() +{ + if [ "$active_ap" = true ]; then + echo "The active profile is $active. Shutting down" + nmcli con down "$active" >/dev/null 2>&1 + fi + + local active_nw="" + for i in "${nw_profile[@]}" + do + echo "Checking: $i" + con="$(nmcli con up $i)" + if ( echo "$con" | grep 'Connection successfully activated' ) >/dev/null 2>&1; then + echo "Connection was good" + active_wifi + active_nw="$active" + elif ( echo "$con" | grep 'Connection activation failed' ) >/dev/null 2>&1; then + echo "Unable to make a connection. Check the password is ok for the ssid ${nw_profile[$c]}" + active_nw="" + else + echo "Unable to confirm the connection status" + active_nw="" + fi + if [ ! -z "$active_nw" ] ;then + echo "A valid connection has been made with $i" + break + fi + done + + if [ -z "$active_nw" ] ;then + echo "A network connection has not been made with any known ssid. Activating access point" + start_ap + fi +} + +#Function Create AP profile +ap_new_profile() +{ + echo "Create a AP profile ${ap_profile_name}" + nmcli device wifi hotspot ifname $wdev0 con-name "$ap_profile_name" ssid "$ap_ssid" band bg password "$ap_pw" >/dev/null 2>&1 + nmcli con mod "$ap_profile_name" ipv4.method shared ipv4.addr "$ap_ip" ipv4.gateway "$ap_gate" >/dev/null 2>&1 + ap_profile+=("$ap_profile_name") +} + +#Main +check_prerequisite +check_device + +while getopts "aht" opt; do + case $opt in + a ) + force_hotspot=true + ;; + t ) + re_enable_timer=true + ;; + h ) + sc="$(basename $0)" + echo -e "\nby default the $sc script will setup a connection to a WiFi network where a profile exists" + echo "otherwise an Access Point called $ap_ssid will be created. Using ip address $ap_ip" + echo "The local wifi signals will be check every 2 minutes. If a known SSID comes into range" + echo "the Access Point will be shutdown and a connection to the Wifi network will be made." + echo "using sudo $sc -a will activate the Access Point regardless of any existing WiFi profile" + echo "and stop the timed checks. Use sudo $sc to return to normal use." + exit + ;; + * ) + echo "option not valid" + exit + ;; + esac +done + +saved_profiles #get list of saved profile +active_wifi +is_active_ap +echo -e "The active profile is $active\n" + +if [ "$force_hotspot" = true ]; then + if [ ! "$active_ap" = true ]; then + systemctl stop "$timer_service_name" + start_ap + elif [ ! "$active" = "$ap_profile_name" ]; then #Other AP is running, swap to this one + nmcli con down "$active" + start_ap + else + echo "Access Point $active is already running" + fi +else + if [ ! -z "$active" ]; then #Active Profile Yes + if [ "$active_ap" = true ]; then #Yes it's an AP profile + nearby_ssids_iw #scan for nearby SSID's + if [ "${ssidChk[0]}" != "$NO_SSID" ]; then #known ssid in range + start_nw + elif [ ! "$active" = "$ap_profile_name" ]; then #Other AP is running, swap to this one + nmcli con down "$active" + start_ap + fi + fi + else #no active profile + nearby_ssids_iw #scan for nearby SSID's + if [ "${ssidChk[0]}" != "$NO_SSID" ]; then #known ssid in range + start_nw + else + start_ap + fi + fi + + if [[ $re_enable_timer = true ]] ; then + #check if timer is active. Will have been disabled if arg -a used. + tup="$(systemctl list-timers | grep '${timer_service_name}')" + if [ -z "$tup" ];then + systemctl start "$timer_service_name" + echo "Reactivated timer" + fi + fi +fi + +active_wifi +is_active_ap +echo -e "\nThe Wifi profile in use is: $active" +echo -e "Is this a local access point? $active_ap\n" diff --git a/resources/autohotspot/NetworkManager/autohotspot.service b/resources/autohotspot/NetworkManager/autohotspot.service new file mode 100644 index 000000000..ff808cd9e --- /dev/null +++ b/resources/autohotspot/NetworkManager/autohotspot.service @@ -0,0 +1,11 @@ +[Unit] +Description=Automatically generates an wifi hotspot when a valid SSID is not in range +After=multi-user.target +Requires=network-online.target + +[Service] +Type=simple +ExecStart=%%AUTOHOTSPOT_SCRIPT%% + +[Install] +WantedBy=multi-user.target diff --git a/resources/autohotspot/NetworkManager/autohotspot.timer b/resources/autohotspot/NetworkManager/autohotspot.timer new file mode 100644 index 000000000..dc7427170 --- /dev/null +++ b/resources/autohotspot/NetworkManager/autohotspot.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Timer to run the %%AUTOHOTSPOT_SERVICE%% every 2 mins + +[Timer] +OnBootSec=0min +OnCalendar=*:0/2 +Unit=%%AUTOHOTSPOT_SERVICE%% + +[Install] +WantedBy=timers.target diff --git a/resources/autohotspot/autohotspot b/resources/autohotspot/autohotspot deleted file mode 100644 index 92cb7eebc..000000000 --- a/resources/autohotspot/autohotspot +++ /dev/null @@ -1,193 +0,0 @@ -#!/bin/bash -#version 0.961-N/HS - -#You may share this script on the condition a reference to RaspberryConnect.com -#must be included in copies or derivatives of this script. - -#A script to switch between a wifi network and a non internet routed Hotspot -#Works at startup or with a seperate timer or manually without a reboot -#Other setup required find out more at -#http://www.raspberryconnect.com - -wifidev="wlan0" #device name to use. Default is wlan0. -#use the command: iw dev ,to see wifi interface name - -IFSdef=$IFS -cnt=0 -#These four lines capture the wifi networks the RPi is setup to use -wpassid=$(awk '/ssid="/{ print $0 }' /etc/wpa_supplicant/wpa_supplicant.conf | awk -F'ssid=' '{ print $2 }' | sed 's/\r//g'| awk 'BEGIN{ORS=","} {print}' | sed 's/\"/''/g' | sed 's/,$//') -IFS="," -ssids=($wpassid) -IFS=$IFSdef #reset back to defaults - - -#Note:If you only want to check for certain SSIDs -#Remove the # in in front of ssids=('mySSID1'.... below and put a # infront of all four lines above -# separated by a space, eg ('mySSID1' 'mySSID2') -#ssids=('mySSID1' 'mySSID2' 'mySSID3') - -#Enter the Routers Mac Addresses for hidden SSIDs, seperated by spaces ie -#( '11:22:33:44:55:66' 'aa:bb:cc:dd:ee:ff' ) -mac=() - -ssidsmac=("${ssids[@]}" "${mac[@]}") #combines ssid and MAC for checking - -createAdHocNetwork() -{ - echo "Creating Hotspot" - ip link set dev "$wifidev" down - ip a add 10.0.0.5/24 brd + dev "$wifidev" - ip link set dev "$wifidev" up - dhcpcd -k "$wifidev" >/dev/null 2>&1 - systemctl start dnsmasq - systemctl start hostapd -} - -KillHotspot() -{ - echo "Shutting Down Hotspot" - ip link set dev "$wifidev" down - systemctl stop hostapd - systemctl stop dnsmasq - ip addr flush dev "$wifidev" - ip link set dev "$wifidev" up - dhcpcd -n "$wifidev" >/dev/null 2>&1 -} - -ChkWifiUp() -{ - echo "Checking WiFi connection ok" - sleep 20 #give time for connection to be completed to router - if ! wpa_cli -i "$wifidev" status | grep 'ip_address' >/dev/null 2>&1 - then #Failed to connect to wifi (check your wifi settings, password etc) - echo 'Wifi failed to connect, falling back to Hotspot.' - wpa_cli terminate "$wifidev" >/dev/null 2>&1 - createAdHocNetwork - fi -} - - -chksys() -{ - #After some system updates hostapd gets masked using Raspbian Buster, and above. This checks and fixes - #the issue and also checks dnsmasq is ok so the hotspot can be generated. - #Check Hostapd is unmasked and disabled - if systemctl -all list-unit-files hostapd.service | grep "hostapd.service masked" >/dev/null 2>&1 ;then - systemctl unmask hostapd.service >/dev/null 2>&1 - fi - if systemctl -all list-unit-files hostapd.service | grep "hostapd.service enabled" >/dev/null 2>&1 ;then - systemctl disable hostapd.service >/dev/null 2>&1 - systemctl stop hostapd >/dev/null 2>&1 - fi - #Check dnsmasq is disabled - if systemctl -all list-unit-files dnsmasq.service | grep "dnsmasq.service masked" >/dev/null 2>&1 ;then - systemctl unmask dnsmasq >/dev/null 2>&1 - fi - if systemctl -all list-unit-files dnsmasq.service | grep "dnsmasq.service enabled" >/dev/null 2>&1 ;then - systemctl disable dnsmasq >/dev/null 2>&1 - systemctl stop dnsmasq >/dev/null 2>&1 - fi -} - - -FindSSID() -{ -#Check to see what SSID's and MAC addresses are in range -ssidChk=('NoSSid') -i=0; j=0 -until [ $i -eq 1 ] #wait for wifi if busy, usb wifi is slower. -do - ssidreply=$((iw dev "$wifidev" scan ap-force | egrep "^BSS|SSID:") 2>&1) >/dev/null 2>&1 - #echo "SSid's in range: " $ssidreply - printf '%s\n' "${ssidreply[@]}" - echo "Device Available Check try " $j - if (($j >= 10)); then #if busy 10 times goto hotspot - echo "Device busy or unavailable 10 times, going to Hotspot" - ssidreply="" - i=1 - elif echo "$ssidreply" | grep "No such device (-19)" >/dev/null 2>&1; then - echo "No Device Reported, try " $j - NoDevice - elif echo "$ssidreply" | grep "Network is down (-100)" >/dev/null 2>&1 ; then - echo "Network Not available, trying again" $j - j=$((j + 1)) - sleep 2 - elif echo "$ssidreply" | grep "Read-only file system (-30)" >/dev/null 2>&1 ; then - echo "Temporary Read only file system, trying again" - j=$((j + 1)) - sleep 2 - elif echo "$ssidreply" | grep "Invalid exchange (-52)" >/dev/null 2>&1 ; then - echo "Temporary unavailable, trying again" - j=$((j + 1)) - sleep 2 - elif echo "$ssidreply" | grep -v "resource busy (-16)" >/dev/null 2>&1 ; then - echo "Device Available, checking SSid Results" - i=1 - else #see if device not busy in 2 seconds - echo "Device unavailable checking again, try " $j - j=$((j + 1)) - sleep 2 - fi -done - -for ssid in "${ssidsmac[@]}" -do - if (echo "$ssidreply" | grep -F -- "$ssid") >/dev/null 2>&1 - then - #Valid SSid found, passing to script - echo "Valid SSID Detected, assesing Wifi status" - ssidChk=$ssid - return 0 - else - #No Network found, NoSSid issued" - echo "No SSid found, assessing WiFi status" - ssidChk='NoSSid' - fi -done -} - -NoDevice() -{ - #if no wifi device,ie usb wifi removed, activate wifi so when it is - #reconnected wifi to a router will be available - echo "No wifi device connected" - wpa_supplicant -B -i "$wifidev" -c /etc/wpa_supplicant/wpa_supplicant.conf >/dev/null 2>&1 - exit 1 -} - -chksys -FindSSID - -#Create Hotspot or connect to valid wifi networks -if [ "$ssidChk" != "NoSSid" ] -then - if systemctl status hostapd | grep "(running)" >/dev/null 2>&1 - then #hotspot running and ssid in range - KillHotspot - echo "Hotspot Deactivated, Bringing Wifi Up" - wpa_supplicant -B -i "$wifidev" -c /etc/wpa_supplicant/wpa_supplicant.conf >/dev/null 2>&1 - ChkWifiUp - elif { wpa_cli -i "$wifidev" status | grep 'ip_address'; } >/dev/null 2>&1 - then #Already connected - echo "Wifi already connected to a network" - else #ssid exists and no hotspot running connect to wifi network - echo "Connecting to the WiFi Network" - wpa_supplicant -B -i "$wifidev" -c /etc/wpa_supplicant/wpa_supplicant.conf >/dev/null 2>&1 - ChkWifiUp - fi -else #ssid or MAC address not in range - if systemctl status hostapd | grep "(running)" >/dev/null 2>&1 - then - echo "Hostspot already active" - elif { wpa_cli status | grep "$wifidev"; } >/dev/null 2>&1 - then - echo "Cleaning wifi files and Activating Hotspot" - wpa_cli terminate >/dev/null 2>&1 - ip addr flush "$wifidev" - ip link set dev "$wifidev" down - rm -r /var/run/wpa_supplicant >/dev/null 2>&1 - createAdHocNetwork - else #"No SSID, activating Hotspot" - createAdHocNetwork - fi -fi diff --git a/resources/autohotspot/autohotspot.service b/resources/autohotspot/autohotspot.service deleted file mode 100644 index 13d9101b4..000000000 --- a/resources/autohotspot/autohotspot.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Automatically generates an internet Hotspot when a valid ssid is not in range -After=multi-user.target - -[Service] -Type=oneshot -RemainAfterExit=yes -ExecStart=/usr/bin/autohotspot - -[Install] -WantedBy=multi-user.target diff --git a/resources/autohotspot/autohotspot.timer b/resources/autohotspot/autohotspot.timer deleted file mode 100644 index eb2acaebe..000000000 --- a/resources/autohotspot/autohotspot.timer +++ /dev/null @@ -1,3 +0,0 @@ -# cron timer for autohotspot -*/5 * * * * %%USER%% sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot -@reboot %%USER%% sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot diff --git a/resources/autohotspot/dhcpcd/autohotspot b/resources/autohotspot/dhcpcd/autohotspot new file mode 100644 index 000000000..a7388512e --- /dev/null +++ b/resources/autohotspot/dhcpcd/autohotspot @@ -0,0 +1,228 @@ +#!/usr/bin/env bash +#version 0.962-N/HS + +#You may share this script on the condition all references to RaspberryConnect.com +#must be included in copies or derivatives of this script. + +#A script to switch between a wifi network and a non internet routed Hotspot +#Works at startup or with a seperate timer or manually without a reboot +#Other setup required find out more at +#http://www.raspberryconnect.com + +#Additions where made for the Phoniebox project +#https://github.com/MiczFlor/RPi-Jukebox-RFID + +while getopts "a" opt; do + case $opt in + a ) + FORCE_HOTSPOT=true + ;; + * ) + echo "option not valid" + exit + ;; + esac +done + +NO_SSID='NoSSid' +ssidChk="$NO_SSID" + +wifidev="%%WIFI_INTERFACE%%" #device name to use. +#use the command: iw dev ,to see wifi interface name +hotspot_ip=%%AUTOHOTSPOT_IP%% +daemon_service="%%AUTOHOTSPOT_SERVICE_DAEMON%%" + +IFSdef=$IFS +cnt=0 +#These four lines capture the wifi networks the RPi is setup to use +wpassid=$(awk '/ssid="/{ print $0 }' /etc/wpa_supplicant/wpa_supplicant.conf | awk -F'ssid=' '{ print $2 }' | sed 's/\r//g'| awk 'BEGIN{ORS=","} {print}' | sed 's/\"/''/g' | sed 's/,$//') +IFS="," +ssids=($wpassid) +IFS=$IFSdef #reset back to defaults + + +#Note:If you only want to check for certain SSIDs +#Remove the # in in front of ssids=('mySSID1'.... below and put a # infront of all four lines above +# separated by a space, eg ('mySSID1' 'mySSID2') +#ssids=('mySSID1' 'mySSID2' 'mySSID3') + +#Enter the Routers Mac Addresses for hidden SSIDs, seperated by spaces ie +#( '11:22:33:44:55:66' 'aa:bb:cc:dd:ee:ff' ) +mac=() + +ssidsmac=("${ssids[@]}" "${mac[@]}") #combines ssid and MAC for checking + +CreateAdHocNetwork() +{ + echo "Creating Hotspot" + ip link set dev "$wifidev" down + ip a add "$hotspot_ip"/24 brd + dev "$wifidev" + ip link set dev "$wifidev" up + dhcpcd -k "$wifidev" >/dev/null 2>&1 + systemctl start dnsmasq + systemctl start hostapd +} + +KillHotspot() +{ + echo "Shutting Down Hotspot" + ip link set dev "$wifidev" down + systemctl stop hostapd + systemctl stop dnsmasq + ip addr flush dev "$wifidev" + ip link set dev "$wifidev" up + dhcpcd -n "$wifidev" >/dev/null 2>&1 +} + +CheckWifiUp() +{ + echo "Checking WiFi connection ok" + sleep 20 #give time for connection to be completed to router + if ! wpa_cli -i "$wifidev" status | grep 'ip_address' >/dev/null 2>&1 + then #Failed to connect to wifi (check your wifi settings, password etc) + echo 'Wifi failed to connect, falling back to Hotspot.' + wpa_cli terminate "$wifidev" >/dev/null 2>&1 + CreateAdHocNetwork + fi +} + +InitWPA() { + systemctl restart "$daemon_service" +} + +CheckServices() +{ + #After some system updates hostapd gets masked using Raspbian Buster, and above. This checks and fixes + #the issue and also checks dnsmasq is ok so the hotspot can be generated. + #Check Hostapd is unmasked and disabled + if (systemctl -all list-unit-files hostapd.service | grep "hostapd.service masked") >/dev/null 2>&1 ;then + systemctl unmask hostapd.service >/dev/null 2>&1 + fi + if (systemctl -all list-unit-files hostapd.service | grep "hostapd.service enabled") >/dev/null 2>&1 ;then + systemctl disable hostapd.service >/dev/null 2>&1 + systemctl stop hostapd >/dev/null 2>&1 + fi + #Check dnsmasq is disabled + if (systemctl -all list-unit-files dnsmasq.service | grep "dnsmasq.service masked") >/dev/null 2>&1 ;then + systemctl unmask dnsmasq >/dev/null 2>&1 + fi + if (systemctl -all list-unit-files dnsmasq.service | grep "dnsmasq.service enabled") >/dev/null 2>&1 ;then + systemctl disable dnsmasq >/dev/null 2>&1 + systemctl stop dnsmasq >/dev/null 2>&1 + fi +} + +CheckDevice() +{ + echo "Device availability check" + local j=0 + while [ true ] #wait for wifi if busy, usb wifi is slower. + do + + if [ $j -ge 5 ]; then + #if no wifi device,ie usb wifi removed, activate wifi so when it is + #reconnected wifi to a router will be available + echo "No wifi device '$wifidev' connected" + InitWPA + exit 1 + elif (iw dev "$wifidev" info 2>&1 >/dev/null) ; then + echo "Wifi device '$wifidev' available" + if (rfkill list wifi -rno HARD,SOFT | grep -i "unblocked.*unblocked") >/dev/null 2>&1 ; then + local wifidev_up=$(ip link show "$wifidev" up) + if [ -z "$wifidev_up" ]; then + echo "Wifi is down. Setting up" + ip link set dev "$wifidev" up + sleep 2 + fi + return + else + echo "Wifi is deactivated" + exit 0 + fi + else + j=$((j + 1)) + sleep 2 + fi + done +} + +FindSSID() +{ + if [ -n "$FORCE_HOTSPOT" ]; then return; fi + + #Check to see what SSID's and MAC addresses are in range + echo "SSID availability check" + local i=0; j=0 + while [ $i -eq 0 ] + do + scanreply=$(iw dev "$wifidev" scan ap-force 2>&1) + ssidreply=$(echo "$scanreply" | egrep "^BSS|SSID:") + if [ $j -ge 5 ]; then + ssidreply="" + i=1 + elif [ -z "$ssidreply" ] ; then + echo "Error scan SSID's, try $j: $scanreply" + j=$((j + 1)) + sleep 2 + else + #success + i=1 + fi + done + + for ssid in "${ssidsmac[@]}" + do + if (echo "$ssidreply" | grep "$ssid") >/dev/null 2>&1 + then + echo "Valid SSID detected, assessing Wifi status" + ssidChk=$ssid + break + fi + done + + if [ "$ssidChk" == "$NO_SSID" ]; then + echo "No Valid SSID detected" + fi +} + +CheckSSID() +{ + #Create Hotspot or connect to valid wifi networks + if [ "$ssidChk" != "$NO_SSID" ] + then + if systemctl status hostapd | grep "(running)" >/dev/null 2>&1 + then #hotspot running and ssid in range + KillHotspot + echo "Hotspot Deactivated, Bringing Wifi Up" + InitWPA + CheckWifiUp + elif { wpa_cli -i "$wifidev" status | grep 'ip_address'; } >/dev/null 2>&1 + then #Already connected + echo "Wifi already connected to a network" + else #ssid exists and no hotspot running connect to wifi network + echo "Connecting to the WiFi Network" + InitWPA + CheckWifiUp + fi + else #ssid or MAC address not in range + if systemctl status hostapd | grep "(running)" >/dev/null 2>&1 + then + echo "Hostspot already active" + elif { wpa_cli status | grep "$wifidev"; } >/dev/null 2>&1 + then + echo "Cleaning wifi files and Activating Hotspot" + wpa_cli terminate >/dev/null 2>&1 + ip addr flush "$wifidev" + ip link set dev "$wifidev" down + rm -r /var/run/wpa_supplicant >/dev/null 2>&1 + CreateAdHocNetwork + else #"No SSID, activating Hotspot" + CreateAdHocNetwork + fi + fi +} + +CheckServices +CheckDevice +FindSSID +CheckSSID diff --git a/resources/autohotspot/dhcpcd/autohotspot-daemon.service b/resources/autohotspot/dhcpcd/autohotspot-daemon.service new file mode 100644 index 000000000..5906a4840 --- /dev/null +++ b/resources/autohotspot/dhcpcd/autohotspot-daemon.service @@ -0,0 +1,10 @@ +[Unit] +Description=Daemon for regular network connection if no hotspot is created +After=multi-user.target + +[Service] +Type=simple +ExecStart=wpa_supplicant -i "%%WIFI_INTERFACE%%" -c /etc/wpa_supplicant/wpa_supplicant.conf + +[Install] +WantedBy=multi-user.target diff --git a/resources/autohotspot/dhcpcd/autohotspot.service b/resources/autohotspot/dhcpcd/autohotspot.service new file mode 100644 index 000000000..ff808cd9e --- /dev/null +++ b/resources/autohotspot/dhcpcd/autohotspot.service @@ -0,0 +1,11 @@ +[Unit] +Description=Automatically generates an wifi hotspot when a valid SSID is not in range +After=multi-user.target +Requires=network-online.target + +[Service] +Type=simple +ExecStart=%%AUTOHOTSPOT_SCRIPT%% + +[Install] +WantedBy=multi-user.target diff --git a/resources/autohotspot/dhcpcd/autohotspot.timer b/resources/autohotspot/dhcpcd/autohotspot.timer new file mode 100644 index 000000000..dc7427170 --- /dev/null +++ b/resources/autohotspot/dhcpcd/autohotspot.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Timer to run the %%AUTOHOTSPOT_SERVICE%% every 2 mins + +[Timer] +OnBootSec=0min +OnCalendar=*:0/2 +Unit=%%AUTOHOTSPOT_SERVICE%% + +[Install] +WantedBy=timers.target diff --git a/resources/autohotspot/dhcpcd/dnsmasq.conf b/resources/autohotspot/dhcpcd/dnsmasq.conf new file mode 100644 index 000000000..da0ba46ef --- /dev/null +++ b/resources/autohotspot/dhcpcd/dnsmasq.conf @@ -0,0 +1,7 @@ +#AutoHotspot Config +#stop DNSmasq from using resolv.conf +no-resolv +#Interface to use +interface=%%WIFI_INTERFACE%% +bind-interfaces +dhcp-range=%%IP_WITHOUT_LAST_SEGMENT%%.100,%%IP_WITHOUT_LAST_SEGMENT%%.200,12h diff --git a/resources/autohotspot/dhcpcd/hostapd b/resources/autohotspot/dhcpcd/hostapd new file mode 100644 index 000000000..92798c162 --- /dev/null +++ b/resources/autohotspot/dhcpcd/hostapd @@ -0,0 +1,20 @@ +# Defaults for hostapd initscript +# +# See /usr/share/doc/hostapd/README.Debian for information about alternative +# methods of managing hostapd. +# +# Uncomment and set DAEMON_CONF to the absolute path of a hostapd configuration +# file and hostapd will be started during system boot. An example configuration +# file can be found at /usr/share/doc/hostapd/examples/hostapd.conf.gz +# +DAEMON_CONF="%%HOSTAPD_CONF%%" + +# Additional daemon options to be appended to hostapd command:- +# -d show more debug messages (-dd for even more) +# -K include key data in debug messages +# -t include timestamps in some debug messages +# +# Note that -B (daemon mode) and -P (pidfile) options are automatically +# configured by the init.d script and must not be added to DAEMON_OPTS. +# +#DAEMON_OPTS="" diff --git a/resources/autohotspot/hostapd.conf b/resources/autohotspot/dhcpcd/hostapd.conf similarity index 66% rename from resources/autohotspot/hostapd.conf rename to resources/autohotspot/dhcpcd/hostapd.conf index 8860368a4..4edc8f045 100644 --- a/resources/autohotspot/hostapd.conf +++ b/resources/autohotspot/dhcpcd/hostapd.conf @@ -1,7 +1,7 @@ #2.4GHz setup wifi 80211 b,g,n -interface=WIFI_INTERFACE +interface=%%WIFI_INTERFACE%% driver=nl80211 -ssid=Phoniebox_Hotspot +ssid=%%AUTOHOTSPOT_SSID%% hw_mode=g channel=8 wmm_enabled=0 @@ -9,12 +9,12 @@ macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 -wpa_passphrase=AUTOHOTSPOT_PASSWORD +wpa_passphrase=%%AUTOHOTSPOT_PASSWORD%% wpa_key_mgmt=WPA-PSK wpa_pairwise=CCMP TKIP rsn_pairwise=CCMP #80211n - Change GB to your WiFi country code -country_code=WIFI_REGION +country_code=%%AUTOHOTSPOT_COUNTRYCODE%% ieee80211n=1 ieee80211d=1 diff --git a/src/cli_client/pbc.c b/src/cli_client/pbc.c index 0b5cfb762..7f39c493e 100644 --- a/src/cli_client/pbc.c +++ b/src/cli_client/pbc.c @@ -44,7 +44,7 @@ #define MAX_PARAMS 16 int g_verbose = 0; -typedef struct +typedef struct { char object [MAX_STRLEN]; char package [MAX_STRLEN]; @@ -61,44 +61,44 @@ int send_zmq_request_and_wait_response(char * request, int request_len, char * r void *context = zmq_ctx_new (); void *requester = zmq_socket (context, ZMQ_REQ); int linger = 200; - + if (g_verbose) { int major, minor, patch; zmq_version (&major, &minor, &patch); printf ("Current ØMQ version is %d.%d.%d\n", major, minor, patch); } - + zmq_setsockopt(requester,ZMQ_LINGER,&linger,sizeof(linger)); zmq_setsockopt(requester,ZMQ_RCVTIMEO,&linger,sizeof(linger)); zmq_connect (requester, address); if (g_verbose) printf("connected to: %s",address); - + zmq_ret = zmq_send (requester, request, request_len, 0); if (zmq_ret > 0) { zmq_ret = zmq_recv (requester, response, max_response_len, 0); - + if (zmq_ret > 0) { - printf ("Received %s (%d Bytes)\n", response,zmq_ret); + printf ("Received %s (%d Bytes)\n", response,zmq_ret); ret = 0; } else { - printf ("zmq_recv rturned %d \n", zmq_ret); + printf ("zmq_recv rturned %d \n", zmq_ret); } } else { - if (g_verbose) printf ("zmq_send returned %d\n", zmq_ret); + if (g_verbose) printf ("zmq_send returned %d\n", zmq_ret); } zmq_close (requester); - zmq_ctx_destroy (context); + zmq_ctx_destroy (context); return (ret); } @@ -114,7 +114,7 @@ void * connect_and_send_request(t_request * tr) if (tr->num_params > 0) { sprintf(kwargs, "\"kwargs\":{"); - + for (n = 0;n < tr->num_params;) { strcat(kwargs,tr->params[n]); @@ -129,7 +129,7 @@ void * connect_and_send_request(t_request * tr) snprintf(json_request,MAX_REQEST_STRLEN,"{\"package\": \"%s\", \"plugin\": \"%s\", \"method\": \"%s\", %s\"id\":%d}",tr->package,tr->object,tr->method,kwargs,123); json_len = strlen(json_request); - + if (g_verbose) printf("Sending Request (%ld Bytes):\n%s\n",json_len,json_request); send_zmq_request_and_wait_response(json_request,json_len,json_response,MAX_REQEST_STRLEN,tr->address); @@ -177,7 +177,7 @@ int HandleOptions(int argc,char *argv[], t_request * tr) { int c; sprintf(tr->address,"tcp://localhost:5555"); - + const struct option long_options[] = { /* These options set a flag. */ @@ -213,7 +213,7 @@ int HandleOptions(int argc,char *argv[], t_request * tr) break; case 'p': strncpy (tr->package,optarg,MAX_STRLEN); - break; + break; case 'o': strncpy (tr->object,optarg,MAX_STRLEN); break; @@ -229,7 +229,7 @@ int HandleOptions(int argc,char *argv[], t_request * tr) case 'a': strncpy (tr->address,optarg,MAX_STRLEN); break; - + default: usage(); abort (); @@ -240,7 +240,7 @@ int HandleOptions(int argc,char *argv[], t_request * tr) if (optind < argc) { while (optind < argc) - { + { check_and_map_parameters_to_json(argv[optind++], tr); } } @@ -256,6 +256,6 @@ int main(int argc,char *argv[]) HandleOptions(argc,argv,&tr); connect_and_send_request(&tr); - + return 0; } diff --git a/src/jukebox/components/hostif/linux/__init__.py b/src/jukebox/components/hostif/linux/__init__.py index 32dfded08..6a6590ad6 100644 --- a/src/jukebox/components/hostif/linux/__init__.py +++ b/src/jukebox/components/hostif/linux/__init__.py @@ -238,7 +238,7 @@ def get_autohotspot_status(): if os.path.isfile("/etc/systemd/system/autohotspot.service"): status = 'inactive' - ret = subprocess.run(['systemctl', 'is-active', 'autohotspot'], + ret = subprocess.run(['systemctl', 'is-active', 'autohotspot.timer'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False, stdin=subprocess.DEVNULL) # 0 = active, 3 = inactive @@ -260,22 +260,21 @@ def get_autohotspot_status(): def stop_autohotspot(): """Stop auto hotspot functionality - Basically disabling the cronjob and running the script one last time manually + Stopping and disabling the timer and running the service one last time manually """ if os.path.isfile("/etc/systemd/system/autohotspot.service"): - cron_job = "/etc/cron.d/autohotspot" - subprocess.run(["sudo", "sed", "-i", r"s/^\*.*/#&/", cron_job], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) - subprocess.run(['sudo', '/usr/bin/systemctl', 'stop', 'autohotspot'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) - subprocess.run(['sudo', '/usr/bin/systemctl', 'disable', 'autohotspot'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) - ret = subprocess.run(['sudo', 'autohotspot'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - check=False) - if ret.returncode != 0: - msg = f"Error 'stop_autohotspot': {ret.stdout} (Code: {ret.returncode})" - logger.error(msg) - return {'error': {'code': -1, 'message': msg}} + # Stop timer + subprocess.run(['sudo', '/usr/bin/systemctl', 'stop', 'autohotspot.timer'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) + # Prevent start after system restart + subprocess.run(['sudo', '/usr/bin/systemctl', 'disable', 'autohotspot.timer'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) + # Prevent start after system restart (should always be disabled, but make sure) + subprocess.run(['sudo', '/usr/bin/systemctl', 'disable', 'autohotspot.service'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) + + subprocess.run(['sudo', '/usr/bin/systemctl', 'start', 'autohotspot.service'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) return 'inactive' else: @@ -285,24 +284,17 @@ def stop_autohotspot(): @plugin.register() def start_autohotspot(): - """start auto hotspot functionality + """Start auto hotspot functionality - Basically enabling the cronjob and running the script one time manually + Enabling and starting the timer (timer will start the service) """ if os.path.isfile("/etc/systemd/system/autohotspot.service"): - cron_job = "/etc/cron.d/autohotspot" - subprocess.run(["sudo", "sed", "-i", "-r", r"s/(^#)(\*[0-9]*)/\*/", cron_job], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) - subprocess.run(['sudo', '/usr/bin/systemctl', 'start', 'autohotspot'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) - subprocess.run(['sudo', '/usr/bin/systemctl', 'enable', 'autohotspot'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) - ret = subprocess.run(['sudo', 'autohotspot'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - check=False) - if ret.returncode != 0: - msg = f"Error 'start_autohotspot': {ret.stdout} (Code: {ret.returncode})" - logger.error(msg) - return {'error': {'code': -1, 'message': msg}} + # Enable start after system restart + subprocess.run(['sudo', '/usr/bin/systemctl', 'enable', 'autohotspot.timer'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) + # Start timer (starts the service immediately) + subprocess.run(['sudo', '/usr/bin/systemctl', 'start', 'autohotspot.timer'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) return 'active' else: From 20389e3f3a18582bc21d2fb9b48978d8324a56b5 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sun, 4 Feb 2024 21:11:45 +0100 Subject: [PATCH 084/121] Allow default value for CoverArtCache path (#2237) --- src/jukebox/components/playermpd/__init__.py | 4 +--- .../components/playermpd/coverart_cache_manager.py | 8 ++++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index 65c6e7267..49f630224 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -157,9 +157,7 @@ def __init__(self): self.decode_2nd_swipe_option() self.mpd_client = mpd.MPDClient() - - coverart_cache_path = cfg.getn('webapp', 'coverart_cache_path') - self.coverart_cache_manager = CoverartCacheManager(os.path.expanduser(coverart_cache_path)) + self.coverart_cache_manager = CoverartCacheManager() # The timeout refer to the low-level socket time-out # If these are too short and the response is not fast enough (due to the PI being busy), diff --git a/src/jukebox/components/playermpd/coverart_cache_manager.py b/src/jukebox/components/playermpd/coverart_cache_manager.py index 7883372ba..a7ae12eef 100644 --- a/src/jukebox/components/playermpd/coverart_cache_manager.py +++ b/src/jukebox/components/playermpd/coverart_cache_manager.py @@ -1,9 +1,13 @@ import os +import jukebox.cfghandler + +cfg = jukebox.cfghandler.get_handler('jukebox') class CoverartCacheManager: - def __init__(self, cache_folder_path): - self.cache_folder_path = cache_folder_path + def __init__(self): + coverart_cache_path = cfg.setndefault('webapp', 'coverart_cache_path', value='../../src/webapp/build/cover-cache') + self.cache_folder_path = os.path.expanduser(coverart_cache_path) def find_file_by_hash(self, hash_value): for filename in os.listdir(self.cache_folder_path): From 5c491085d04d12168a26abc1414344639e6c2d67 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sun, 4 Feb 2024 23:09:36 +0100 Subject: [PATCH 085/121] Bump to v3.6.0-alpha --- src/jukebox/jukebox/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index 65ccc87c0..a7f37c5e6 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,8 +1,8 @@ VERSION_MAJOR = 3 -VERSION_MINOR = 5 +VERSION_MINOR = 6 VERSION_PATCH = 0 -VERSION_EXTRA = "" +VERSION_EXTRA = "alpha" # build a version string in compliance with the SemVer specification # https://semver.org/#semantic-versioning-specification-semver From b35013c70def28506e7bae3a79fdc0e01b6d656f Mon Sep 17 00:00:00 2001 From: s-martin Date: Mon, 5 Feb 2024 10:54:43 +0100 Subject: [PATCH 086/121] Link to nfcpy in docs (#2238) * Link to nfcpy * update doc for serial devices * Change name Co-authored-by: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> --------- Co-authored-by: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> --- documentation/developers/rfid/README.md | 3 ++- documentation/developers/rfid/generic_nfcpy.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/developers/rfid/README.md b/documentation/developers/rfid/README.md index c1412a90b..0b2df4db3 100644 --- a/documentation/developers/rfid/README.md +++ b/documentation/developers/rfid/README.md @@ -8,6 +8,7 @@ * [RDM6300 Reader](rdm6300.md) * [MFRC522 SPI Reader](mfrc522_spi.md) * [PN532 I2C Reader](pn532_i2c.md) + * [Generic Readers without HID (NFCpy)](generic_nfcpy.md) * [Mock Reader](mock_reader.md) * [Template Reader](template_reader.md) - \ No newline at end of file + diff --git a/documentation/developers/rfid/generic_nfcpy.md b/documentation/developers/rfid/generic_nfcpy.md index bea7b302a..76de98d88 100644 --- a/documentation/developers/rfid/generic_nfcpy.md +++ b/documentation/developers/rfid/generic_nfcpy.md @@ -4,7 +4,7 @@ This module is based on the user space NFC reader library [nfcpy](https://nfcpy. The link above also contains a list of [supported devices](https://nfcpy.readthedocs.io/en/latest/overview.html#supported-devices). The goal of this module is to handle USB NFC devices, that don't have a HID-keyboard -driver, and thus cannot be used with the [genericusb](genericusb.md) module. +driver, and thus cannot be used with the [genericusb](genericusb.md) module. Also some serial devices are supported. > [!NOTE] > Since nfcpy is a user-space library, it is required to supress the kernel from loading its driver. From 027ec2905e006565fbba2f51f90e2a7d3053838c Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:10:39 +0100 Subject: [PATCH 087/121] remove listen on ipv6 if disabled (#2254) --- installation/includes/02_helpers.sh | 15 +++++++++++++++ installation/routines/setup_jukebox_webapp.sh | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index be496b1b9..dfad65187 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -299,6 +299,21 @@ verify_file_contains_string() { log " CHECK" } +verify_file_does_not_contain_string() { + local string="$1" + local file="$2" + log " Verify '${string}' not found in '${file}'" + + if [[ -z "${string}" || -z "${file}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + if grep -iq "${string}" "${file}"; then + exit_on_error "ERROR: '${string}' found in '${file}'" + fi + log " CHECK" +} + verify_file_contains_string_once() { local string="$1" local file="$2" diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 7fcbce7ff..f95e547f1 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -117,6 +117,10 @@ _jukebox_webapp_register_as_system_service_with_nginx() { sudo cp -f "${INSTALLATION_PATH}/resources/default-settings/nginx.default" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" + if [ "$DISABLE_IPv6" = true ] ; then + sudo sed -i '/listen \[::\]:80/d' "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" + fi + # make sure nginx can access the home directory of the user sudo chmod o+x "${HOME_PATH}" @@ -147,6 +151,10 @@ _jukebox_webapp_check() { verify_apt_packages nginx verify_files_exists "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" + if [ "$DISABLE_IPv6" = true ] ; then + verify_file_does_not_contain_string "listen [::]:80" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" + fi + verify_service_enablement nginx.service enabled } From 9110c9424ea086aa7289a59ac7d69d0297b3adc0 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Sun, 11 Feb 2024 23:50:56 +0100 Subject: [PATCH 088/121] hotfix v3.5.1 --- src/jukebox/jukebox/version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index a7f37c5e6..dae1bc8c0 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,8 +1,8 @@ VERSION_MAJOR = 3 -VERSION_MINOR = 6 -VERSION_PATCH = 0 -VERSION_EXTRA = "alpha" +VERSION_MINOR = 5 +VERSION_PATCH = 1 +VERSION_EXTRA = "" # build a version string in compliance with the SemVer specification # https://semver.org/#semantic-versioning-specification-semver From d6b48d8c7a3cb5069e05687a1ac397c96f45d6e4 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Mon, 12 Feb 2024 00:07:24 +0100 Subject: [PATCH 089/121] Bump to v3.6.0-alpha --- src/jukebox/jukebox/version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index dae1bc8c0..a7f37c5e6 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,8 +1,8 @@ VERSION_MAJOR = 3 -VERSION_MINOR = 5 -VERSION_PATCH = 1 -VERSION_EXTRA = "" +VERSION_MINOR = 6 +VERSION_PATCH = 0 +VERSION_EXTRA = "alpha" # build a version string in compliance with the SemVer specification # https://semver.org/#semantic-versioning-specification-semver From 75743da4ccbd9e7b68f6e410eb89fefdc240736e Mon Sep 17 00:00:00 2001 From: s-martin Date: Wed, 14 Feb 2024 09:25:01 +0100 Subject: [PATCH 090/121] Extract docs for battery monitor (#2257) * extract battmon docs into markdown * Fix typo * add link to ADS1015 --- documentation/builders/README.md | 16 +++++---- .../components/power/batterymonitor.md | 33 +++++++++++++++++++ .../batt_mon_i2c_ads1015/__init__.py | 31 +---------------- 3 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 documentation/builders/components/power/batterymonitor.md diff --git a/documentation/builders/README.md b/documentation/builders/README.md index 6d9e67bff..ed08f5980 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -9,29 +9,31 @@ ## Features * Audio - * [Audio Output](./audio.md) - * [Bluetooth audio buttons](./bluetooth-audio-buttons.md) + * [Audio Output](./audio.md) + * [Bluetooth audio buttons](./bluetooth-audio-buttons.md) * [GPIO Recipes](./gpio.md) * [Card Database](./card-database.md) - * [RFID Cards synchronisation](./components/synchronisation/rfidcards.md) + * [RFID Cards synchronisation](./components/synchronisation/rfidcards.md) * [Auto Hotspot](./autohotspot.md) * File Management - * [Network share / Samba](./samba.md) + * [Network share / Samba](./samba.md) ## Hardware Components * [Power](./components/power/) - * [OnOff SHIM for safe power on/off](./components/power/onoff-shim.md) + * [OnOff SHIM for safe power on/off](./components/power/onoff-shim.md) + * [Battery Monitor based on a ADS1015](./components/power/batterymonitor.md) * [Soundcards](./components/soundcards/) - * [HiFiBerry Boards](./components/soundcards/hifiberry.md) + * [HiFiBerry Boards](./components/soundcards/hifiberry.md) * [RFID Readers](./../developers/rfid/README.md) ## Web Application * Music - * [Playlists, Livestreams and Podcasts](./webapp/playlists-livestreams-podcasts.md) + * [Playlists, Livestreams and Podcasts](./webapp/playlists-livestreams-podcasts.md) ## Advanced + * [Troubleshooting](./troubleshooting.md) * [Concepts](./concepts.md) * [System](./system.md) diff --git a/documentation/builders/components/power/batterymonitor.md b/documentation/builders/components/power/batterymonitor.md new file mode 100644 index 000000000..d57e14acb --- /dev/null +++ b/documentation/builders/components/power/batterymonitor.md @@ -0,0 +1,33 @@ +# Battery Monitor based on a ADS1015 + +> [!CAUTION] +> Lithium and other batteries are dangerous and must be treated with care. +> Rechargeable Lithium Ion batteries are potentially hazardous and can +> present a serious **FIRE HAZARD** if damaged, defective or improperly used. +> Do not use this circuit to a lithium ion battery without expertise and +> training in handling and use of batteries of this type. +> Use appropriate test equipment and safety protocols during development. +> There is no warranty, this may not work as expected or at all! + +The script in [src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/\_\_init\_\_.py](../../../../src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py) is intended to read out the voltage of a single Cell LiIon Battery using a [CY-ADS1015 Board](https://www.adafruit.com/product/1083): + +```text + 3.3V + + + | + .----o----. + ___ | | SDA + .--------|___|---o----o---------o AIN0 o------ + | 2MΩ | | | | SCL + | .-. | | ADS1015 o------ + --- | | --- | | + Battery - 1.5MΩ| | ---100nF '----o----' + 2.9V-4.2V| '-' | | + | | | | + === === === === +``` + +> [!WARNING] +> +> * the circuit is constantly draining the battery! (leak current up to: 2.1µA) +> * the time between sample needs to be a minimum 1sec with this high impedance voltage divider don't use the continuous conversion method! diff --git a/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py b/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py index af193a37f..551759c14 100644 --- a/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py +++ b/src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py @@ -37,36 +37,7 @@ class battmon_ads1015(BatteryMonitorBase.BattmonBase): """Battery Monitor based on a ADS1015 - > [!CAUTION] - > Lithium and other batteries are dangerous and must be treated with care. - > Rechargeable Lithium Ion batteries are potentially hazardous and can - > present a serious **FIRE HAZARD** if damaged, defective or improperly used. - > Do not use this circuit to a lithium ion battery without expertise and - > training in handling and use of batteries of this type. - > Use appropriate test equipment and safety protocols during development. - > There is no warranty, this may not work as expected or at all! - - This script is intended to read out the Voltage of a single Cell LiIon Battery using a CY-ADS1015 Board: - - 3.3V - + - | - .----o----. - ___ | | SDA - .--------|___|---o----o---------o AIN0 o------ - | 2MΩ | | | | SCL - | .-. | | ADS1015 o------ - --- | | --- | | - Battery - 1.5MΩ| | ---100nF '----o----' - 2.9V-4.2V| '-' | | - | | | | - === === === === - - Attention: - * the circuit is constantly draining the battery! (leak current up to: 2.1µA) - * the time between sample needs to be a minimum 1sec with this high impedance voltage divider - don't use the continuous conversion method! - + See [Battery Monitor documentation](../../builders/components/power/batterymonitor.md) """ def __init__(self, cfg): From 876219ad9d674807d6294321b53b9607614f2109 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:43:03 +0100 Subject: [PATCH 091/121] github actions update (#2239) * Bump actions/download-artifact from 3 to 4 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4) * Bump actions/upload-artifact from 3 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v3...v4) * Bump geekyeggo/delete-artifact from 2 to 4 * add write permission for artifact deletion * ignore fails for artifact deletion * Revert "ignore fails for artifact deletion" This reverts commit a759d89fc0e9e091e0bd11ff7c94cff96c0a5ff6. * Revert "add write permission for artifact deletion" This reverts commit 102840204290fb6543fc5d8f8245dca1ac21c63e. * remove cleanup stage for now --- .../bundle_webapp_and_release_v3.yml | 4 +-- .../test_docker_debian_codename_sub_v3.yml | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/bundle_webapp_and_release_v3.yml b/.github/workflows/bundle_webapp_and_release_v3.yml index 13bffe472..a64d6e288 100644 --- a/.github/workflows/bundle_webapp_and_release_v3.yml +++ b/.github/workflows/bundle_webapp_and_release_v3.yml @@ -101,7 +101,7 @@ jobs: tar -czvf ${{ steps.vars.outputs.webapp_bundle_name }} build - name: Artifact Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.vars.outputs.webapp_bundle_name }} path: ${{ steps.build-webapp.outputs.webapp-root-path }}/${{ steps.vars.outputs.webapp_bundle_name }} @@ -119,7 +119,7 @@ jobs: steps: - name: Artifact Download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ needs.build.outputs.webapp_bundle_name }} diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml index a9ec217dc..6deedb478 100644 --- a/.github/workflows/test_docker_debian_codename_sub_v3.yml +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -134,7 +134,7 @@ jobs: BASE_TEST_IMAGE=${{ steps.vars.outputs.image_tag_name_local_base }} - name: Artifact Upload Docker Image - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.vars.outputs.image_file_name }} path: ${{ steps.vars.outputs.image_file_path }} @@ -159,7 +159,7 @@ jobs: uses: docker/setup-buildx-action@v3.0.0 - name: Artifact Download Docker Image - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ needs.build.outputs.image_file_name }} @@ -177,15 +177,15 @@ jobs: args: | ./${{ matrix.test_script }} - # cleanup after test execution - cleanup: - # run only if tests didn't fail: keep the artifact to make job reruns possible - if: ${{ !failure() }} - needs: [build, test] - runs-on: ${{ inputs.runs_on }} - - steps: - - name: Artifact Delete Docker Image - uses: geekyeggo/delete-artifact@v2 - with: - name: ${{ needs.build.outputs.image_file_name }} + ## cleanup after test execution + #cleanup: + # # run only if tests didn't fail: keep the artifact to make job reruns possible + # if: ${{ !failure() }} + # needs: [build, test] + # runs-on: ${{ inputs.runs_on }} + # + # steps: + # - name: Artifact Delete Docker Image + # uses: geekyeggo/delete-artifact@v4 + # with: + # name: ${{ needs.build.outputs.image_file_name }} From f630d30fea559963c26694ed624d6a1025cd04ff Mon Sep 17 00:00:00 2001 From: s-martin Date: Wed, 14 Feb 2024 10:44:38 +0100 Subject: [PATCH 092/121] (doc) update the docstring markdown file (#2259) --- documentation/developers/docstring/README.md | 97 +++++++++++++------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/documentation/developers/docstring/README.md b/documentation/developers/docstring/README.md index aa7cefecc..c48c34a41 100644 --- a/documentation/developers/docstring/README.md +++ b/documentation/developers/docstring/README.md @@ -104,6 +104,12 @@ * [pbox\_set\_state](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.pbox_set_state) * [que\_set\_pbox](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.que_set_pbox) * [create\_outputs](#components.rfid.hardware.fake_reader_gui.gpioz_gui_addon.create_outputs) +* [components.rfid.hardware.generic\_nfcpy.description](#components.rfid.hardware.generic_nfcpy.description) +* [components.rfid.hardware.generic\_nfcpy.generic\_nfcpy](#components.rfid.hardware.generic_nfcpy.generic_nfcpy) + * [ReaderClass](#components.rfid.hardware.generic_nfcpy.generic_nfcpy.ReaderClass) + * [cleanup](#components.rfid.hardware.generic_nfcpy.generic_nfcpy.ReaderClass.cleanup) + * [stop](#components.rfid.hardware.generic_nfcpy.generic_nfcpy.ReaderClass.stop) + * [read\_card](#components.rfid.hardware.generic_nfcpy.generic_nfcpy.ReaderClass.read_card) * [components.rfid.hardware.generic\_usb.description](#components.rfid.hardware.generic_usb.description) * [components.rfid.hardware.generic\_usb.generic\_usb](#components.rfid.hardware.generic_usb.generic_usb) * [components.rfid.hardware.rc522\_spi.description](#components.rfid.hardware.rc522_spi.description) @@ -1948,6 +1954,61 @@ Add all output devices to the GUI List of all added GUI objects + + +# components.rfid.hardware.generic\_nfcpy.description + +List of supported devices https://nfcpy.readthedocs.io/en/latest/overview.html + + + + +# components.rfid.hardware.generic\_nfcpy.generic\_nfcpy + + + +## ReaderClass Objects + +```python +class ReaderClass(ReaderBaseClass) +``` + +The reader class for nfcpy supported NFC card readers. + + + + +#### cleanup + +```python +def cleanup() +``` + +The cleanup function: free and release all resources used by this card reader (if any). + + + + +#### stop + +```python +def stop() +``` + +This function is called to tell the reader to exit its reading function. + + + + +#### read\_card + +```python +def read_card() -> str +``` + +Blocking or non-blocking function that waits for a new card to appear and return the card's UID as string + + # components.rfid.hardware.generic\_usb.description @@ -2659,7 +2720,7 @@ def stop_autohotspot() Stop auto hotspot functionality -Basically disabling the cronjob and running the script one last time manually +Stopping and disabling the timer and running the service one last time manually @@ -2671,9 +2732,9 @@ Basically disabling the cronjob and running the script one last time manually def start_autohotspot() ``` -start auto hotspot functionality +Start auto hotspot functionality -Basically enabling the cronjob and running the script one time manually +Enabling and starting the timer (timer will start the service) @@ -2966,35 +3027,7 @@ class battmon_ads1015(BatteryMonitorBase.BattmonBase) Battery Monitor based on a ADS1015 -> [!CAUTION] -> Lithium and other batteries are dangerous and must be treated with care. -> Rechargeable Lithium Ion batteries are potentially hazardous and can -> present a serious **FIRE HAZARD** if damaged, defective or improperly used. -> Do not use this circuit to a lithium ion battery without expertise and -> training in handling and use of batteries of this type. -> Use appropriate test equipment and safety protocols during development. -> There is no warranty, this may not work as expected or at all! - -This script is intended to read out the Voltage of a single Cell LiIon Battery using a CY-ADS1015 Board: - - 3.3V - + - | - .----o----. - ___ | | SDA - .--------|___|---o----o---------o AIN0 o------ - | 2MΩ | | | | SCL - | .-. | | ADS1015 o------ - --- | | --- | | - Battery - 1.5MΩ| | ---100nF '----o----' - 2.9V-4.2V| '-' | | - | | | | - === === === === - -Attention: -* the circuit is constantly draining the battery! (leak current up to: 2.1µA) -* the time between sample needs to be a minimum 1sec with this high impedance voltage divider - don't use the continuous conversion method! +See [Battery Monitor documentation](../../builders/components/power/batterymonitor.md) From 21639b9480008d98e03456c3a7c87878ef8cee9b Mon Sep 17 00:00:00 2001 From: s-martin Date: Sun, 18 Feb 2024 22:01:17 +0100 Subject: [PATCH 093/121] (maint) Remove duplicate rst (#2269) * remove duplicate rst doc file * fix link --- documentation/developers/rfid/mock_reader.md | 2 +- .../bluetooth_audio_buttons/README.rst | 55 ------------------- 2 files changed, 1 insertion(+), 56 deletions(-) delete mode 100644 src/jukebox/components/controls/bluetooth_audio_buttons/README.rst diff --git a/documentation/developers/rfid/mock_reader.md b/documentation/developers/rfid/mock_reader.md index 4d5a3ea36..d6cac8cd0 100644 --- a/documentation/developers/rfid/mock_reader.md +++ b/documentation/developers/rfid/mock_reader.md @@ -6,7 +6,7 @@ machine - probably in a Python virtual environment. **place-capable**: yes -If you [mock the GPIO pins](../../../src/jukebox/components/gpio/gpioz/README.rst#use-mock-pins), this GUI will show the GPIO devices. +If you [mock the GPIO pins](../../builders/gpio.md#use-mock-pins), this GUI will show the GPIO devices. ![image](mock_reader.png) diff --git a/src/jukebox/components/controls/bluetooth_audio_buttons/README.rst b/src/jukebox/components/controls/bluetooth_audio_buttons/README.rst deleted file mode 100644 index 87a7eab2b..000000000 --- a/src/jukebox/components/controls/bluetooth_audio_buttons/README.rst +++ /dev/null @@ -1,55 +0,0 @@ -When a bluetooth sound device (headphone, speakers) connects -attempt to automatically listen to it's buttons (play, next, ...) - -The bluetooth input device name is matched automatically from the -bluetooth sound card device name. During boot up, it is uncertain if the bluetooth device connects first, -or the Jukebox service is ready first. Therefore, -after service initialization, already connected bluetooth sound devices are scanned and an attempt is made -to find their input buttons. - -.. note:: If the automatic matching fails, there currently is no - manual configuration option. Open an issue ticket if you have problems with the automatic matching. - -Button key codes are standardized and by default the buttons -play, pause, next song, previous song are recognized. Volume up/down is handled independently -from this module by PulseAudio and the bluetooth audio transmission protocol. - -The module needs to be enabled in the main configuration file with: - -.. code-block:: yaml - - bluetooth_audio_buttons: - enable: true - - -Custom key bindings ---------------------- - -You may change or extend the actions assigned to a button in the configuration. If the configuration contains -a block 'mapping', the default button-action mapping is *completely* replaced with the new mapping. The definitions for -each key looks like ``key-code: {rpc_command_definition}``. -The RPC command follows the regular RPC command rules as defined in :ref:`userguide/rpc_commands:RPC Commands`. - -.. code-block:: yaml - - bluetooth_audio_buttons: - enable: true - mapping: - # Play & pause both map to toggle which is also the usual behaviour of headsets - 200: - alias: toggle - 201: - alias: toggle - # Re-map next song button, to set defined output volume (for some fun) - 163: - package: volume - plugin: ctrl - method: set_volume - args: [18] - # Re-map prev song button to shutdown - 165: - alias: shutdown - - -Key codes can be found in the log files. Press the various buttons on your headset, while watching the -logs with e.g. ``tail -f shared/logs/app.log``. Look for entries like ``No callback registered for button ...``. From 5138bb535df254639f550ecb06223871724cf689 Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:49:10 +0100 Subject: [PATCH 094/121] Hotfix for pyzmq installation (#2272) * dont use prereleases * dont use prereleases (docker) * Bump to version 3.5.2 --- docker/armv7/jukebox.Dockerfile | 2 +- docker/jukebox.Dockerfile | 2 +- installation/routines/setup_jukebox_core.sh | 2 +- src/jukebox/jukebox/version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/armv7/jukebox.Dockerfile b/docker/armv7/jukebox.Dockerfile index d81c447de..a242f5faf 100644 --- a/docker/armv7/jukebox.Dockerfile +++ b/docker/armv7/jukebox.Dockerfile @@ -65,7 +65,7 @@ WORKDIR ${HOME} COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt -RUN pip install --no-cache-dir --pre --no-binary pyzmq pyzmq +RUN pip install --no-cache-dir --no-binary pyzmq pyzmq EXPOSE 5555 5556 diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index e7a7a45b1..0936f5d46 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -50,7 +50,7 @@ RUN [ "$(uname -m)" = "aarch64" ] && ARCH="arm64" || ARCH="$(uname -m)"; \ rm -f libzmq.tar.gz; RUN export ZMQ_PREFIX=${PREFIX} && export ZMQ_DRAFT_API=1 -RUN pip install -v --no-binary pyzmq --pre pyzmq +RUN pip install -v --no-binary pyzmq pyzmq EXPOSE 5555 5556 diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index d9c06b937..cb85198be 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -86,7 +86,7 @@ _jukebox_core_build_and_install_pyzmq() { fi ZMQ_PREFIX="${JUKEBOX_ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ - pip install -v --no-binary pyzmq --pre pyzmq + pip install -v --no-binary pyzmq pyzmq else print_lc " Skipping. pyzmq already installed" fi diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index dae1bc8c0..b97391207 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,7 +1,7 @@ VERSION_MAJOR = 3 VERSION_MINOR = 5 -VERSION_PATCH = 1 +VERSION_PATCH = 2 VERSION_EXTRA = "" # build a version string in compliance with the SemVer specification From 07208e60c507bd3c0761e48718bc8b363029aa47 Mon Sep 17 00:00:00 2001 From: Vito Zanotelli Date: Tue, 20 Feb 2024 16:27:51 +0100 Subject: [PATCH 095/121] Event Device Support (`evdev`) (#1943) * Add event device plugin This implements an "event device" listener plugin, that enables the user to configure the phoniebox to respond to events from an "event device" (device under /dev/input). This incluces eg button presses from an USB controller or keyboard. * Adds documentation for the event device plugin Includes a detailed how-to as well as example config * Allow empty button config This is an actual usecase in case someone wants to setup a device and figure out the button ids by looking at the logs * Update README.rst * Improve README - Remove duplicated section as suggested - Add Zero Delay Arcade USB Encoder usecase * Give more meaningfull name to listener thread variable * Link to old documentation * Remove README and fix typo The documentation is now part of the markdown documentation * Remove unecessary plain=1 * Remove indents in example code There was some left over indentation from the old rtf format in the python code examples. * Add example how to access evdev in docker * Example for new Evdev config structure This is tighly modeled after gpio. In contrast to GPIO, this is a list of devices containing each one or more input/output devices. Eg a Joystick has keys but potentially also rumble or leds. Currently only very simply input devices (buttons) are supported. This structer should enable to extend this easily. * Move evdev config to separate file As suggested this should be a separate file, similar to the gpioz config * Adapt evdev code to new config Now the config for evdev is akin to GPIO. The big difference is that multiple devices with each multiple input/output devices are supported. Note that the backend is still the old event device listener backend. Thus to support more features, the backend would need to be quite drastically overhauled. * Update documentation for new evdev config format * Fix wrong reference * Fix bug to correctly check supported input device type * Add more tests for evdev init * Fix wrong attribute name The attribute 'device_request' of EvDevKeyListener is now called 'device_name_request' * Validate input config better Fails if there is no device_name and setts proper default for 'exact' * Add pyzmq installation for github action This is required for some tests. Following the instructions from issue #2050 Specifically: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2050#issuecomment-1804785695 * Revert "Add pyzmq installation for github action" This reverts commit 75161b545a7a127a5d486c0770c449fbbd52c3d4. As this was not working * Mock `jukebox.publishing` in tests to avoid zmq Currently it is hard to install zmq (#2050) and for CI pytest `zmq` is not available. As the test do not really require `jukebox.publishing` running, mocking it in the test avoids it being imported. Thus the tests do not require `zmq` to be installed. --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> Co-authored-by: Vito Zanotelli Co-authored-by: s-martin --- documentation/builders/README.md | 1 + documentation/builders/event-devices.md | 120 ++++++++++ documentation/developers/docker.md | 14 ++ resources/default-settings/evdev.example.yaml | 59 +++++ .../bluetooth_audio_buttons/__init__.py | 4 +- .../controls/event_devices/__init__.py | 221 ++++++++++++++++++ test/evdev/test_evdev_init.py | 185 +++++++++++++++ 7 files changed, 602 insertions(+), 2 deletions(-) create mode 100644 documentation/builders/event-devices.md create mode 100644 resources/default-settings/evdev.example.yaml create mode 100644 src/jukebox/components/controls/event_devices/__init__.py create mode 100644 test/evdev/test_evdev_init.py diff --git a/documentation/builders/README.md b/documentation/builders/README.md index ed08f5980..512d26ed0 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -26,6 +26,7 @@ * [Soundcards](./components/soundcards/) * [HiFiBerry Boards](./components/soundcards/hifiberry.md) * [RFID Readers](./../developers/rfid/README.md) +* [Event devices (USB and other buttons)](./event-devices.md) ## Web Application diff --git a/documentation/builders/event-devices.md b/documentation/builders/event-devices.md new file mode 100644 index 000000000..8fe5d08e0 --- /dev/null +++ b/documentation/builders/event-devices.md @@ -0,0 +1,120 @@ +# Event devices + +## Background +Event devices are generic input devices that are exposed in `/dev/input`. +This includes USB peripherals (Keyboards, Controllers, Joysticks or Mouse) as well as potentially bluetooth devices. + +A specific usecase for this could be, if a Zero Delay Arcade USB Encoder is used to wire arcade buttons instead of using GPIO pins. + +These device interface support various kinds of input events, such as press, movements and potentially also outputs (eg. rumble, led lights, ...). Currently only the usage of button presses as input is supported. + +This functionality was previously implemented under the name of [USB buttons](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/develop/components/controls/buttons_usb_encoder/README.md). + +The devices and their button mappings need to be mapped in the configuration file. + +## Configuration + +To configure event devices, first add the plugin as an entry to the module list of your main configuration file ``shared/settings/jukebox.yaml``: + +``` yaml +modules: + named: + event_devices: controls.event_devices +``` + +And add the following section with the plugin specific configuration: +``` yaml +evdev: + enabled: true + config_file: ../../shared/settings/evdev.yaml +``` + +The actual configuration itself is stored in a separate file. In this case in ``../../shared/settings/evdev.yaml``. + +The configuration is structured akin to the configuration of the [GPIO devices](./gpio.md). + +In contrast to `gpio`, multiple devices (eg arcade controllser, keyboards, joysticks, mice, ...) are supported, each with their own `input_devices` (=buttons). `output_devices` or actions other than `on_press` are currently not yet supported. + +``` yaml +devices: # list of devices to listen for + {device nickname}: # config for a specific device + device_name: {device_name} # name of the device + exact: False/True # optional to require exact match. Otherwise it is sufficient that a part of the name matches + input_devices: # list of buttons to listen for for this device + {button nickname}: + type: Button + kwargs: + key_code: {key-code}: # evdev event id + actions: + on_press: # Currently only the on_press action is supported + {rpc_command_definition} # eg `alias: toggle` +``` +The `{device nickname}` is only for your own orientation and can be choosen freely. +For each device you need to figure out the `{device_name}` and the `{event_id}` corresponding to key strokes, as indicated in the sections below. + +### Identifying the `{device_name}` + +The `{device_name}` can be identified using the following Python snippet: + +``` Python +import evdev +devices = [evdev.InputDevice(path) for path in evdev.list_devices()] +for device in devices: + print(device.path, device.name, device.phys) +``` + +The output could be in the style of: + +``` +/dev/input/event1 Dell Dell USB Keyboard usb-0000:00:12.1-2/input0 +/dev/input/event0 Dell USB Optical Mouse usb-0000:00:12.0-2/input0 +``` + +In this example, the `{device_name}` could be `DELL USB Optical Mouse`. +Note that if you use the option `exact: False`, it would be sufficient to add a substring such as `USB Keyboard`. + +### Identifying the `{key-code}` + +The key code for a button press can be determined using the following code snippet: + +``` Python +import evdev +device = evdev.InputDevice('/dev/input/event0') +device.capabilities(verbose=True)[('EV_KEY', evdev.ecodes.EV_KEY)] +``` + +With the `InputDevice` corresponding to the path from the output of the section `{device_name}` (eg. in the example `/dev/input/event0` +would correspond to `Dell Dell USB Keyboard`). + +If the naming is not clear, it is also possible to empirically check for the key code by listening for events: + +``` Python +from evdev import InputDevice, categorize, ecodes +dev = InputDevice('/dev/input/event1') +print(dev) +for event in dev.read_loop(): + if event.type == ecodes.EV_KEY: + print(categorize(event)) +``` +The output could be of the form: +``` +device /dev/input/event1, name "DragonRise Inc. Generic USB Joystick ", phys "usb-3f980000.usb-1.2/input0" +key event at 1672569673.124168, 297 (BTN_BASE4), down +key event at 1672569673.385170, 297 (BTN_BASE4), up +``` + +In this example output, the `{key-code}` would be `297` + +Alternatively, the device could also be setup without a mapping. +Afterwards, when pressing keys, the key codes can be found in the log files. Press various buttons on your device, +while watching the logs with `tail -f shared/logs/app.log`. +Look for entries like `No callback registered for button ...`. + +### Specifying the `{rpc_command_definition}` + +The RPC command follows the regular RPC command rules as defined in the [following documentation](./rpc-commands.md). + + +## Full example config + +A complete configuration example for a USB Joystick controller can be found in the [examples](../../resources/default-settings/evdev.example.yaml). \ No newline at end of file diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 827178aca..19a57e414 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -291,6 +291,20 @@ $ docker run -it --rm \ --name jukebox jukebox ``` +## Testing EVDEV devices in Linux +To test the [event device capabilities](../builders/event-devices.md) in docker, the device needs to be made available to the container. + +### Linux +Mount the device into the container by configuring the appropriate device in a `devices` section of the `jukebox` service in the docker compose file. For example: + +```yaml + jukebox: + ... + devices: + - /dev/input/event3:/dev/input/event3 +``` + + ### Resources #### Mac diff --git a/resources/default-settings/evdev.example.yaml b/resources/default-settings/evdev.example.yaml new file mode 100644 index 000000000..5fbcd57e8 --- /dev/null +++ b/resources/default-settings/evdev.example.yaml @@ -0,0 +1,59 @@ +devices: # A list of evdev devices each containing one or multiple input/output devices + joystick: # A nickname for a device + device_name: DragonRise Inc. Generic USB # Device name + exact: false # If true, the device name must match exactly, otherwise it is sufficient to contain the name + input_devices: + TogglePlayback: + type: Button + kwargs: + key_code: 299 + actions: + on_press: + alias: toggle + NextSong: + type: Button + kwargs: + key_code: 298 + actions: + on_press: + alias: next_song + PrevSong: + type: Button + kwargs: + key_code: 297 + actions: + on_press: + alias: prev_song + VolumeUp: + type: Button + kwargs: + key_code: 296 + actions: + on_press: + alias: change_volume + args: 5 + VolumeDown: + type: Button + kwargs: + key_code: 295 + actions: + on_press: + alias: change_volume + args: -5 + VolumeReset: + type: Button + kwargs: + key_code: 291 + actions: + on_press: + package: volume + plugin: ctrl + method: set_volume + args: [18] + Shutdown: + type: Button + kwargs: + key_code: 292 + actions: + on_press: + alias: shutdown \ No newline at end of file diff --git a/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py b/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py index 4d17f398e..f03a447cd 100644 --- a/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py +++ b/src/jukebox/components/controls/bluetooth_audio_buttons/__init__.py @@ -49,8 +49,8 @@ def activate(device_name: str, exact: bool = True, open_initial_delay: float = 0 # Do a bit of housekeeping: Delete dead threads listener = list(filter(lambda x: x.is_alive(), listener)) # Check that there is no running thread for this device already - for ll in listener: - if ll.device_request == device_name and ll.is_alive(): + for thread in listener: + if thread.device_request == device_name and thread.is_alive(): logger.debug(f"Button listener thread already active for '{device_name}'") return diff --git a/src/jukebox/components/controls/event_devices/__init__.py b/src/jukebox/components/controls/event_devices/__init__.py new file mode 100644 index 000000000..407a73f14 --- /dev/null +++ b/src/jukebox/components/controls/event_devices/__init__.py @@ -0,0 +1,221 @@ +""" +Plugin to register event_devices (ie USB controllers, keyboards etc) in a + generic manner. + +This effectively does: + + * parse the configured event devices from the evdev.yaml + * setup listen threads + +""" +from __future__ import annotations + +import logging +from typing import Callable +from typing import Tuple + +import jukebox.cfghandler +import jukebox.plugs as plugin +import jukebox.utils +from components.controls.common.evdev_listener import EvDevKeyListener + +logger = logging.getLogger("jb.EventDevice") +cfg_main = jukebox.cfghandler.get_handler("jukebox") +cfg_evdev = jukebox.cfghandler.get_handler("eventdevices") + +# Keep track of all active key event listener threads +# Removal of dead listener threads is done in lazy fashion: +# only on a new connect are dead threads removed +listener: list[EvDevKeyListener] = [] +# Running count of all created listener threads for unique thread naming IDs +listener_cnt = 0 + +#: Indicates that the module is enabled and loaded w/o errors +IS_ENABLED: bool = False +#: The path of the config file the event device configuration was loaded from +CONFIG_FILE: str + +# Constants +_TYPE_BUTTON = 'Button' +_ACTION_ON_PRESS = 'on_press' + +_SUPPORTED_TYPES = [_TYPE_BUTTON] +_SUPPORTED_ACTIONS = {_TYPE_BUTTON: _ACTION_ON_PRESS} + + +@plugin.register +def activate( + device_name: str, + button_callbacks: dict[int, Callable], + exact: bool = True, + mandatory_keys: set[int] | None = None, +): + """Activate an event device listener + + :param device_name: device name + :type device_name: str + :param button_callbacks: mapping of event + code to RPC + :type button_callbacks: dict[int, Callable] + :param exact: Should the device_name match exactly + (default, false) or be a substring of the name? + :type exact: bool, optional + :param mandatory_keys: Mandatory event ids the + device needs to support. Defaults to None + to require all ids from the button_callbacks + :type mandatory_keys: set[int] | None, optional + """ + global listener + global listener_cnt + logger.debug("activate event device: %s", device_name) + # Do a bit of housekeeping: Delete dead threads + listener = list(filter(lambda x: x.is_alive(), listener)) + # Check that there is no running thread for this device already + for thread in listener: + if thread.device_name_request == device_name and thread.is_alive(): + logger.debug( + "Event device listener thread already active for '%s'", + device_name, + ) + return + + listener_cnt += 1 + new_listener = EvDevKeyListener( + device_name_request=device_name, + exact_name=exact, + thread_name=f"EvDevKeyListener-{listener_cnt}", + ) + + listener.append(new_listener) + if button_callbacks is not None: + new_listener.button_callbacks = button_callbacks + if mandatory_keys is not None: + new_listener.mandatory_keys = mandatory_keys + else: + new_listener.mandatory_keys = set(button_callbacks.keys()) + new_listener.start() + + +@plugin.initialize +def initialize(): + """Initialize event device button listener from config + + Initializes event buttons from the main configuration file. + Please see the documentation `builders/event-devices.md` for a specification of the format. + """ + global IS_ENABLED + global CONFIG_FILE + IS_ENABLED = False + enable = cfg_main.setndefault('evdev', 'enable', value=False) + CONFIG_FILE = cfg_main.setndefault('evdev', 'config_file', value='../../shared/settings/evdev.yaml') + if not enable: + return + try: + jukebox.cfghandler.load_yaml(cfg_evdev, CONFIG_FILE) + except Exception as e: + logger.error(f"Disable Event Devices due to error loading evdev config file. {e.__class__.__name__}: {e}") + return + + IS_ENABLED = True + + with cfg_evdev: + for name, config in cfg_evdev.getn( + "devices", + default={}, + ).items(): + logger.debug("activate %s", name) + try: + device_name, exact, button_callbacks = parse_device_config(config) + except Exception as e: + logger.error(f"Error parsing event device config for '{name}'. {e.__class__.__name__}: {e}") + continue + + logger.debug( + f'Call activate with: "{device_name}" and exact: {exact}', + ) + activate( + device_name, + button_callbacks=button_callbacks, + exact=exact, + ) + + +def parse_device_config(config: dict) -> Tuple[str, bool, dict[int, Callable]]: + """Parse the device configuration from the config file + + :param config: The configuration of the device + :type config: dict + :return: The parsed device configuration + :rtype: Tuple[str, bool, dict[int, Callable]] + """ + device_name = config.get("device_name") + if device_name is None: + raise ValueError("'device_name' is required but missing") + exact = bool(config.get("exact", False)) + input_devices = config.get("input_devices", {}) + # Raise warning if not used config present + if 'output_devices' in config: + logger.warning( + "Output devices are not yet supported for event devices", + ) + + # Parse input devices and convert them to button mappings. + # Due to the current implementation of the Event Device Listener, + # only the 'on_press' action is supported. + button_mapping = _input_devices_to_key_mapping(input_devices) + button_callbacks: dict[int, Callable] = {} + for key, action_request in button_mapping.items(): + button_callbacks[key] = jukebox.utils.bind_rpc_command( + action_request, + dereference=False, + logger=logger, + ) + return device_name, exact, button_callbacks + + +def _input_devices_to_key_mapping(input_devices: dict) -> dict: + """Convert input devices to key mapping + + Currently this only supports 'button' input devices with the 'on_press' action. + + :param input_devices: The input devices + :type input_devices: dict + :return: The mapping of key_code to action + :rtype: dict + """ + mapping = {} + for nickname, device in input_devices.items(): + input_type = device.get('type') + if input_type not in _SUPPORTED_TYPES: + logger.warning( + f"Input '{nickname}' device type '{input_type}' is not supported", + ) + continue + + key_code = device.get('kwargs', {}).get('key_code') + if key_code is None: + logger.warning( + f"Input '{nickname}' has no key_code and cannot be mapped.", + ) + continue + + actions = device.get('actions') + + for action_name, action in actions.items(): + if action_name not in _SUPPORTED_ACTIONS[_TYPE_BUTTON]: + logger.warning( + f"Input '{nickname}' has unsupported action '{action_name}'.\n" + f"Currently supported actions: {_SUPPORTED_ACTIONS}", + ) + if action_name == _ACTION_ON_PRESS: + mapping[key_code] = action + + return mapping + + +@plugin.atexit +def atexit(**ignored_kwargs): + global listener + for ll in listener: + ll.stop() + return listener diff --git a/test/evdev/test_evdev_init.py b/test/evdev/test_evdev_init.py new file mode 100644 index 000000000..c37f6ea85 --- /dev/null +++ b/test/evdev/test_evdev_init.py @@ -0,0 +1,185 @@ +""" Tests for the evdev __init__ module +""" +import sys +import unittest +from unittest.mock import patch +from unittest.mock import MagicMock + +# Before importing the module, the jukebox.plugs decorators need to be patched +# to not try to register the plugins +import jukebox.plugs as plugin + + +def dummy_decorator(fkt): + return fkt + + +plugin.register = dummy_decorator +plugin.initialize = dummy_decorator +plugin.atexit = dummy_decorator + +# Mock the jukebox.publishing module to prevent issues with zmq +# which is currently hard to install(see issue #2050) +# and not installed properly for CI +sys.modules['jukebox.publishing'] = MagicMock() + +# Import uses the patched decorators +from components.controls.event_devices import _input_devices_to_key_mapping # noqa: E402 +from components.controls.event_devices import parse_device_config # noqa: E402 + + +class TestInputDevicesToKeyMapping(unittest.TestCase): + def test_mapping_with_supported_input_type_and_key_code(self): + input_devices = { + 'device1': { + 'type': 'Button', + 'kwargs': { + 'key_code': 123 + }, + 'actions': { + 'on_press': 'action1' + } + }, + 'device2': { + 'type': 'Button', + 'kwargs': { + 'key_code': 456 + }, + 'actions': { + 'on_press': 'action2' + } + } + } + + expected_mapping = { + 123: 'action1', + 456: 'action2' + } + + mapping = _input_devices_to_key_mapping(input_devices) + + self.assertEqual(mapping, expected_mapping) + + def test_mapping_with_missing_type(self): + input_devices = { + 'device1': { + 'kwargs': { + 'key_code': 123 + }, + 'actions': { + 'on_press': 'action1' + } + } + } + + expected_mapping = {} + + mapping = _input_devices_to_key_mapping(input_devices) + + self.assertEqual(mapping, expected_mapping) + + def test_mapping_with_unsupported_input_type(self): + input_devices = { + 'device1': { + 'type': 'unknown', + 'kwargs': { + 'key_code': 'A' + }, + 'actions': { + 'on_press': 'action1' + } + } + } + + mapping = _input_devices_to_key_mapping(input_devices) + + self.assertEqual(mapping, {}) + + def test_mapping_with_missing_key_code(self): + input_devices = { + 'device1': { + 'type': 'button', + 'kwargs': {}, + 'actions': { + 'on_press': 'action1' + } + } + } + + mapping = _input_devices_to_key_mapping(input_devices) + + self.assertEqual(mapping, {}) + + def test_mapping_with_unsupported_action(self): + input_devices = { + 'device1': { + 'type': 'button', + 'kwargs': { + 'key_code': 'A' + }, + 'actions': { + 'unknown_action': 'action1' + } + } + } + + mapping = _input_devices_to_key_mapping(input_devices) + + self.assertEqual(mapping, {}) + + +class TestParseDeviceConfig(unittest.TestCase): + @patch('components.controls.event_devices.jukebox.utils.bind_rpc_command') + def test_parse_device_config(self, bind_rpc_command_mock): + config = { + "device_name": "Test Device", + "exact": True, + "input_devices": { + 'device1': { + 'type': 'Button', + 'kwargs': {'key_code': 123}, + 'actions': { + 'on_press': 'action1' + } + } + } + } + + device_name, exact, button_callbacks = parse_device_config(config) + self.assertEqual(device_name, "Test Device") + self.assertEqual(exact, True) + self.assertEqual(button_callbacks, { + 123: bind_rpc_command_mock.return_value, + }) + + def test_parse_device_config_missing_input_devices(self): + config = { + "device_name": "Test Device", + "exact": True + } + device_name, exact, button_callbacks = parse_device_config(config) + self.assertEqual(device_name, "Test Device") + self.assertEqual(exact, True) + self.assertEqual(button_callbacks, {}) + + def test_parse_device_config_missing_device_name(self): + config = { + "exact": True, + "input_devices": {} + } + self.assertRaises(ValueError, parse_device_config, config) + + def test_parse_device_config_missing_exact(self): + """Test that the default value for exact is False""" + config = { + "device_name": "Test Device", + "input_devices": {} + } + device_name, exact, button_callbacks = parse_device_config(config) + self.assertEqual(device_name, "Test Device") + self.assertEqual(exact, False) + self.assertEqual(button_callbacks, {}) + + +if __name__ == '__main__': + unittest.main() From c5e58d58ee1c3fc493841536b845d3f1bcfe4f5a Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sat, 16 Mar 2024 08:11:05 +0100 Subject: [PATCH 096/121] Fix search filter in library #2290 (#2292) --- src/webapp/src/components/Library/lists/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webapp/src/components/Library/lists/index.js b/src/webapp/src/components/Library/lists/index.js index 22970d9a9..e2b7a2d46 100644 --- a/src/webapp/src/components/Library/lists/index.js +++ b/src/webapp/src/components/Library/lists/index.js @@ -28,7 +28,7 @@ const LibraryLists = () => { const [cardId] = useState(searchParams.get('cardId')); const [musicFilter, setMusicFilter] = useState(''); - const handleMusicFolder = (event) => { + const handleMusicFilter = (event) => { setMusicFilter(event.target.value); }; @@ -49,7 +49,7 @@ const LibraryLists = () => { {isSelecting && } Date: Sat, 16 Mar 2024 16:50:46 +0100 Subject: [PATCH 097/121] fix: Stop Back Action at audiofolder root level (#2293) * bugfix: Stop Back Action at audiofolder root level This also allows to go back when folder is empty Fixes #2224 * fix: Flake8 issue * Remove some minor issues --- src/jukebox/jukebox/playlistgenerator.py | 9 +++++-- src/webapp/public/locales/de/translation.json | 4 +-- src/webapp/public/locales/en/translation.json | 4 +-- .../actions/play-music/selected-folder.js | 3 +-- .../Library/lists/folders/folder-list-item.js | 13 +++++---- .../Library/lists/folders/folder-list.js | 27 ++++++++++++------- .../components/Library/lists/folders/index.js | 9 ++++--- src/webapp/src/config.js | 7 ++--- 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/jukebox/jukebox/playlistgenerator.py b/src/jukebox/jukebox/playlistgenerator.py index b9f0223c6..e7f5b50d3 100755 --- a/src/jukebox/jukebox/playlistgenerator.py +++ b/src/jukebox/jukebox/playlistgenerator.py @@ -275,7 +275,12 @@ def get_directory_content(self, path='.'): logger.error(f" {e.__class__.__name__}: {e}") else: for m in content: - self.playlist.append({'type': TYPE_DECODE[m.filetype], 'name': m.name, 'path': m.path}) + self.playlist.append({ + 'type': TYPE_DECODE[m.filetype], + 'name': m.name, + 'path': m.path, + 'relpath': os.path.relpath(m.path, self._music_library_base_path) + }) def _parse_nonrecusive(self, path='.'): return [x.path for x in self._get_directory_content(path) if x.filetype != TYPE_DIR] @@ -294,7 +299,7 @@ def _parse_recursive(self, path='.'): return recursive_playlist def parse(self, path='.', recursive=False): - """Parse the folder ``path`` and create a playlist from it's content + """Parse the folder ``path`` and create a playlist from its content :param path: Path to folder **relative** to ``music_library_base_path`` :param recursive: Parse folder recursivley, or stay in top-level folder diff --git a/src/webapp/public/locales/de/translation.json b/src/webapp/public/locales/de/translation.json index f3bd89782..d1a4391d6 100644 --- a/src/webapp/public/locales/de/translation.json +++ b/src/webapp/public/locales/de/translation.json @@ -138,8 +138,8 @@ "title": "Wähle ein Album, einen Ordner oder einen Song aus" }, "folders": { - "no-music": "Keine Musik vorhanden!", - "empty-folder": "Dieser Ordner ist leer!", + "no-music": "☝️ Keine Musik vorhanden!", + "empty-folder": "Dieser Ordner ist leer! 🙈", "show-folder-content": "Zeige den Ordnerinhalt an", "back-button-label": "Zurück" }, diff --git a/src/webapp/public/locales/en/translation.json b/src/webapp/public/locales/en/translation.json index 348d3771d..74fd9a696 100644 --- a/src/webapp/public/locales/en/translation.json +++ b/src/webapp/public/locales/en/translation.json @@ -138,8 +138,8 @@ "title": "Select an album, folder or song" }, "folders": { - "no-music": "No music found!", - "empty-folder": "This folder is empty!", + "no-music": "☝️ No music found!", + "empty-folder": "This folder is empty! 🙈", "show-folder-content": "Show folder content", "back-button-label": "Back" }, diff --git a/src/webapp/src/components/Cards/controls/actions/play-music/selected-folder.js b/src/webapp/src/components/Cards/controls/actions/play-music/selected-folder.js index bf1976fac..3191c36cf 100644 --- a/src/webapp/src/components/Cards/controls/actions/play-music/selected-folder.js +++ b/src/webapp/src/components/Cards/controls/actions/play-music/selected-folder.js @@ -8,7 +8,6 @@ import { import NoMusicSelected from './no-music-selected'; import FolderTypeAvatar from '../../../../Library/lists/folders/folder-type-avatar'; -import { DEFAULT_AUDIO_DIR } from '../../../../../config'; const SelectedFolder = ({ values: [folder] }) => { // TODO: Implement type correctly @@ -19,7 +18,7 @@ const SelectedFolder = ({ values: [folder] }) => { - + ); diff --git a/src/webapp/src/components/Library/lists/folders/folder-list-item.js b/src/webapp/src/components/Library/lists/folders/folder-list-item.js index 755feef15..3be77fbbc 100644 --- a/src/webapp/src/components/Library/lists/folders/folder-list-item.js +++ b/src/webapp/src/components/Library/lists/folders/folder-list-item.js @@ -13,7 +13,6 @@ import NavigateNextIcon from '@mui/icons-material/NavigateNext'; import request from '../../../../utils/request'; import FolderLink from './folder-link'; import FolderTypeAvatar from './folder-type-avatar'; -import { DEFAULT_AUDIO_DIR } from '../../../../config'; const FolderListItem = ({ folder, @@ -21,12 +20,12 @@ const FolderListItem = ({ registerMusicToCard, }) => { const { t } = useTranslation(); - const { type, name, path } = folder; + const { type, name, relpath } = folder; const playItem = () => { switch(type) { - case 'directory': return request('play_folder', { folder: path, recursive: true }); - case 'file': return request('play_single', { song_url: path.replace(`${DEFAULT_AUDIO_DIR}/`, '') }); + case 'directory': return request('play_folder', { folder: relpath, recursive: true }); + case 'file': return request('play_single', { song_url: relpath }); // TODO: Add missing Podcast // TODO: Add missing Stream default: return; @@ -35,8 +34,8 @@ const FolderListItem = ({ const registerItemToCard = () => { switch(type) { - case 'directory': return registerMusicToCard('play_folder', { folder: path, recursive: true }); - case 'file': return registerMusicToCard('play_single', { song_url: path.replace(`${DEFAULT_AUDIO_DIR}/`, '') }); + case 'directory': return registerMusicToCard('play_folder', { folder: relpath, recursive: true }); + case 'file': return registerMusicToCard('play_single', { song_url: relpath }); // TODO: Add missing Podcast // TODO: Add missing Stream default: return; @@ -50,7 +49,7 @@ const FolderListItem = ({ type === 'directory' ? diff --git a/src/webapp/src/components/Library/lists/folders/folder-list.js b/src/webapp/src/components/Library/lists/folders/folder-list.js index 36a9bcebd..3222e4234 100644 --- a/src/webapp/src/components/Library/lists/folders/folder-list.js +++ b/src/webapp/src/components/Library/lists/folders/folder-list.js @@ -1,12 +1,17 @@ import React, { memo } from 'react'; import { dropLast } from "ramda"; +import { useTranslation } from 'react-i18next'; -import { List } from '@mui/material'; +import { + List, + ListItem, + Typography, +} from '@mui/material'; import FolderListItem from './folder-list-item'; import FolderListItemBack from './folder-list-item-back'; -import { ROOT_DIRS } from '../../../../config'; +import { ROOT_DIR } from '../../../../config'; const FolderList = ({ dir, @@ -14,13 +19,14 @@ const FolderList = ({ isSelecting, registerMusicToCard, }) => { + const { t } = useTranslation(); + const getParentDir = (dir) => { - // TODO: ROOT_DIRS should be removed after paths are relative const decodedDir = decodeURIComponent(dir); - if (ROOT_DIRS.includes(decodedDir)) return undefined; + if (decodedDir === ROOT_DIR) return undefined; - const parentDir = dropLast(1, decodedDir.split('/')).join('/'); + const parentDir = dropLast(1, decodedDir.split('/')).join('/') || ROOT_DIR; return parentDir; } @@ -29,11 +35,14 @@ const FolderList = ({ return ( {parentDir && - + + } + {folders.length === 0 && + + {t('library.folders.empty-folder')} + } - {folders.map((folder, key) => + {folders.length > 0 && folders.map((folder, key) => { const { t } = useTranslation(); - const { dir = './' } = useParams(); + const { dir = ROOT_DIR } = useParams(); const [folders, setFolders] = useState([]); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -49,9 +51,8 @@ const Folders = ({ if (isLoading) return ; if (error) return {t('library.loading-error')}; - if (!filteredFolders.length) { - if (musicFilter) return {`☝️ ${t('library.folders.no-music')}`}; - return {`${t('library.folders.empty-folder')} 🙈`}; + if (musicFilter && !filteredFolders.length) { + return {t('library.folders.no-music')}; } return ( diff --git a/src/webapp/src/config.js b/src/webapp/src/config.js index 482db205e..46a6ec1df 100644 --- a/src/webapp/src/config.js +++ b/src/webapp/src/config.js @@ -17,9 +17,7 @@ const SUBSCRIPTIONS = [ 'volume.level', ]; -const DEFAULT_AUDIO_DIR = '../../shared/audiofolders'; -const ROOT_DIRS = ['./', DEFAULT_AUDIO_DIR]; - +const ROOT_DIR = './'; // TODO: The reason why thos commands are empty objects is due to a legacy // situation where titles associated with those commands were stored here @@ -83,11 +81,10 @@ const JUKEBOX_ACTIONS_MAP = { const TIMER_STEPS = [0, 2, 5, 10, 15, 20, 30, 45, 60, 120, 180, 240]; export { - DEFAULT_AUDIO_DIR, JUKEBOX_ACTIONS_MAP, PUBSUB_ENDPOINT, REQRES_ENDPOINT, - ROOT_DIRS, + ROOT_DIR, SUBSCRIPTIONS, TIMER_STEPS, } From cfd72b2f825c69f2d81247d47483c5c6b461d27f Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:03:47 +0100 Subject: [PATCH 098/121] Reactivate delete artifact (#2297) * activated delete-artifacts again with v5 * deactivate failOnError for deletion --- .../test_docker_debian_codename_sub_v3.yml | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml index 6deedb478..e3ef02bbe 100644 --- a/.github/workflows/test_docker_debian_codename_sub_v3.yml +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -138,7 +138,7 @@ jobs: with: name: ${{ steps.vars.outputs.image_file_name }} path: ${{ steps.vars.outputs.image_file_path }} - retention-days: 1 + retention-days: 2 # Run tests with build image @@ -177,15 +177,16 @@ jobs: args: | ./${{ matrix.test_script }} - ## cleanup after test execution - #cleanup: - # # run only if tests didn't fail: keep the artifact to make job reruns possible - # if: ${{ !failure() }} - # needs: [build, test] - # runs-on: ${{ inputs.runs_on }} - # - # steps: - # - name: Artifact Delete Docker Image - # uses: geekyeggo/delete-artifact@v4 - # with: - # name: ${{ needs.build.outputs.image_file_name }} + # cleanup after test execution + cleanup: + # run only if tests didn't fail: keep the artifact to make job reruns possible + if: ${{ !failure() }} + needs: [build, test] + runs-on: ${{ inputs.runs_on }} + + steps: + - name: Artifact Delete Docker Image + uses: geekyeggo/delete-artifact@v5 + with: + name: ${{ needs.build.outputs.image_file_name }} + failOnError: false From 2b5ef6ed2beaa6e5aa8a9cd65b42367dca4de398 Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Wed, 20 Mar 2024 22:01:40 +0000 Subject: [PATCH 099/121] (docs) fix gpio shutdown button naming (#2301) --- documentation/builders/gpio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/builders/gpio.md b/documentation/builders/gpio.md index 9d496c6b7..d0d82b206 100644 --- a/documentation/builders/gpio.md +++ b/documentation/builders/gpio.md @@ -112,7 +112,7 @@ A button to shutdown the Jukebox if it is pressed for more than 3 seconds. Note ```yml input_devices: - IncreaseVolume: + Shutdown: type: LongPressButton kwargs: pin: 3 From c3b622c393267f308d17b807ea6bb4d2639f24e7 Mon Sep 17 00:00:00 2001 From: s-martin Date: Thu, 21 Mar 2024 10:49:04 +0100 Subject: [PATCH 100/121] Add markdown linting (#2284) * fix docs * add markdown action * use different action * change linter * add checkout * change globs * change wildcard * Aktualisieren von markdown_v3.yml * fix ignore * comment ignore * change version * use master * new action used for linting * Fix config * Remove ignore * Ignore docstring dir * ignore GitHub * Aktualisieren von markdown_v3.yml * use dot true * Aktualisieren von markdown_v3.yml * Use new action * Fix continue-on-error * Remove unnecessary code * Add markdownlint config * Rename . markdownlint-cli2.yaml to .markdownlint-cli2.yaml * Use config file * Simplify globs * Use globs and ignore in config file * Ignore $ errors * Ignore src and .github * Use only config file * Fix general glob * Ignore inline html * Disable MD010 * Fix language * dont warn trailing punctuation * fix warnings * fix a warning * reduce ignore * fix warnings * fail check, if markdownlint fails * add runner script and hook * Update documentation/builders/components/power/onoff-shim.md Co-authored-by: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> * Update playlists-livestreams-podcasts.md * add documentation for documentation with md * get rid of docker * fix comments * incorporate comments * Update documentation/developers/documentation.md Co-authored-by: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> * check, if cmd does not exisr --------- Co-authored-by: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> --- .githooks/pre-commit | 17 ++ .github/ISSUE_TEMPLATE/bug_template.md | 2 - .github/workflows/markdown_v3.yml | 28 +++ .markdownlint-cli2.yaml | 52 +++++ CODE_OF_CONDUCT.md | 14 +- CONTRIBUTING.md | 15 +- documentation/README.md | 2 +- documentation/builders/audio.md | 3 + documentation/builders/autohotspot.md | 15 +- .../builders/components/power/onoff-shim.md | 7 +- .../components/soundcards/hifiberry.md | 1 - documentation/builders/configuration.md | 5 +- documentation/builders/event-devices.md | 12 +- documentation/builders/installation.md | 21 +- documentation/builders/samba.md | 5 +- documentation/builders/troubleshooting.md | 8 +- .../webapp/playlists-livestreams-podcasts.md | 16 +- documentation/developers/README.md | 1 + documentation/developers/docker.md | 52 +++-- documentation/developers/documentation.md | 39 ++++ documentation/developers/rfid/README.md | 1 - .../developers/rfid/generic_nfcpy.md | 7 +- documentation/developers/rfid/mfrc522_spi.md | 3 +- documentation/developers/rfid/pn532_i2c.md | 12 +- .../developers/rfid/template_reader.md | 3 +- documentation/developers/webapp.md | 17 +- run_markdownlint.sh | 13 ++ src/webapp/package-lock.json | 211 ++++++++++++++++++ src/webapp/package.json | 3 + 29 files changed, 492 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/markdown_v3.yml create mode 100644 .markdownlint-cli2.yaml create mode 100644 documentation/developers/documentation.md create mode 100755 run_markdownlint.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit index b1b0c0348..8ffc7de0c 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -9,6 +9,9 @@ # Note: This only checks the modified files # - docs build of if any python file is staged # Note: This builds the entire documentation if a changed file goes into the documentation +# - Markdownlint if any markdown file is staged +# Note: This checks all markdown files as configured in .markdownlint-cli2.yaml + # # If there are problem with this script, commit may still be done with # git commit --no-verify @@ -40,6 +43,20 @@ fi code=$(( flake8_code + doc_code )) +# Pass all staged markdown files through markdownlint-cli2 +MD_FILES="$(git diff --diff-filter=d --staged --name-only -- **/*.md)" +markdownlint_code=0 +if [[ -n $MD_FILES ]]; then + echo -e "\n**************************************************************" + echo "Modified Markdown files. Running markdownlint-cli2 ... " + echo -e "**************************************************************\n" + ./run_markdownlint.sh + markdownlint_code=$? + echo "Markdownlint-cli2 return code: $markdownlint_code" +fi + +code=$(( flake8_code + doc_code + markdownlint_code)) + if [[ code -gt 0 ]]; then echo -e "\n**************************************************************" echo -e "ERROR(s) during pre-commit checks. Aborting commit!" diff --git a/.github/ISSUE_TEMPLATE/bug_template.md b/.github/ISSUE_TEMPLATE/bug_template.md index 508cf50b9..afcb9bd0c 100644 --- a/.github/ISSUE_TEMPLATE/bug_template.md +++ b/.github/ISSUE_TEMPLATE/bug_template.md @@ -33,7 +33,6 @@ Please post here the output of 'tail -n 500 /var/log/syslog' or 'journalctl -u m i.e. `find logfiles at https://paste.ubuntu.com/p/cRS7qM8ZmP/` --> - ## Software ### Base image and version @@ -59,7 +58,6 @@ the following command will help with that i.e. `scripts/installscripts/buster-install-default.sh` --> - ## Hardware ### RaspberryPi version diff --git a/.github/workflows/markdown_v3.yml b/.github/workflows/markdown_v3.yml new file mode 100644 index 000000000..38284038e --- /dev/null +++ b/.github/workflows/markdown_v3.yml @@ -0,0 +1,28 @@ +name: Markdown Linting + +on: + push: + branches: + - 'future3/**' + paths: + - '**.md' + pull_request: + branches: + - 'future3/**' + paths: + - '**.md' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Linting markdown + uses: DavidAnson/markdownlint-cli2-action@v15 + with: + config: .markdownlint-cli2.yaml + #continue-on-error: true diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml new file mode 100644 index 000000000..2fd1409d8 --- /dev/null +++ b/.markdownlint-cli2.yaml @@ -0,0 +1,52 @@ +# +# markdownlint-cli2 configuration, see https://github.com/DavidAnson/markdownlint-cli2?tab=readme-ov-file#configuration +# + +# rules, see https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md +config: + line-length: false + # ignore dollar signs + commands-show-output: false + no-trailing-punctuation: false + +# Include a custom rule package +#customRules: +# - markdownlint-rule-titlecase + +# Fix no fixable errors +fix: false + +# Define a custom front matter pattern +#frontMatter: "[^]*<\/head>" + +# Define glob expressions to use (only valid at root) +globs: + - "**.md" + +# Define glob expressions to ignore +ignores: + - "documentation/developers/docstring/*" + - "src/**" + +# Use a plugin to recognize math +#markdownItPlugins: +# - +# - "@iktakahiro/markdown-it-katex" + +# Additional paths to resolve module locations from +#modulePaths: +# - "./modules" + +# Enable inline config comments +noInlineConfig: false + +# Disable progress on stdout (only valid at root) +noProgress: true + +# Use a specific formatter (only valid at root) +#outputFormatters: +# - +# - markdownlint-cli2-formatter-default + +# Show found files on stdout (only valid at root) +showFound: true diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 332baee88..a28c343f0 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,14 +1,14 @@ +# Contributor Covenant Code of Conduct -Dear Phonieboxians, - -As the Phoniebox community is growing, somebody suggested a pull request with the below document. I was hesitant to include it right away, but at the same time I thought: it might be good to have some kind of document to formulate the foundation this project is built on. To tell you the truth, this document is not it. However, it is a start and I thought: why not open this in the spirit of open source, sharing and pull requests and see if and how you or you or you want to change or add parts of this very *standard and corporate* document. Like most of you, I also have a small kid and my time is scarce, I might find some time though to add a bit. - -All the best, Micz +> [!NOTE] +> Dear Phonieboxians, +> +> As the Phoniebox community is growing, somebody suggested a pull request with the below document. I was hesitant to include it right away, but at the same time I thought: it might be good to have some kind of document to formulate the foundation this project is built on. To tell you the truth, this document is not it. However, it is a start and I thought: why not open this in the spirit of open source, sharing and pull requests and see if and how you or you or you want to change or add parts of this very *standard and corporate* document. Like most of you, I also have a small kid and my time is scarce, I might find some time though to add a bit. +> +> All the best, Micz 2018-08-21 -# Contributor Covenant Code of Conduct - This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] ## Our Pledge diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4ac9cd16..feb47f2c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,15 +45,16 @@ as local, temporary scratch areas. Contributors have played a bigger role over time to keep Phoniebox on the edge of innovation :) -Our goal is to make it simple for you to contribute changes that improve functionality in your specific environment. -To achieve this, we have a set of guidelines that we kindly request contributors to adhere to. +Our goal is to make it simple for you to contribute changes that improve functionality in your specific environment. +To achieve this, we have a set of guidelines that we kindly request contributors to adhere to. These guidelines help us maintain a streamlined process and stay on top of incoming contributions. To report bug fixes and improvements, please follow the steps outlined below: + 1. For bug fixes and minor improvements, simply open a new issue or pull request (PR). 2. If you intend to port a feature from Version 2.x to future3 or wish to implement a new feature, we recommend reaching out to us beforehand. - - In such cases, please create an issue outlining your plans and intentions. - - We will ensure that there are no ongoing efforts on the same topic. + * In such cases, please create an issue outlining your plans and intentions. + * We will ensure that there are no ongoing efforts on the same topic. We eagerly await your contributions! You can review the current [feature list](documentation/developers/status.md) to check for available features and ongoing work. @@ -108,7 +109,7 @@ Run the checks below on the code. Fix those issues! Or you are running in delays We provide git hooks for those checks for convenience. To activate ~~~bash -cp .githooks/pre-commit` .git/hooks/. +cp .githooks/pre-commit .git/hooks/. ~~~ ### Python Code @@ -152,7 +153,7 @@ to detect in advance. If the code change results in a test failure, we will make our best effort to correct the error. If a fix cannot be determined and committed within 24 hours -of its discovery, the commit(s) responsible _may_ be reverted, at the +of its discovery, the commit(s) responsible *may* be reverted, at the discretion of the committer and Phonie maintainers. The original contributor will be notified of the revert. @@ -163,7 +164,7 @@ The original contributor will be notified of the revert. ## Guidelines -* Phoniebox runs on Raspberry Pi OS. +* Phoniebox runs on Raspberry Pi OS. * Minimum python version is currently **Python 3.9**. ## Additional Resources diff --git a/documentation/README.md b/documentation/README.md index bb11dd6f4..cbfb6276a 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -42,7 +42,7 @@ project check out the [documentation of Version 2](https://github.com/MiczFlor/R Version 3 has reached a mature state and will soon be the default version. However, some features may still be missing. Please check the [Feature Status](./developers/status.md), if YOUR feature is already implemented. -> [!NOTE] +> [!NOTE] > If version 3 has all the features you need, we recommend using Version 3. If there is a feature missing, please open an issue. diff --git a/documentation/builders/audio.md b/documentation/builders/audio.md index 93c46dd54..765c6a08e 100644 --- a/documentation/builders/audio.md +++ b/documentation/builders/audio.md @@ -24,6 +24,7 @@ to setup the configuration for the Jukebox Core App. Run the following steps in a console: + ```bash # Check available PulseAudio sinks $ pactl list sinks short @@ -45,6 +46,7 @@ $ paplay /usr/share/sounds/alsa/Front_Center.wav # This must also work when using an ALSA device $ aplay /usr/share/sounds/alsa/Front_Center.wav ``` + You can also try different PulseAudio sinks without setting the default sink. In this case the volume is the last used volume level for this sink: @@ -86,6 +88,7 @@ Pairing successful .... [PowerLocus Buddy]# exit ``` + If `bluetoothctl` has trouble to execute due to permission issue, try `sudo bluetoothctl`. Wait for a few seconds and then with `$ pactl list sinks short`, check wether the Bluetooth device shows up as an output. diff --git a/documentation/builders/autohotspot.md b/documentation/builders/autohotspot.md index 5a62a37ca..8efde605a 100644 --- a/documentation/builders/autohotspot.md +++ b/documentation/builders/autohotspot.md @@ -7,11 +7,12 @@ The Auto-Hotspot function enables the Jukebox to switch its connection between a ## How to connect -When the Jukebox cannot connect to a known WiFi, it will automatically create a hotspot. +When the Jukebox cannot connect to a known WiFi, it will automatically create a hotspot. You can connect to this hotspot using the password set during installation. Afterwards, you can access the Web App or connect via SSH as before, using the IP from the configuration. The default configuration is + ``` text * SSID : Phoniebox_Hotspot_ * Password : PlayItLoud! @@ -23,8 +24,7 @@ The default configuration is Auto-Hotspot can be enabled or disabled using the Web App or RPC Commands. -> [!NOTE] -> Disabling the Auto-Hotspot will run the WiFi check again and maintain the last connection state until reboot. +Disabling the Auto-Hotspot will run the WiFi check again and maintain the last connection state until reboot. > [!IMPORTANT] > If you disable this feature, you will lose access to the Jukebox if you are not near a known WiFi after reboot! @@ -34,11 +34,13 @@ Auto-Hotspot can be enabled or disabled using the Web App or RPC Commands. ### AutoHotspot functionality is not working Check the `autohotspot.service` status + ``` bash sudo systemctl status autohotspot.service ``` and logs + ``` bash sudo journalctl -u autohotspot.service -n 50 ``` @@ -52,12 +54,13 @@ Check your WiFi configuration. ### You need to add a new WiFi network to the Raspberry Pi #### Using the command line + Connect to the hotspot and open a terminal. Use the [raspi-config](https://www.raspberrypi.com/documentation/computers/configuration.html#wireless-lan) tool to add the new WiFi. ## Resources * [Raspberry Connect - Auto WiFi Hotspot Switch](https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection) * [Raspberry Pi - Configuring networking](https://www.raspberrypi.com/documentation/computers/configuration.html#using-the-command-line) -* [dhcpcd / wpa_supplicant]() - * [hostapd](http://w1.fi/hostapd/) - * [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) +* dhcpcd / wpa_supplicant + * [hostapd](http://w1.fi/hostapd/) + * [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) diff --git a/documentation/builders/components/power/onoff-shim.md b/documentation/builders/components/power/onoff-shim.md index b83ea6140..e9a2849c9 100644 --- a/documentation/builders/components/power/onoff-shim.md +++ b/documentation/builders/components/power/onoff-shim.md @@ -9,7 +9,7 @@ To install the software, open a terminal and type the following command to run t > [!NOTE] > The installation will ask you a few questions. You can safely answer with the default response. -``` +```bash curl https://get.pimoroni.com/onoffshim | bash ``` @@ -28,9 +28,8 @@ The OnOff SHIM comes with a 12-PIN header which needs soldering. If you want to | GPLCLK0 | 7 | 7 | GPIO4 | | GPIO17 | 11 | 11 | GPIO17 | -* More information can be found here: https://pinout.xyz/pinout/onoff_shim +* More information can be found here: ## Assembly options -![](https://cdn.review-images.pimoroni.com/upload-b6276a310ccfbeae93a2d13ec19ab83b-1617096824.jpg?width=640) - +![OnOffShim soldered on a Raspberry Pi](https://cdn.review-images.pimoroni.com/upload-b6276a310ccfbeae93a2d13ec19ab83b-1617096824.jpg?width=640) diff --git a/documentation/builders/components/soundcards/hifiberry.md b/documentation/builders/components/soundcards/hifiberry.md index 1f19fa96d..f663abb42 100644 --- a/documentation/builders/components/soundcards/hifiberry.md +++ b/documentation/builders/components/soundcards/hifiberry.md @@ -19,7 +19,6 @@ If you know you HifiBerry Board identifier, you can run the script as a 1-liner If you like to disable your HiFiberry Sound card and enable onboard sound, run the following command - ```bash ./setup_hifiberry.sh disable ``` diff --git a/documentation/builders/configuration.md b/documentation/builders/configuration.md index dd4c1fd53..c75f53683 100644 --- a/documentation/builders/configuration.md +++ b/documentation/builders/configuration.md @@ -27,9 +27,10 @@ $ ./run_jukebox.sh # Restart the service $ systemctl --user start jukebox-daemon ``` -To try different configurations, you can start the Jukebox with a custom config file. + +To try different configurations, you can start the Jukebox with a custom config file. This could be useful if you want your Jukebox to only allow a lower volume when started -at nighttime, signaling it's time to go to bed. :-) +at nighttime, signaling it's time to go to bed. :-) The path to the custom config file must be either absolute or relative to the folder `src/jukebox/`. ```bash diff --git a/documentation/builders/event-devices.md b/documentation/builders/event-devices.md index 8fe5d08e0..95e871430 100644 --- a/documentation/builders/event-devices.md +++ b/documentation/builders/event-devices.md @@ -1,6 +1,7 @@ # Event devices ## Background + Event devices are generic input devices that are exposed in `/dev/input`. This includes USB peripherals (Keyboards, Controllers, Joysticks or Mouse) as well as potentially bluetooth devices. @@ -23,6 +24,7 @@ modules: ``` And add the following section with the plugin specific configuration: + ``` yaml evdev: enabled: true @@ -49,6 +51,7 @@ devices: # list of devices to listen for on_press: # Currently only the on_press action is supported {rpc_command_definition} # eg `alias: toggle` ``` + The `{device nickname}` is only for your own orientation and can be choosen freely. For each device you need to figure out the `{device_name}` and the `{event_id}` corresponding to key strokes, as indicated in the sections below. @@ -65,7 +68,7 @@ for device in devices: The output could be in the style of: -``` +```text /dev/input/event1 Dell Dell USB Keyboard usb-0000:00:12.1-2/input0 /dev/input/event0 Dell USB Optical Mouse usb-0000:00:12.0-2/input0 ``` @@ -96,8 +99,10 @@ for event in dev.read_loop(): if event.type == ecodes.EV_KEY: print(categorize(event)) ``` + The output could be of the form: -``` + +```text device /dev/input/event1, name "DragonRise Inc. Generic USB Joystick ", phys "usb-3f980000.usb-1.2/input0" key event at 1672569673.124168, 297 (BTN_BASE4), down key event at 1672569673.385170, 297 (BTN_BASE4), up @@ -114,7 +119,6 @@ Look for entries like `No callback registered for button ...`. The RPC command follows the regular RPC command rules as defined in the [following documentation](./rpc-commands.md). - ## Full example config -A complete configuration example for a USB Joystick controller can be found in the [examples](../../resources/default-settings/evdev.example.yaml). \ No newline at end of file +A complete configuration example for a USB Joystick controller can be found in the [examples](../../resources/default-settings/evdev.example.yaml). diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index 7cd321b7a..e12514350 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -3,7 +3,7 @@ ## Install Raspberry Pi OS Lite > [!IMPORTANT] -> All Raspberry Pi models are supported. For sufficient performance, **we recommend Pi 2, 3 or Zero 2** (`ARMv7` models). Because Pi 1 or Zero 1 (`ARMv6` models) have limited resources, they are slower (during installation and start up procedure) and might require a bit more work! Pi 4 and 5 are an excess ;-) +> All Raspberry Pi models are supported. For sufficient performance, **we recommend Pi 2, 3 or Zero 2** (`ARMv7` models). Because Pi 1 or Zero 1 (`ARMv6` models) have limited resources, they are slower (during installation and start up procedure) and might require a bit more work! Pi 4 and 5 are an excess ;-) Before you can install the Phoniebox software, you need to prepare your Raspberry Pi. @@ -27,8 +27,9 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr 8. Confirm the next warning about erasing the SD card with `Yes` 9. Wait for the imaging process to be finished (it'll take a few minutes) - ### Pre-boot preparation + +
In case you forgot to customize the OS settings, follow these instructions after RPi OS has been written to the SD card. @@ -81,8 +82,9 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to ### Pre-install preparation / workarounds #### Network management since Bookworm +
-With Bookworm, network management has changed. Now, "NetworkManager" is used instead of "dhcpcd". +With Bookworm, network management has changed. Now, "NetworkManager" is used instead of "dhcpcd". Both methods are supported during installation, but "NetworkManager" is recommended as it is simpler to set up and use. For Bullseye, this can also be activated, though it requires a manual process before running the installation. @@ -91,32 +93,38 @@ If the settings are changed, your network will reset, and WiFi will not be confi Therefore, make sure you use a wired connection or perform the following steps in a local terminal with a connected monitor and keyboard. Change network config + * run `sudo raspi-config` * select `6 - Advanced Options` * select `AA - Network Config` * select `NetworkManager` If you need Wifi, add the information now + * select `1 - System Options` * select `1 - Wireless LAN` * enter Wifi information +
#### Workaround for 64-bit Kernels (Pi 4 and newer) +
The installation process checks if a 32-bit OS is running, as 64-bit is currently not supported. This check also fails if the kernel is running in 64-bit mode. This is the default for Raspberry Pi models 4 and newer. -To be able to run the installation, you have to switch to the 32-bit mode by modifying the `config.txt` and add/change the line `arm_64bit=0`. +To be able to run the installation, you have to switch to the 32-bit mode by modifying the `config.txt` and add/change the line `arm_64bit=0`. Up to Bullseye, the `config.txt` file is located at `/boot/`. Since Bookworm, the location changed to `/boot/firmware/` ([see here](https://www.raspberrypi.com/documentation/computers/config_txt.html)). Reboot before you proceed.
+ ## Install Phoniebox software Choose a version, run the corresponding install command in your SSH terminal and follow the instructions. + * [Stable Release](#stable-release) * [Pre-Release](#pre-release) * [Development](#development) @@ -127,6 +135,7 @@ After a successful installation, [configure your Phoniebox](configuration.md). > Depending on your hardware, this installation might last around 60 minutes (usually it's faster, 20-30 min). It updates OS packages, installs Phoniebox dependencies and applies settings. Be patient and don't let your computer go to sleep. It might disconnect your SSH connection causing the interruption of the installation process. Consider starting the installation in a terminal multiplexer like 'screen' or 'tmux' to avoid this. ### Stable Release + This will install the latest **stable release** from the *future3/main* branch. ```bash @@ -134,6 +143,7 @@ cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID ``` ### Pre-Release + This will install the latest **pre-release** from the *future3/develop* branch. ```bash @@ -141,6 +151,7 @@ cd; GIT_BRANCH='future3/develop' bash <(wget -qO- https://raw.githubusercontent. ``` ### Development + You can also install a specific branch and/or a fork repository. Update the variables to refer to your desired location. (The URL must not necessarily be updated, unless you have actually updated the file being downloaded.) > [!IMPORTANT] @@ -155,9 +166,9 @@ cd; GIT_USER='MiczFlor' GIT_BRANCH='future3/develop' bash <(wget -qO- https://ra > If you install another branch or from a fork repository, the Web App needs to be built locally. This is part of the installation process. See the the developers [Web App](../developers/webapp.md) documentation for further details. ### Logs + To follow the installation closely, use this command in another terminal. ```bash cd; tail -f INSTALL-.log ``` - diff --git a/documentation/builders/samba.md b/documentation/builders/samba.md index ac9a93bbc..8e486a181 100644 --- a/documentation/builders/samba.md +++ b/documentation/builders/samba.md @@ -4,15 +4,14 @@ To conveniently copy files to your Phoniebox via network `samba` can be configur ## Connect -To access the share open your OS network environment and select your Phoniebox device. +To access the share open your OS network environment and select your Phoniebox device. Alternatively directly access it via url with the file explorer (e.g. Windows `\\`, MacOS `smb://`). See also + * [MacOS](https://support.apple.com/lt-lt/guide/mac-help/mchlp1140/mac) ## User name / Password As login credentials use the same username you used to run the installation with. The password is `raspberry`. You can change the password anytime using the command `sudo smbpasswd -a ""`. - - diff --git a/documentation/builders/troubleshooting.md b/documentation/builders/troubleshooting.md index 5b4061aa8..a18272afb 100644 --- a/documentation/builders/troubleshooting.md +++ b/documentation/builders/troubleshooting.md @@ -47,10 +47,10 @@ The default logging config does 2 things: 1. It writes 2 log files: -```bash -shared/logs/app.log : Complete Debug Messages -shared/logs/errors.log : Only Errors and Warnings -``` + ```bash + shared/logs/app.log : Complete Debug Messages + shared/logs/errors.log : Only Errors and Warnings + ``` 2. Prints logging messages to the console. If run as a service, only error messages are emitted to console to avoid spamming the system log files. diff --git a/documentation/builders/webapp/playlists-livestreams-podcasts.md b/documentation/builders/webapp/playlists-livestreams-podcasts.md index 2b8be79a1..f45969e6e 100644 --- a/documentation/builders/webapp/playlists-livestreams-podcasts.md +++ b/documentation/builders/webapp/playlists-livestreams-podcasts.md @@ -3,11 +3,13 @@ By default, the Jukebox represents music based on its metadata like album name, artist or song name. The hierarchy and order of songs is determined by their original definition, e.g. order of songs within an album. If you prefer a specific list of songs to be played, you can use playlists (files ending with `*.m3u`). Jukebox also supports livestreams and podcasts (if connected to the internet) through playlists. ## Playlists + If you like the Jukebox to play songs in a pre-defined order, you can use .m3u playlists. A .m3u playlist is a plain text file that contains a list of file paths or URLs to multimedia files. Each entry in the playlist represents a single song, and they are listed in the order in which they should be played. ### Structure of a .m3u playlist + A .m3u playlist is a simple text document with each song file listed on a separate line. Each entry is optionally preceded by a comment line that starts with a '#' symbol. The actual file paths or URLs of the media files come after the comment. ### Creating a .m3u playlist @@ -18,7 +20,7 @@ A .m3u playlist is a simple text document with each song file listed on a separa 1. On the following lines, list the file paths or URLs of the media files you want to include in the playlist, one per line. They must refer to true files paths on your Jukebox. They can be relative or absolute paths. 1. Save the file with the .m3u extension, e.g. `my_playlist.m3u`. -``` +```text # Absolute /home//RPi-Jukebox-RFID/shared/audiofolders/Simone Sommerland/Die 30 besten Kindergartenlieder/08 - Pitsch, patsch, Pinguin.mp3 /home//RPi-Jukebox-RFID/shared/audiofolders/Simone Sommerland/Die 30 besten Spiel- Und Bewegungslieder/12 - Das rote Pferd.mp3 @@ -42,7 +44,7 @@ Based on the note above, we suggest to use m3u playlists like this, especially i #### Example folder structure -``` +```text └── audiofolders ├── wake-up-songs │ └── playlist.m3u @@ -74,9 +76,9 @@ In order to play radio livestreams on your Jukebox, you use playlists to registe You can now assign livestreams to cards [following the example](#assigning-a-m3u-playlist-to-a-card) of playlists. -#### Example folder structure and playlist names +#### Example folder structure and playlist names for livestreams -``` +```text └── audiofolders ├── wdr-kids │ └── wdr-kids-livestream.txt @@ -108,13 +110,13 @@ We will explain options 1 and 2 more closely. ### Using podcast.txt playlist in Jukebox 1. [Follow the steps above](#using-m3u-playlists-in-jukebox) to add a playlist to your Jukebox (make sure you have created individual folders). -1. When creating the playlist file, make sure it's called or at least ends with `podcasts.txt` instead of `.m3u`. (Examples: `awesome-podcast.txt`, `podcast.txt`). +1. When creating the playlist file, make sure it's called or at least ends with `podcast.txt` instead of `.m3u`. (Examples: `awesome-podcast.txt`, `podcast.txt`). 1. Add links to your individual podcast episodes just like you would with songs in .m3u playlists 1. As an alternative, you can provide a single RSS feed (XML). Jukebox will expand the file and refer to all episodes listed within this file. -#### Example folder structure and playlist names +#### Example folder structure and playlist names for podcasts -``` +```text └── audiofolders ├── die-maus │ └── die-maus-podcast.txt diff --git a/documentation/developers/README.md b/documentation/developers/README.md index 6addeaff1..ff21a8ceb 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -4,6 +4,7 @@ * [Development Environment](./development-environment.md) * [Python Development Notes](python.md) +* [Documentation (with Markdown)](documentatíon.md) ## Reference diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 19a57e414..9b4db4697 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -49,17 +49,23 @@ They can be run individually or in combination. To do that, we use 1. [Install Docker & Compose (Mac)](https://docs.docker.com/docker-for-mac/install/) 2. Install pulseaudio 1. Use Homebrew to install - ``` - $ brew install pulseaudio - ``` - 2. Enable pulseaudio network capabilities. In an editor, open `/opt/homebrew/Cellar/pulseaudio/16.1/etc/pulse/default.pa` (you might need to adapt this path to your own system settings). Uncomment the following line. - ``` - load-module module-native-protocol-tcp - ``` + + ```bash + $ brew install pulseaudio + ``` + + 2. Enable pulseaudio network capabilities. In an editor, open `/opt/homebrew/Cellar/pulseaudio/16.1/etc/pulse/default.pa` (you might need to adapt this path to your own system settings). Uncomment the following line: + + ```text + load-module module-native-protocol-tcp + ``` + 3. Restart the pulseaudio service - ``` - $ brew services restart pulseaudio - ``` + + ```bash + $ brew services restart pulseaudio + ``` + 4. If you have trouble with your audio, try these resources to troubleshoot: [[1]](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712), [[2]](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html), [[3]](https://stackoverflow.com/a/50939994/1062438) > [!NOTE] @@ -189,7 +195,7 @@ details. If you notice the following exception while running MPD in Docker, it refers to a incorrect setup of your Mac host Pulseaudio. -``` +```text mpd | ALSA lib pulse.c:242:(pulse_connect) PulseAudio: Unable to connect: Connection refused mpd | exception: Failed to read mixer for 'Global ALSA->Pulse stream': failed to attach to pulse: Connection refused ``` @@ -197,15 +203,20 @@ mpd | exception: Failed to read mixer for 'Global ALSA->Pulse stream': fail To fix the issue, try the following. 1. Stop your Pulseaudio service - ``` + + ```bash brew service stop pulseaudio ``` + 2. Start Pulseaudio with this command - ``` + + ```bash pulseaudio --load=module-native-protocol-tcp --exit-idle-time=-1 --daemon ``` + 3. Check if daemon is working - ``` + + ```bash pulseaudio --check -v ``` @@ -213,8 +224,6 @@ Everything else should have been set up properly as a [prerequisite](#mac) * [Source](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712) - - #### Other error messages When starting the `mpd` container, you will see the following errors. @@ -265,12 +274,11 @@ jukebox | 319:server.py - jb.pub.server - host.timer.cputemp If you encounter the following error, refer to [Pulseaudio issues on Mac](#pulseaudio-issue-on-mac). -``` +```text jukebox | 21.12.2023 08:50:09 - 629:plugs.py - jb.plugin - MainThread - ERROR - Ignoring failed package load finalizer: 'volume.finalize()' jukebox | 21.12.2023 08:50:09 - 630:plugs.py - jb.plugin - MainThread - ERROR - Reason: NameError: name 'pulse_control' is not defined ``` - ## Appendix ### Individual Docker Image @@ -291,10 +299,10 @@ $ docker run -it --rm \ --name jukebox jukebox ``` -## Testing EVDEV devices in Linux +## Testing ``evdev`` devices in Linux + To test the [event device capabilities](../builders/event-devices.md) in docker, the device needs to be made available to the container. -### Linux Mount the device into the container by configuring the appropriate device in a `devices` section of the `jukebox` service in the docker compose file. For example: ```yaml @@ -304,9 +312,9 @@ Mount the device into the container by configuring the appropriate device in a ` - /dev/input/event3:/dev/input/event3 ``` - ### Resources + #### Mac * @@ -320,6 +328,8 @@ Mount the device into the container by configuring the appropriate device in a ` * * + + #### Audio * diff --git a/documentation/developers/documentation.md b/documentation/developers/documentation.md new file mode 100644 index 000000000..51cd8cda2 --- /dev/null +++ b/documentation/developers/documentation.md @@ -0,0 +1,39 @@ +# Documentation with Markdown + +We use markdown for documentation. Please add/update documentation in `documentation`. + +## Linting + +To ensure a consistent documentation we lint markdown files. + +We use [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) for linting. + +`.markdownlint-cli2.yaml` configures linting consistently for the Github Action, the pre-commit hook, manual linting and the [markdownlint extension](https://github.com/DavidAnson/vscode-markdownlint) for Visual Studio Code. + +You can start a manual check, if you call `run_markdownlint.sh`. + +If markdown files are changed and the pre-commit hook is enabled, `run_markdownlint.sh` is triggered on commits. + +After creating a PR or pushing to the repo a Github Action triggers the linter, if markdown files are changed (see `.github/workflows/markdown_v3.yml`). + +### Disabling Rules + +> [!NOTE] +> Please use disabling rules with caution and always try to fix the violation first. + +A few rules are globally disabled in `.markdownlint-cli2.yaml` (see section `config`). + +If you want to disable a rule for a specific section of a markdown file you can use + +```markdown + +section where MD010 should be ignored + +``` + +### References + +* +* Rules: + * + * diff --git a/documentation/developers/rfid/README.md b/documentation/developers/rfid/README.md index 0b2df4db3..f0db4fd6a 100644 --- a/documentation/developers/rfid/README.md +++ b/documentation/developers/rfid/README.md @@ -11,4 +11,3 @@ * [Generic Readers without HID (NFCpy)](generic_nfcpy.md) * [Mock Reader](mock_reader.md) * [Template Reader](template_reader.md) - diff --git a/documentation/developers/rfid/generic_nfcpy.md b/documentation/developers/rfid/generic_nfcpy.md index 76de98d88..27c6ececb 100644 --- a/documentation/developers/rfid/generic_nfcpy.md +++ b/documentation/developers/rfid/generic_nfcpy.md @@ -11,13 +11,14 @@ driver, and thus cannot be used with the [genericusb](genericusb.md) module. Als > The setup will do this automatically, so make sure the device is connected > before running the [RFID reader configuration tool](../coreapps.md#RFID-Reader). -# Configuration +## Configuration -The installation script will scan for compatible devices and will assist in configuration. +The installation script will scan for compatible devices and will assist in configuration. By setting `rfid > readers > generic_nfcpy > config > device_path` in `shared/settings/rfid.yaml` you can override the device location. By specifying an explicit device location it is possible to use multiple readers compatible with NFCpy. Example configuration for a usb-device with vendor ID 072f and product ID 2200: + ```yaml rfid: readers: @@ -33,4 +34,4 @@ rfid: alias: pause ``` -For possible values see the `path` parameter in this [nfcpy documentation](https://nfcpy.readthedocs.io/en/latest/modules/clf.html#nfc.clf.ContactlessFrontend.open) \ No newline at end of file +For possible values see the `path` parameter in this [nfcpy documentation](https://nfcpy.readthedocs.io/en/latest/modules/clf.html#nfc.clf.ContactlessFrontend.open) diff --git a/documentation/developers/rfid/mfrc522_spi.md b/documentation/developers/rfid/mfrc522_spi.md index 8a04f729e..363df3836 100644 --- a/documentation/developers/rfid/mfrc522_spi.md +++ b/documentation/developers/rfid/mfrc522_spi.md @@ -57,7 +57,8 @@ If true all card read-outs will be logged, even when card is permanently on read The following pin-out is for the default SPI Bus 0 on Raspberry Pins. -*MFRC522 default wiring (spi_bus=0, spi_ce=0)* +### MFRC522 default wiring (spi_bus=0, spi_ce=0) + |Pin Board Name |Function |RPI GPIO |RPI Pin | |----------------|----------|----------|---------| |SDA |CE |GPIO8 |24 | diff --git a/documentation/developers/rfid/pn532_i2c.md b/documentation/developers/rfid/pn532_i2c.md index d60cb2e54..33b7e3f51 100644 --- a/documentation/developers/rfid/pn532_i2c.md +++ b/documentation/developers/rfid/pn532_i2c.md @@ -29,7 +29,7 @@ You can usually pick up a board at ## Board Connections -*Default wiring* +### Default wiring | PN532 | RPI GPIO | RPI Pin | |-------|--------------|---------| @@ -45,9 +45,9 @@ PI's own voltage regulator. ## Jumpers -*Jumper settings for I2C protocol* +### Jumper settings for I2C protocol -Jumper | Position --------|---------- -SEL0 | ON -SEL1 | OFF +| Jumper | Position | +|--------|----------| +|SEL0 | ON | +|SEL1 | OFF | diff --git a/documentation/developers/rfid/template_reader.md b/documentation/developers/rfid/template_reader.md index 5b8458691..e02f56c69 100644 --- a/documentation/developers/rfid/template_reader.md +++ b/documentation/developers/rfid/template_reader.md @@ -1,9 +1,8 @@ # Template Reader -*Template for creating and integrating a new RFID Reader* - > [!NOTE] +> Template for creating and integrating a new RFID Reader. > For developers only This template provides the skeleton API for a new Reader. If you follow diff --git a/documentation/developers/webapp.md b/documentation/developers/webapp.md index 2e8504337..0406566fa 100644 --- a/documentation/developers/webapp.md +++ b/documentation/developers/webapp.md @@ -19,7 +19,7 @@ sudo apt-get -y update && sudo apt-get -y install nodejs The Web App is a React application based on [Create React App](https://create-react-app.dev/). To start a development server, run the following command: -``` +```bash cd ~/RPi-Jukebox-RFID/src/webapp npm install # Just the first time or when dependencies change npm start @@ -37,12 +37,14 @@ cd ~/RPi-Jukebox-RFID/src/webapp; \ After a successfull build you might need to restart the web server. -``` +```bash sudo systemctl restart nginx.service ``` ## Known Issues while building + + ### JavaScript heap out of memory While (re-) building the Web App, you get the following output: @@ -71,6 +73,7 @@ Use the [provided script](#build-the-web-app) to rebuild the Web App. It sets th If you need to run the commands manually, make sure to have enough memory available (min. 512 MB). The following commands might help. Set the swapsize to 512 MB (and deactivate swapfactor). Adapt accordingly if you have a SD Card with small capacity. + ```bash sudo dphys-swapfile swapoff sudo sed -i "s|.*CONF_SWAPSIZE=.*|CONF_SWAPSIZE=512|g" /etc/dphys-swapfile @@ -80,6 +83,7 @@ sudo dphys-swapfile swapon ``` Set Node's maximum amount of memory. Memory must be available. + ``` bash export NODE_OPTIONS=--max-old-space-size=512 npm run build @@ -105,7 +109,6 @@ Node tried to allocate more memory than available on the system. See [JavaScript heap out of memory](#javascript-heap-out-of-memory) - ### Client network socket disconnected ``` {.bash emphasize-lines="8,9"} @@ -122,12 +125,12 @@ npm ERR! network 'proxy' config is set properly. See: 'npm help config' #### Reason -The network connection is too slow or has issues. -This tends to happen on `armv6l` devices where building takes significantly more time due to limited resources. +The network connection is too slow or has issues. +This tends to happen on `armv6l` devices where building takes significantly more time due to limited resources. #### Solution -Try to use an ethernet connection. A reboot and/or running the script multiple times might also help ([Build produces EOF errors](#build-produces-eof-errors) might occur). +Try to use an ethernet connection. A reboot and/or running the script multiple times might also help ([Build produces EOF errors](#build-produces-eof-errors) might occur). If the error still persists, try to raise the timeout for npm package resolution. @@ -144,6 +147,8 @@ A previous run failed during installation and left a package corrupted. #### Solution Remove the mode packages and rerun again the script. + ``` {.bash emphasize-lines="8,9"} rm -rf node_modules ``` + diff --git a/run_markdownlint.sh b/run_markdownlint.sh new file mode 100755 index 000000000..0f890590e --- /dev/null +++ b/run_markdownlint.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Runner script to ensure +# - independent from working directory + +# Change working directory to project root +SOURCE=${BASH_SOURCE[0]} +SCRIPT_DIR="$(dirname "$SOURCE")" +PROJECT_ROOT="$SCRIPT_DIR" +cd "$PROJECT_ROOT" || { echo "Could not change directory"; exit 1; } + +# Run markdownlint-cli2 +./src/webapp/node_modules/.bin/markdownlint-cli2 --config .markdownlint-cli2.yaml "#node_modules" || { echo "ERROR: markdownlint-cli2 not found"; exit 1; } diff --git a/src/webapp/package-lock.json b/src/webapp/package-lock.json index 814090555..218e67ab7 100644 --- a/src/webapp/package-lock.json +++ b/src/webapp/package-lock.json @@ -28,6 +28,9 @@ "react-scripts": "^5.0.1", "url": "^0.11.3", "uuid": "^9.0.1" + }, + "devDependencies": { + "markdownlint-cli2": "^0.12.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -4042,6 +4045,18 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -12284,6 +12299,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -12525,6 +12546,15 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -12663,11 +12693,165 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", + "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.0.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdownlint": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.33.0.tgz", + "integrity": "sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==", + "dev": true, + "dependencies": { + "markdown-it": "14.0.0", + "markdownlint-micromark": "0.1.8" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.12.1.tgz", + "integrity": "sha512-RcK+l5FjJEyrU3REhrThiEUXNK89dLYNJCYbvOUKypxqIGfkcgpz8g08EKqhrmUbYfYoLC5nEYQy53NhJSEtfQ==", + "dev": true, + "dependencies": { + "globby": "14.0.0", + "jsonc-parser": "3.2.0", + "markdownlint": "0.33.0", + "markdownlint-cli2-formatter-default": "0.0.4", + "micromatch": "4.0.5", + "yaml": "2.3.4" + }, + "bin": { + "markdownlint-cli2": "markdownlint-cli2.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2-formatter-default": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.4.tgz", + "integrity": "sha512-xm2rM0E+sWgjpPn1EesPXx5hIyrN2ddUnUwnbCsD/ONxYtw3PX6LydvdH6dciWAoFDpwzbHM1TO7uHfcMd6IYg==", + "dev": true, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" + } + }, + "node_modules/markdownlint-cli2/node_modules/globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdownlint-cli2/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdownlint-cli2/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdownlint-cli2/node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/markdownlint-micromark": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.8.tgz", + "integrity": "sha512-1ouYkMRo9/6gou9gObuMDnvZM8jC/ly3QCFQyoSPCS2XV1ZClU0xpKbL1Ar3bWWRT1RnBZkWUEiNKrI2CwiBQA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -14889,6 +15073,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -17220,6 +17413,12 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -17280,6 +17479,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", diff --git a/src/webapp/package.json b/src/webapp/package.json index 115b66852..1619a8c60 100644 --- a/src/webapp/package.json +++ b/src/webapp/package.json @@ -47,5 +47,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "markdownlint-cli2": "^0.12.1" } } From de40661897c93bc9f5ae2d3199ead4781c99bfe2 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Fri, 22 Mar 2024 09:46:46 +0100 Subject: [PATCH 101/121] docs: Manually upgrade to the latest version (#2300) * docs: Manually upgrade to the latest version * Fix typo * Fix Typos * Solve last issue * fix: Align with comments * fix: indentation was off * fix: markdown lint * fix: one small bugfix * fix: remove last linting issues * fix: adding info about missing upgrade feature --- documentation/builders/update.md | 94 ++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/documentation/builders/update.md b/documentation/builders/update.md index cb919492a..6a9004625 100644 --- a/documentation/builders/update.md +++ b/documentation/builders/update.md @@ -12,6 +12,100 @@ Restore your old files after the new installation was successful and check if ne $ diff shared/settings/jukebox.yaml resources/default-settings/jukebox.default.yaml ``` +## Manually upgrade to the latest version + +> [!CAUTION] +> This documentation is only recommended for users running on `future3/develop` branch. For optimal system updates, it is strongly recommended to utilize the upgrade feature when transitioning to the next version (The Upgrade Feature will come in the future [#2304](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2304)). Manual updates may necessitate specific migration steps and, if overlooked, could result in system failure. Please use these steps with caution. + +If you only want to update a few recent commits, this following explanation outlines the steps to do so + +Typically, 4 steps need to be considered + +1. Backup Local Changes (Optional) +1. Pull the latest version from Github +1. Replace the Web App with the most recent build +1. Optional: Update the config files + +### Fetch the most recent version from Github + +First, SSH into your Phoniebox. + +```bash +cd ~/RPi-Jukebox-RFID/ +``` + +Second, get the latest version from Github. Depending on your proficiency with Git, you can also checkout a specific branch or version. +Be aware, in case you have made changes to the software, stash them to keep them safe. + +1. Backup Local Changes (Optional): + - Stash your local changes: + + ```bash + git stash push -m "Backup before pull" + ``` + + - Create a Backup Branch (and potentially delete it in case it already exists): + + ```bash + git branch -D backup-before-pull + git branch backup-before-pull + ``` + +1. Pull Latest Changes: + + ```bash + git pull + ``` + +1. Update Web App: + 1. Backup the current webapp build + + ```bash + cd ~/RPi-Jukebox-RFID/src/webapp + rm -rf build-backup + mv build build-backup + ``` + + 1. Go to the [Github Release page](https://github.com/MiczFlor/RPi-Jukebox-RFID/releases) find the latest `Pre-release` release (typically Alpha). + 1. Under "Assets", find the latest Web App release called "webapp-build-latest.tar.gz" and copy the URL. + 1. On your Phoniebox, download the file and extract the archive. Afterwards, delete the archive + + ```bash + wget {URL} + tar -xzf webapp-build-latest.tar.gz + rm -rf webapp-build-latest.tar.gz + ``` + +1. Reboot the Phoniebox: + + ```bash + sudo reboot + ``` + +1. Verify the version of your Phoniebox in the settings tab. + +Revert to Backup If Needed: + +- Checkout the backup branch: + + ```bash + git checkout backup-before-pull + ``` + +- Reapply stashed changes (if any): + + ```bash + git stash pop + ``` + +- Revert Web App: + + ```bash + cd ~/RPi-Jukebox-RFID/src/webapp + rm -rf build + mv build-backup build + ``` + ## Migration Path from Version 2 There is no update path coming from Version 2.x of the Jukebox. From c319ca9ed1d452b73a10d262e92088aff38f62d1 Mon Sep 17 00:00:00 2001 From: s-martin Date: Wed, 27 Mar 2024 21:03:25 +0100 Subject: [PATCH 102/121] Customize markdownlint (#2310) * Customize markdownlint * Remove ignore in file * Remove ignore in file --- .markdownlint-cli2.yaml | 5 +++++ documentation/builders/installation.md | 2 -- documentation/developers/webapp.md | 3 --- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml index 2fd1409d8..88c67e576 100644 --- a/.markdownlint-cli2.yaml +++ b/.markdownlint-cli2.yaml @@ -8,6 +8,11 @@ config: # ignore dollar signs commands-show-output: false no-trailing-punctuation: false + no-duplicate-heading: + siblings_only: true + # allow some tags we use for formatting + no-inline-html: + allowed_elements: [ "details", "summary" ] # Include a custom rule package #customRules: diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index e12514350..2228ee47f 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -29,7 +29,6 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr ### Pre-boot preparation -
In case you forgot to customize the OS settings, follow these instructions after RPi OS has been written to the SD card. @@ -119,7 +118,6 @@ Up to Bullseye, the `config.txt` file is located at `/boot/`. Since Bookworm, th Reboot before you proceed.
- ## Install Phoniebox software diff --git a/documentation/developers/webapp.md b/documentation/developers/webapp.md index 0406566fa..30b791816 100644 --- a/documentation/developers/webapp.md +++ b/documentation/developers/webapp.md @@ -43,8 +43,6 @@ sudo systemctl restart nginx.service ## Known Issues while building - - ### JavaScript heap out of memory While (re-) building the Web App, you get the following output: @@ -151,4 +149,3 @@ Remove the mode packages and rerun again the script. ``` {.bash emphasize-lines="8,9"} rm -rf node_modules ``` - From 62ebd27be077f79eeae5c9731e92839f7110b5f0 Mon Sep 17 00:00:00 2001 From: s-martin Date: Sat, 30 Mar 2024 19:09:45 +0100 Subject: [PATCH 103/121] Update CodeQL to v3 (#2312) * use codeql v3 * make sure there's always an update --- .github/workflows/codeql-analysis_v3.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis_v3.yml b/.github/workflows/codeql-analysis_v3.yml index 89693df2a..d06cce1a5 100644 --- a/.github/workflows/codeql-analysis_v3.yml +++ b/.github/workflows/codeql-analysis_v3.yml @@ -44,6 +44,7 @@ jobs: - name: Install dependencies run: | # Install necessary packages + sudo apt-get update sudo apt-get install libasound2-dev pulseaudio python3 -m venv .venv source ".venv/bin/activate" @@ -56,7 +57,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +69,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -82,4 +83,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 27b23cbf2590cb6058753a68f1b4fcafc622dcf2 Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sat, 6 Apr 2024 09:49:40 +0200 Subject: [PATCH 104/121] fix: Build libzmq locally to work on all host types (#2307) * Externalize libzmq build * Update documentation * Use dedicated build directory for libzmq * improve: update make command to be more efficient * Update docker/Dockerfile.libzmq Co-authored-by: notapirate * fix: Remove unneccesary MD linting rule * fix: Remove another markdown linter * Remove uncessary file * refactor: Rename docker.pulse.mpd.conf --------- Co-authored-by: notapirate --- ...{jukebox.Dockerfile => Dockerfile.jukebox} | 25 +- docker/Dockerfile.libzmq | 25 ++ docker/{mpd.Dockerfile => Dockerfile.mpd} | 0 .../{webapp.Dockerfile => Dockerfile.webapp} | 0 docker/config/docker.mpd.conf | 25 +- docker/config/docker.pulse.mpd.conf | 412 ------------------ docker/docker-compose.linux.yml | 4 +- docker/docker-compose.yml | 10 +- documentation/developers/docker.md | 195 ++++++--- installation/routines/setup_jukebox_core.sh | 2 +- 10 files changed, 183 insertions(+), 515 deletions(-) rename docker/{jukebox.Dockerfile => Dockerfile.jukebox} (67%) create mode 100644 docker/Dockerfile.libzmq rename docker/{mpd.Dockerfile => Dockerfile.mpd} (100%) rename docker/{webapp.Dockerfile => Dockerfile.webapp} (100%) delete mode 100644 docker/config/docker.pulse.mpd.conf diff --git a/docker/jukebox.Dockerfile b/docker/Dockerfile.jukebox similarity index 67% rename from docker/jukebox.Dockerfile rename to docker/Dockerfile.jukebox index 0936f5d46..3d63a4a02 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/Dockerfile.jukebox @@ -1,3 +1,4 @@ +FROM libzmq:local as libzmq FROM debian:bullseye-slim # These are only dependencies that are required to get as close to the @@ -6,8 +7,7 @@ RUN apt-get update && apt-get install -y \ libasound2-dev \ pulseaudio \ pulseaudio-utils \ - --no-install-recommends \ - && rm -rf /var/lib/apt/lists/* + --no-install-recommends ARG UID ARG USER @@ -21,7 +21,7 @@ RUN usermod -aG pulse ${USER} # Install all Jukebox dependencies RUN apt-get update && apt-get install -qq -y \ --allow-downgrades --allow-remove-essential --allow-change-held-packages \ - g++ at wget \ + build-essential at wget \ espeak mpc mpg123 git ffmpeg spi-tools netcat \ python3 python3-venv python3-dev python3-mutagen @@ -37,21 +37,14 @@ ENV VIRTUAL_ENV=${INSTALLATION_PATH}/.venv RUN python3 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" - +# Install all Python dependencies RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt -ENV ZMQ_TMP_DIR libzmq -ENV ZMQ_VERSION 4.3.5 -ENV ZMQ_PREFIX /usr/local - -RUN [ "$(uname -m)" = "aarch64" ] && ARCH="arm64" || ARCH="$(uname -m)"; \ - wget https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ - tar -xzf libzmq.tar.gz -C ${ZMQ_PREFIX}; \ - rm -f libzmq.tar.gz; - -RUN export ZMQ_PREFIX=${PREFIX} && export ZMQ_DRAFT_API=1 -RUN pip install -v --no-binary pyzmq pyzmq +# Install pyzmq Python dependency separately +ENV ZMQ_PREFIX /opt/libzmq +ENV ZMQ_DRAFT_API 1 +COPY --from=libzmq ${ZMQ_PREFIX} ${ZMQ_PREFIX} +RUN pip install -v pyzmq --no-binary pyzmq EXPOSE 5555 5556 - WORKDIR ${INSTALLATION_PATH}/src/jukebox diff --git a/docker/Dockerfile.libzmq b/docker/Dockerfile.libzmq new file mode 100644 index 000000000..78cf52368 --- /dev/null +++ b/docker/Dockerfile.libzmq @@ -0,0 +1,25 @@ +FROM debian:bullseye-slim + +# Install necessary build dependencies +RUN apt-get update && apt-get install -y \ + build-essential wget tar + +# Define environment variables for libzmq +ENV ZMQ_VERSION 4.3.5 +ENV ZMQ_PREFIX /opt/libzmq + +# Download, compile, and install libzmq +RUN mkdir -p ${ZMQ_PREFIX}; \ + wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ + tar -xzf libzmq.tar.gz; \ + cd zeromq-${ZMQ_VERSION}; \ + ./configure --prefix=${ZMQ_PREFIX} --enable-drafts; \ + make -j$(nproc) && make install + +# Cleanup unnecessary files +RUN rm -rf /zeromq-${ZMQ_VERSION} libzmq.tar.gz + +# Create final image with only the libzmq build fragments +FROM scratch +ENV ZMQ_PREFIX /opt/libzmq +COPY --from=0 ${ZMQ_PREFIX} ${ZMQ_PREFIX} diff --git a/docker/mpd.Dockerfile b/docker/Dockerfile.mpd similarity index 100% rename from docker/mpd.Dockerfile rename to docker/Dockerfile.mpd diff --git a/docker/webapp.Dockerfile b/docker/Dockerfile.webapp similarity index 100% rename from docker/webapp.Dockerfile rename to docker/Dockerfile.webapp diff --git a/docker/config/docker.mpd.conf b/docker/config/docker.mpd.conf index 4ec64890e..ad7713d0d 100644 --- a/docker/config/docker.mpd.conf +++ b/docker/config/docker.mpd.conf @@ -11,7 +11,7 @@ # be disabled and audio files will only be accepted over ipc socket (using # file:// protocol) or streaming files over an accepted protocol. # -music_directory "/home/pi/RPi-Jukebox-RFID/shared/audiofolders" +music_directory "~/RPi-Jukebox-RFID/shared/audiofolders" # # This setting sets the MPD internal playlist directory. The purpose of this # directory is storage for playlists created by MPD. The server will use @@ -67,7 +67,7 @@ sticker_file "~/.config/mpd/sticker.sql" # initialization. This setting is disabled by default and MPD is run as the # current user. # -user "root" +# user "root" # # This setting specifies the group that MPD will run as. If not specified # primary group of user specified with "user" setting will be used (if set). @@ -225,6 +225,10 @@ decoder { # gapless "no" } +decoder { + plugin "wildmidi" + enabled "no" +} # ############################################################################### @@ -239,12 +243,11 @@ decoder { # audio_output { type "alsa" - name "My ALSA Device" -# device "pulse" # optional - mixer_type "software" # optional -# mixer_device "default" # optional -# mixer_control "Master" # optional -# mixer_index "0" # optional + name "Global ALSA->Pulse stream" +# mixer_type "hardware" + mixer_control "Master" + mixer_device "pulse" + device "pulse" } # # An example of an OSS output: @@ -311,9 +314,9 @@ audio_output { # Please see README.Debian if you want mpd to play through the pulseaudio # daemon started as part of your graphical desktop session! # -# audio_output { - # type "pulse" - # name "My Pulse Output" +#audio_output { +# type "pulse" +# name "My Pulse Output" # server "remote_server" # optional # sink "remote_server_sink" # optional # } diff --git a/docker/config/docker.pulse.mpd.conf b/docker/config/docker.pulse.mpd.conf deleted file mode 100644 index ad7713d0d..000000000 --- a/docker/config/docker.pulse.mpd.conf +++ /dev/null @@ -1,412 +0,0 @@ -# An example configuration file for MPD. -# Read the user manual for documentation: http://www.musicpd.org/doc/user/ -# or /usr/share/doc/mpd/html/user.html - - -# Files and directories ####################################################### -# -# This setting controls the top directory which MPD will search to discover the -# available audio files and add them to the daemon's online database. This -# setting defaults to the XDG directory, otherwise the music directory will be -# be disabled and audio files will only be accepted over ipc socket (using -# file:// protocol) or streaming files over an accepted protocol. -# -music_directory "~/RPi-Jukebox-RFID/shared/audiofolders" -# -# This setting sets the MPD internal playlist directory. The purpose of this -# directory is storage for playlists created by MPD. The server will use -# playlist files not created by the server but only if they are in the MPD -# format. This setting defaults to playlist saving being disabled. -# -# playlists are inside the Phoniebox path: -playlist_directory "~/.config/mpd/playlists" -# -# This setting sets the location of the MPD database. This file is used to -# load the database at server start up and store the database while the -# server is not up. This setting defaults to disabled which will allow -# MPD to accept files over ipc socket (using file:// protocol) or streaming -# files over an accepted protocol. -# -db_file "~/.config/mpd/database" -# -# These settings are the locations for the daemon log files for the daemon. -# These logs are great for troubleshooting, depending on your log_level -# settings. -# -# The special value "syslog" makes MPD use the local syslog daemon. This -# setting defaults to logging to syslog, or to journal if mpd was started as -# a systemd service. -# -log_file "~/.config/mpd/log" -# -# This setting sets the location of the file which stores the process ID -# for use of mpd --kill and some init scripts. This setting is disabled by -# default and the pid file will not be stored. -# -pid_file "~/.config/mpd/pid" -# -# This setting sets the location of the file which contains information about -# most variables to get MPD back into the same general shape it was in before -# it was brought down. This setting is disabled by default and the server -# state will be reset on server start up. -# -state_file "~/.config/mpd/state" -# -# The location of the sticker database. This is a database which -# manages dynamic information attached to songs. -# -sticker_file "~/.config/mpd/sticker.sql" -# -############################################################################### - - -# General music daemon options ################################################ -# -# This setting specifies the user that MPD will run as. MPD should never run as -# root and you may use this setting to make MPD change its user ID after -# initialization. This setting is disabled by default and MPD is run as the -# current user. -# -# user "root" -# -# This setting specifies the group that MPD will run as. If not specified -# primary group of user specified with "user" setting will be used (if set). -# This is useful if MPD needs to be a member of group such as "audio" to -# have permission to use sound card. -# -#group "nogroup" -# -# This setting sets the address for the daemon to listen on. Careful attention -# should be paid if this is assigned to anything other then the default, any. -# This setting can deny access to control of the daemon. Choose any if you want -# to have mpd listen on every address. Not effective if systemd socket -# activation is in use. -# -# For network -bind_to_address "any" -# -# And for Unix Socket -#bind_to_address "/run/mpd/socket" -# -# This setting is the TCP port that is desired for the daemon to get assigned -# to. -# -port "6600" -# -# This setting controls the type of information which is logged. Available -# setting arguments are "default", "secure" or "verbose". The "verbose" setting -# argument is recommended for troubleshooting, though can quickly stretch -# available resources on limited hardware storage. -# -log_level "default" -# -# Setting "restore_paused" to "yes" puts MPD into pause mode instead -# of starting playback after startup. -# -#restore_paused "no" -# -# This setting enables MPD to create playlists in a format usable by other -# music players. -# -#save_absolute_paths_in_playlists "no" -# -# This setting defines a list of tag types that will be extracted during the -# audio file discovery process. The complete list of possible values can be -# found in the user manual. -#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc" -# -# This example just enables the "comment" tag without disabling all -# the other supported tags: -#metadata_to_use "+comment" -# -# This setting enables automatic update of MPD's database when files in -# music_directory are changed. -# -auto_update "yes" -# -# Limit the depth of the directories being watched, 0 means only watch -# the music directory itself. There is no limit by default. -# -auto_update_depth "10" -# -############################################################################### - - -# Symbolic link behavior ###################################################### -# -# If this setting is set to "yes", MPD will discover audio files by following -# symbolic links outside of the configured music_directory. -# -#follow_outside_symlinks "yes" -# -# If this setting is set to "yes", MPD will discover audio files by following -# symbolic links inside of the configured music_directory. -# -#follow_inside_symlinks "yes" -# -############################################################################### - - -# Zeroconf / Avahi Service Discovery ########################################## -# -# If this setting is set to "yes", service information will be published with -# Zeroconf / Avahi. -# -#zeroconf_enabled "yes" -# -# The argument to this setting will be the Zeroconf / Avahi unique name for -# this MPD server on the network. %h will be replaced with the hostname. -# -#zeroconf_name "Music Player @ %h" -# -############################################################################### - - -# Permissions ################################################################# -# -# If this setting is set, MPD will require password authorization. The password -# setting can be specified multiple times for different password profiles. -# -#password "password@read,add,control,admin" -# -# This setting specifies the permissions a user has who has not yet logged in. -# -#default_permissions "read,add,control,admin" -# -############################################################################### - - -# Database ####################################################################### -# - -#database { -# plugin "proxy" -# host "other.mpd.host" -# port "6600" -#} - -# Input ####################################################################### -# - -input { - plugin "curl" -# proxy "proxy.isp.com:8080" -# proxy_user "user" -# proxy_password "password" -} - -# QOBUZ input plugin -input { - enabled "no" - plugin "qobuz" -# app_id "ID" -# app_secret "SECRET" -# username "USERNAME" -# password "PASSWORD" -# format_id "N" -} - -# TIDAL input plugin -input { - enabled "no" - plugin "tidal" -# token "TOKEN" -# username "USERNAME" -# password "PASSWORD" -# audioquality "Q" -} - -# Decoder ##################################################################### -# - -decoder { - plugin "hybrid_dsd" - enabled "no" -# gapless "no" -} - -decoder { - plugin "wildmidi" - enabled "no" -} -# -############################################################################### - -# Audio Output ################################################################ -# -# MPD supports various audio output types, as well as playing through multiple -# audio outputs at the same time, through multiple audio_output settings -# blocks. Setting this block is optional, though the server will only attempt -# autodetection for one sound card. -# -# An example of an ALSA output: -# -audio_output { - type "alsa" - name "Global ALSA->Pulse stream" -# mixer_type "hardware" - mixer_control "Master" - mixer_device "pulse" - device "pulse" -} -# -# An example of an OSS output: -# -#audio_output { -# type "oss" -# name "My OSS Device" -# device "/dev/dsp" # optional -# mixer_type "hardware" # optional -# mixer_device "/dev/mixer" # optional -# mixer_control "PCM" # optional -#} -# -# An example of a shout output (for streaming to Icecast): -# -#audio_output { -# type "shout" -# encoder "vorbis" # optional -# name "My Shout Stream" -# host "localhost" -# port "8000" -# mount "/mpd.ogg" -# password "hackme" -# quality "5.0" -# bitrate "128" -# format "44100:16:1" -# protocol "icecast2" # optional -# user "source" # optional -# description "My Stream Description" # optional -# url "http://example.com" # optional -# genre "jazz" # optional -# public "no" # optional -# timeout "2" # optional -# mixer_type "software" # optional -#} -# -# An example of a recorder output: -# -#audio_output { -# type "recorder" -# name "My recorder" -# encoder "vorbis" # optional, vorbis or lame -# path "/var/lib/mpd/recorder/mpd.ogg" -## quality "5.0" # do not define if bitrate is defined -# bitrate "128" # do not define if quality is defined -# format "44100:16:1" -#} -# -# An example of a httpd output (built-in HTTP streaming server): -# -#audio_output { -# type "httpd" -# name "My HTTP Stream" -# encoder "vorbis" # optional, vorbis or lame -# port "8000" -# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6 -# quality "5.0" # do not define if bitrate is defined -# bitrate "128" # do not define if quality is defined -# format "44100:16:1" -# max_clients "0" # optional 0=no limit -#} -# -# An example of a pulseaudio output (streaming to a remote pulseaudio server) -# Please see README.Debian if you want mpd to play through the pulseaudio -# daemon started as part of your graphical desktop session! -# -#audio_output { -# type "pulse" -# name "My Pulse Output" -# server "remote_server" # optional -# sink "remote_server_sink" # optional -# } -# -# An example of a winmm output (Windows multimedia API). -# -#audio_output { -# type "winmm" -# name "My WinMM output" -# device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional -# or -# device "0" # optional -# mixer_type "hardware" # optional -#} -# -# An example of an openal output. -# -#audio_output { -# type "openal" -# name "My OpenAL output" -# device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional -#} -# -## Example "pipe" output: -# -#audio_output { -# type "pipe" -# name "my pipe" -# command "aplay -f cd 2>/dev/null" -## Or if you're want to use AudioCompress -# command "AudioCompress -m | aplay -f cd 2>/dev/null" -## Or to send raw PCM stream through PCM: -# command "nc example.org 8765" -# format "44100:16:2" -#} -# -## An example of a null output (for no audio output): -# -#audio_output { -# type "null" -# name "My Null Output" -# mixer_type "none" # optional -#} -# -############################################################################### - - -# Normalization automatic volume adjustments ################################## -# -# This setting specifies the type of ReplayGain to use. This setting can have -# the argument "off", "album", "track" or "auto". "auto" is a special mode that -# chooses between "track" and "album" depending on the current state of -# random playback. If random playback is enabled then "track" mode is used. -# See for more details about ReplayGain. -# This setting is off by default. -# -#replaygain "album" -# -# This setting sets the pre-amp used for files that have ReplayGain tags. By -# default this setting is disabled. -# -#replaygain_preamp "0" -# -# This setting sets the pre-amp used for files that do NOT have ReplayGain tags. -# By default this setting is disabled. -# -#replaygain_missing_preamp "0" -# -# This setting enables or disables ReplayGain limiting. -# MPD calculates actual amplification based on the ReplayGain tags -# and replaygain_preamp / replaygain_missing_preamp setting. -# If replaygain_limit is enabled MPD will never amplify audio signal -# above its original level. If replaygain_limit is disabled such amplification -# might occur. By default this setting is enabled. -# -#replaygain_limit "yes" -# -# This setting enables on-the-fly normalization volume adjustment. This will -# result in the volume of all playing audio to be adjusted so the output has -# equal "loudness". This setting is disabled by default. -# -volume_normalization "yes" -# -############################################################################### - -# Character Encoding ########################################################## -# -# If file or directory names do not display correctly for your locale then you -# may need to modify this setting. -# -filesystem_charset "UTF-8" -# -############################################################################### diff --git a/docker/docker-compose.linux.yml b/docker/docker-compose.linux.yml index 9609dc18d..6285484f8 100755 --- a/docker/docker-compose.linux.yml +++ b/docker/docker-compose.linux.yml @@ -12,7 +12,7 @@ services: volumes: - ../shared/audiofolders:/home/pi/RPi-Jukebox-RFID/shared/audiofolders - ../shared/playlists:/home/pi/.config/mpd/playlists - - ./config/docker.pulse.mpd.conf:/home/pi/.config/mpd/mpd.conf + - ./config/docker.mpd.conf:/home/pi/.config/mpd/mpd.conf - $XDG_RUNTIME_DIR/pulse/native:/tmp/pulse-sock jukebox: @@ -25,5 +25,5 @@ services: - PULSE_SERVER=unix:/tmp/pulse-sock volumes: - ../shared:/home/pi/RPi-Jukebox-RFID/shared - - ./config/docker.pulse.mpd.conf:/home/pi/.config/mpd/mpd.conf + - ./config/docker.mpd.conf:/home/pi/.config/mpd/mpd.conf - $XDG_RUNTIME_DIR/pulse/native:/tmp/pulse-sock diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 03191dbd5..38a112551 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -8,7 +8,7 @@ services: - USER=root - HOME=/root context: ../ - dockerfile: ./docker/mpd.Dockerfile + dockerfile: ./docker/Dockerfile.mpd container_name: mpd image: phoniebox/mpd environment: @@ -17,7 +17,7 @@ services: volumes: - ../shared/audiofolders:/root/RPi-Jukebox-RFID/shared/audiofolders - ../shared/playlists:/root/.config/mpd/playlists - - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf + - ./config/docker.mpd.conf:/root/.config/mpd/mpd.conf jukebox: build: @@ -26,7 +26,7 @@ services: - USER=root - HOME=/root context: ../ - dockerfile: ./docker/jukebox.Dockerfile + dockerfile: ./docker/Dockerfile.jukebox container_name: jukebox image: phoniebox/jukebox depends_on: @@ -43,13 +43,13 @@ services: - ../src/jukebox:/root/RPi-Jukebox-RFID/src/jukebox - ../src/webapp/public/cover-cache:/root/RPi-Jukebox-RFID/src/webapp/build/cover-cache - ../shared:/root/RPi-Jukebox-RFID/shared - - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf + - ./config/docker.mpd.conf:/root/.config/mpd/mpd.conf command: python run_jukebox.py webapp: build: context: ../ - dockerfile: ./docker/webapp.Dockerfile + dockerfile: ./docker/Dockerfile.webapp container_name: webapp image: phoniebox/webapp depends_on: diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 9b4db4697..74d95c112 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -20,14 +20,14 @@ need to adapt some of those commands to your needs. 2. Pull the Jukebox repository: ```bash - $ git clone https://github.com/MiczFlor/RPi-Jukebox-RFID.git + git clone https://github.com/MiczFlor/RPi-Jukebox-RFID.git ``` 3. Create a jukebox.yaml file * Copy the `./resources/default-settings/jukebox.default.yaml` to `./shared/settings` and rename the file to `jukebox.yaml`. ```bash - $ cp ./resources/default-settings/jukebox.default.yaml ./shared/settings/jukebox.yaml + cp ./resources/default-settings/jukebox.default.yaml ./shared/settings/jukebox.yaml ``` * Override/Merge the values from the following [Override file](../../docker/config/jukebox.overrides.yaml) in your `jukebox.yaml`. @@ -39,127 +39,168 @@ need to adapt some of those commands to your needs. ## Run development environment -In contrary to how everything is set up on the Raspberry Pi, it\'s good +In contrary to how everything is set up on the Raspberry Pi, it's good practice to isolate different components in different Docker images. They can be run individually or in combination. To do that, we use `docker-compose`. ### Mac +
+ +See details + 1. [Install Docker & Compose (Mac)](https://docs.docker.com/docker-for-mac/install/) -2. Install pulseaudio +1. Install pulseaudio 1. Use Homebrew to install ```bash - $ brew install pulseaudio + brew install pulseaudio ``` - 2. Enable pulseaudio network capabilities. In an editor, open `/opt/homebrew/Cellar/pulseaudio/16.1/etc/pulse/default.pa` (you might need to adapt this path to your own system settings). Uncomment the following line: + 1. Enable pulseaudio network capabilities. In an editor, open `/opt/homebrew/Cellar/pulseaudio/16.1/etc/pulse/default.pa` (you might need to adapt this path to your own system settings). Uncomment the following line: ```text load-module module-native-protocol-tcp ``` - 3. Restart the pulseaudio service + 1. Restart the pulseaudio service ```bash - $ brew services restart pulseaudio + brew services restart pulseaudio ``` - 4. If you have trouble with your audio, try these resources to troubleshoot: [[1]](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712), [[2]](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html), [[3]](https://stackoverflow.com/a/50939994/1062438) + 1. If you have trouble with your audio, try these resources to troubleshoot: [[1]](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712), [[2]](https://devops.datenkollektiv.de/running-a-docker-soundbox-on-mac.html), [[3]](https://stackoverflow.com/a/50939994/1062438) -> [!NOTE] -> In order for Pulseaudio to work properly with Docker on your Mac, you need to start Pulseaudio in a specific way. Otherwise MPD will throw an exception. See [Pulseaudio issues on Mac](#pulseaudio-issue-on-mac) for more info. +1. Run `docker-compose` -``` bash -// Build Images -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build + > [!NOTE] + > In order for Pulseaudio to work properly with Docker on your Mac, you need to start Pulseaudio in a specific way. Otherwise MPD will throw an exception. See [Pulseaudio issues on Mac](#pulseaudio-issue-on-mac) for more info. -// Run Docker Environment -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml up + 1. Build libzmq for your host machine -// Shuts down Docker containers and Docker network -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml down -``` + ```bash + docker build -f docker/Dockerfile.libzmq -t libzmq:local . + ``` + + 1. Build Images + + ```bash + docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml build + ``` + + 1. Run Docker Environment -> Runs the entire Phoniebox environment + + ```bash + docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml up + ``` + + * Shuts down Docker containers and Docker network + + ```bash + docker-compose -f docker/docker-compose.yml -f docker/docker-compose.mac.yml down + ``` + +
### Windows +
+ +See details + 1. Install [Docker & Compose (Windows)](https://docs.docker.com/docker-for-windows/install/) -2. Download [pulseaudio](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) +1. Download [pulseaudio](https://www.freedesktop.org/wiki/Software/PulseAudio/Ports/Windows/Support/) -3. Uncompress somewhere in your user folder +1. Uncompress somewhere in your user folder -4. Edit `$INSTALL_DIR/etc/pulse/default.pa` +1. Edit `$INSTALL_DIR/etc/pulse/default.pa` -5. Add the following line +1. Add the following line ``` bash load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 ``` -6. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the +1. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the following line and change it to: ``` bash exit-idle-time = -1 ``` -7. Execute `$INSTALL_DIR/bin/pulseaudio.exe` +1. Execute `$INSTALL_DIR/bin/pulseaudio.exe` -8. Make sure Docker is running (e.g. start Docker Desktop) +1. Make sure Docker is running (e.g. start Docker Desktop) -9. Run `docker-compose` +1. Run `docker-compose` - ``` bash - // Build Images - $ docker-compose -f docker/docker-compose.yml build + 1. Build libzmq for your host machine - // Run Docker Environment - $ docker-compose -f docker/docker-compose.yml up + ```bash + docker build -f docker/Dockerfile.libzmq -t libzmq:local . + ``` - // Shuts down Docker containers and Docker network - $ docker-compose -f docker/docker-compose.yml down - ``` + 1. Build Images + + ```bash + docker-compose -f docker/docker-compose.yml build + ``` + + 1. Run Docker Environment -> Runs the entire Phoniebox environment + + ```bash + docker-compose -f docker/docker-compose.yml up + ``` + + * Shuts down Docker containers and Docker network + + ```bash + docker-compose -f docker/docker-compose.yml down + ``` + +
### Linux +
+ +See details + 1. Install Docker & Compose * [Docker](https://docs.docker.com/engine/install/debian/) * [Compose](https://docs.docker.com/compose/install/) -2. Make sure you don\'t use `sudo` to run your `docker-compose`. Check out +1. Make sure you don\'t use `sudo` to run your `docker-compose`. Check out Docker\'s [post-installation guide](https://docs.docker.com/engine/install/linux-postinstall/) for more information. -```bash -// Build Images -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build +1. Run `docker-compose` -// Run Docker Environment -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up + 1. Build libzmq for your host machine -// Shuts down Docker containers and Docker network -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down -``` + ```bash + docker build -f docker/Dockerfile.libzmq -t libzmq:local . + ``` -Note: if you have `mpd` running on your system, you need to stop it -using: + 1. Build Images -``` bash -$ sudo systemctl stop mpd.socket -$ sudo mpd --kill -``` + ```bash + docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml build + ``` -Otherwise you might get the error message: + 1. Run Docker Environment -> Runs the entire Phoniebox environment -``` bash -$ docker-compose -f docker-compose.yml -f docker-compose.linux.yml up -Starting mpd ... -Starting mpd ... error -(...) -Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already in use -``` + ```bash + docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml up + ``` -Read these threads for details: [thread 1](https://unix.stackexchange.com/questions/456909/socket-already-in-use-but-is-not-listed-mpd) and [thread 2](https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num/5106755#5106755) + * Shuts down Docker containers and Docker network + + ```bash + docker-compose -f docker/docker-compose.yml -f docker/docker-compose.linux.yml down + ``` + +
## Test & Develop @@ -175,7 +216,7 @@ restart your `jukebox` container. Update the below path with your specific host environment. ``` bash -$ docker-compose -f docker/docker-compose.yml -f docker/docker-compose.[ENVIRONMENT].yml restart jukebox +docker-compose -f docker/docker-compose.yml -f docker/docker-compose.[ENVIRONMENT].yml restart jukebox ``` ## Known issues @@ -208,13 +249,13 @@ To fix the issue, try the following. brew service stop pulseaudio ``` -2. Start Pulseaudio with this command +1. Start Pulseaudio with this command ```bash pulseaudio --load=module-native-protocol-tcp --exit-idle-time=-1 --daemon ``` -3. Check if daemon is working +1. Check if daemon is working ```bash pulseaudio --check -v @@ -224,6 +265,27 @@ Everything else should have been set up properly as a [prerequisite](#mac) * [Source](https://gist.github.com/seongyongkim/b7d630a03e74c7ab1c6b53473b592712) +#### `mpd` issues on Linux + +If you have `mpd` running on your system, you need to stop it using: + +``` bash +sudo systemctl stop mpd.socket +sudo mpd --kill +``` + +Otherwise you might get the error message: + +``` bash +docker-compose -f docker-compose.yml -f docker-compose.linux.yml up +Starting mpd ... +Starting mpd ... error +(...) +Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already in use +``` + +Read these threads for details: [thread 1](https://unix.stackexchange.com/questions/456909/socket-already-in-use-but-is-not-listed-mpd) and [thread 2](https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num/5106755#5106755) + #### Other error messages When starting the `mpd` container, you will see the following errors. @@ -274,7 +336,7 @@ jukebox | 319:server.py - jb.pub.server - host.timer.cputemp If you encounter the following error, refer to [Pulseaudio issues on Mac](#pulseaudio-issue-on-mac). -```text +``` bash jukebox | 21.12.2023 08:50:09 - 629:plugs.py - jb.plugin - MainThread - ERROR - Ignoring failed package load finalizer: 'volume.finalize()' jukebox | 21.12.2023 08:50:09 - 630:plugs.py - jb.plugin - MainThread - ERROR - Reason: NameError: name 'pulse_control' is not defined ``` @@ -289,8 +351,8 @@ run `mpd` or `webapp`. The following command can be run on a Mac. ``` bash -$ docker build -f docker/jukebox.Dockerfile -t jukebox . -$ docker run -it --rm \ +docker build -f docker/Dockerfile.jukebox -t jukebox . +docker run -it --rm \ -v $(PWD)/src/jukebox:/home/pi/RPi-Jukebox-RFID/src/jukebox \ -v $(PWD)/shared/audiofolders:/home/pi/RPi-Jukebox-RFID/shared/audiofolders \ -v ~/.config/pulse:/root/.config/pulse \ @@ -314,7 +376,6 @@ Mount the device into the container by configuring the appropriate device in a ` ### Resources - #### Mac * @@ -328,8 +389,6 @@ Mount the device into the container by configuring the appropriate device in a ` * * - - #### Audio * diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index cb85198be..9dcca2256 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -86,7 +86,7 @@ _jukebox_core_build_and_install_pyzmq() { fi ZMQ_PREFIX="${JUKEBOX_ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ - pip install -v --no-binary pyzmq pyzmq + pip install -v pyzmq --no-binary pyzmq else print_lc " Skipping. pyzmq already installed" fi From aadff23662399a7388f9881e6da170cf6c17b24f Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Mon, 8 Apr 2024 07:14:00 +0000 Subject: [PATCH 105/121] Fix PlayerMPD.rewind to start with the first song (#2323) rewind() is documented to jump to the first song of the playlist. Instead, it jumped to the second song as SONGPOS in MPDClient.play(SONGPOS) is zero-indexed [1], so mpd.play(1) started the second song. [1] https://mpd.readthedocs.io/en/latest/protocol.html#the-queue "The position is a 0-based index" Related: #2294 --- src/jukebox/components/playermpd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index 49f630224..4ae9458ec 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -350,7 +350,7 @@ def rewind(self): Note: Will not re-read folder config, but leave settings untouched""" logger.debug("Rewind") with self.mpd_lock: - self.mpd_client.play(1) + self.mpd_client.play(0) @plugs.tag def replay(self): From 7c7024c918cd5afb091264e2616f29e6c7b70ed6 Mon Sep 17 00:00:00 2001 From: s-martin Date: Mon, 8 Apr 2024 20:55:29 +0200 Subject: [PATCH 106/121] Add Python 3.12 to Action (#2320) * Try python 3.12 * Trigger py file for action * Revert previous commit * fix flake8 warnings for python 3.12 * fix e126 * fix e126 * ignoring e126 as it is too strict * revert e126 attempts --- .flake8 | 2 ++ .github/workflows/pythonpackage_future3.yml | 2 +- src/jukebox/components/controls/common/evdev_listener.py | 2 +- src/jukebox/components/volume/__init__.py | 2 +- src/jukebox/run_configure_audio.py | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index 7f201b078..6f7bcd443 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,8 @@ [flake8] max-line-length = 127 ignore = + # continuation line over-indented for hanging indent + E126, # continuation line over-indented for visual indent E127, # continuation line under-indented for visual indent diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index 2236a5a47..da7007b97 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -19,7 +19,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 diff --git a/src/jukebox/components/controls/common/evdev_listener.py b/src/jukebox/components/controls/common/evdev_listener.py index a4279afda..a1f336758 100644 --- a/src/jukebox/components/controls/common/evdev_listener.py +++ b/src/jukebox/components/controls/common/evdev_listener.py @@ -160,7 +160,7 @@ def run(self): self._connect() except FileNotFoundError as e: # This error occurs, if opening the bluetooth input device fails - logger.debug(f"{e} (attempt: {idx+1}/{self.open_retry_cnt}). Retrying in {self.open_retry_delay}") + logger.debug(f"{e} (attempt: {idx + 1}/{self.open_retry_cnt}). Retrying in {self.open_retry_delay}") time.sleep(self.open_retry_delay) except AttributeError as e: # This error occurs, when the device can be found, but does not have the mandatory keys diff --git a/src/jukebox/components/volume/__init__.py b/src/jukebox/components/volume/__init__.py index 9dd827e4a..b99e94616 100644 --- a/src/jukebox/components/volume/__init__.py +++ b/src/jukebox/components/volume/__init__.py @@ -424,7 +424,7 @@ def _publish_outputs(self, pulse_inst: pulsectl.Pulse): def _set_output(self, pulse_inst: pulsectl.Pulse, sink_index: int): error_state = 1 if not 0 <= sink_index < len(self._sink_list): - logger.error(f"Sink index '{sink_index}' out of range (0..{len(self._sink_list)-1}). " + logger.error(f"Sink index '{sink_index}' out of range (0..{len(self._sink_list) - 1}). " f"Did you configure your secondary output device?") else: # Before we switch the sink, check the new sinks volume levels... diff --git a/src/jukebox/run_configure_audio.py b/src/jukebox/run_configure_audio.py index 93f0a4c6a..191a25e5d 100644 --- a/src/jukebox/run_configure_audio.py +++ b/src/jukebox/run_configure_audio.py @@ -192,7 +192,7 @@ def query_sinks(pulse_config: PaConfigClass): # noqa: C901 if sink_is_equalizer(primary_signal_chain[sidx - 1]): pulse_config.enable_equalizer = False print(f"\n*** Equalizer already configured for '{pulse_config.primary}' with name\n" - f" '{primary_signal_chain[sidx-1].name}'. Shifting entry point...") + f" '{primary_signal_chain[sidx - 1].name}'. Shifting entry point...") pulse_config.primary = primary_signal_chain[sidx - 1].name sidx -= 1 except ValueError: From 3865c5ab8af929ba05147754a60a29ce1f1bfe22 Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Mon, 8 Apr 2024 19:00:06 +0000 Subject: [PATCH 107/121] Fix CoverartCacheManager (#2325) * Fix CoverartCacheManager for songs with no art Previously, an ERROR was logged for each song without cover art when the Web UI was open. This commit avoids the error, caches the no-cover-art result and saves a roundtrip to mpd for all no-cover-art songs. * refactor: Reducing code and simplifying some logical statements * fix: flake8 error * refactor: reducing complexity for cache filename * refactor: introduce queuing for saving cache files * fix: remove slugify * feat: Use mutagen instead of MPD to retrieve cover art, include cache flush, and thread * fix: flake8 error * Update src/jukebox/components/playermpd/__init__.py Co-authored-by: Christian Hoffmann --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --- requirements.txt | 2 +- src/jukebox/components/playermpd/__init__.py | 46 +++------- .../playermpd/coverart_cache_manager.py | 90 ++++++++++++++++--- src/jukebox/components/rpc_command_alias.py | 4 + .../albums/album-list/album-list-item.js | 4 +- 5 files changed, 97 insertions(+), 49 deletions(-) diff --git a/requirements.txt b/requirements.txt index c172a7636..8ddfc881a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,11 +10,11 @@ wheel # Jukebox Core # For USB inputs (reader, buttons) and bluetooth buttons evdev +mutagen pyalsaaudio pulsectl python-mpd2 ruamel.yaml -python-slugify # For playlistgenerator requests # For the publisher event reactor loop: diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index 4ae9458ec..dcbef2ea8 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -87,7 +87,7 @@ import logging import time import functools -from slugify import slugify +from pathlib import Path import components.player import jukebox.cfghandler import jukebox.utils as utils @@ -521,40 +521,10 @@ def play_card(self, folder: str, recursive: bool = False): @plugs.tag def get_single_coverart(self, song_url): - """ - Saves the album art image to a cache and returns the filename. - """ - base_filename = slugify(song_url) - - try: - metadata_list = self.mpd_client.listallinfo(song_url) - metadata = {} - if metadata_list: - metadata = metadata_list[0] - - if 'albumartist' in metadata and 'album' in metadata: - base_filename = slugify(f"{metadata['albumartist']}-{metadata['album']}") - - cache_filename = self.coverart_cache_manager.find_file_by_hash(base_filename) - - if cache_filename: - return cache_filename - - # Cache file does not exist - # Fetch cover art binary - album_art_data = self.mpd_client.readpicture(song_url) + mp3_file_path = Path(components.player.get_music_library_path(), song_url).expanduser() + cache_filename = self.coverart_cache_manager.get_cache_filename(mp3_file_path) - # Save to cache - cache_filename = self.coverart_cache_manager.save_to_cache(base_filename, album_art_data) - - return cache_filename - - except mpd.base.CommandError as e: - logger.error(f"{e.__class__.__qualname__}: {e} at uri {song_url}") - except Exception as e: - logger.error(f"{e.__class__.__qualname__}: {e} at uri {song_url}") - - return "" + return cache_filename @plugs.tag def get_album_coverart(self, albumartist: str, album: str): @@ -562,6 +532,14 @@ def get_album_coverart(self, albumartist: str, album: str): return self.get_single_coverart(song_list[0]['file']) + @plugs.tag + def flush_coverart_cache(self): + """ + Deletes the Cover Art Cache + """ + + return self.coverart_cache_manager.flush_cache() + @plugs.tag def get_folder_content(self, folder: str): """ diff --git a/src/jukebox/components/playermpd/coverart_cache_manager.py b/src/jukebox/components/playermpd/coverart_cache_manager.py index a7ae12eef..bb2346497 100644 --- a/src/jukebox/components/playermpd/coverart_cache_manager.py +++ b/src/jukebox/components/playermpd/coverart_cache_manager.py @@ -1,26 +1,90 @@ -import os +from mutagen.mp3 import MP3 +from mutagen.id3 import ID3, APIC +from pathlib import Path +import hashlib +import logging +from queue import Queue +from threading import Thread import jukebox.cfghandler +COVER_PREFIX = 'cover' +NO_COVER_ART_EXTENSION = 'no-art' +NO_CACHE = '' +CACHE_PENDING = 'CACHE_PENDING' + +logger = logging.getLogger('jb.CoverartCacheManager') cfg = jukebox.cfghandler.get_handler('jukebox') class CoverartCacheManager: def __init__(self): coverart_cache_path = cfg.setndefault('webapp', 'coverart_cache_path', value='../../src/webapp/build/cover-cache') - self.cache_folder_path = os.path.expanduser(coverart_cache_path) + self.cache_folder_path = Path(coverart_cache_path).expanduser() + self.write_queue = Queue() + self.worker_thread = Thread(target=self.process_write_requests) + self.worker_thread.daemon = True # Ensure the thread closes with the program + self.worker_thread.start() + + def generate_cache_key(self, base_filename: str) -> str: + return f"{COVER_PREFIX}-{hashlib.sha256(base_filename.encode()).hexdigest()}" + + def get_cache_filename(self, mp3_file_path: str) -> str: + base_filename = Path(mp3_file_path).stem + cache_key = self.generate_cache_key(base_filename) + + for path in self.cache_folder_path.iterdir(): + if path.stem == cache_key: + if path.suffix == f".{NO_COVER_ART_EXTENSION}": + return NO_CACHE + return path.name + + self.save_to_cache(mp3_file_path) + return CACHE_PENDING + + def save_to_cache(self, mp3_file_path: str): + self.write_queue.put(mp3_file_path) - def find_file_by_hash(self, hash_value): - for filename in os.listdir(self.cache_folder_path): - if filename.startswith(hash_value): - return filename - return None + def _save_to_cache(self, mp3_file_path: str): + base_filename = Path(mp3_file_path).stem + cache_key = self.generate_cache_key(base_filename) + file_extension, data = self._extract_album_art(mp3_file_path) - def save_to_cache(self, base_filename, album_art_data): - mime_type = album_art_data['type'] - file_extension = 'jpg' if mime_type == 'image/jpeg' else mime_type.split('/')[-1] - cache_filename = f"{base_filename}.{file_extension}" + cache_filename = f"{cache_key}.{file_extension}" + full_path = self.cache_folder_path / cache_filename # Works due to Pathlib - with open(os.path.join(self.cache_folder_path, cache_filename), 'wb') as file: - file.write(album_art_data['binary']) + with full_path.open('wb') as file: + file.write(data) + logger.debug(f"Created file: {cache_filename}") return cache_filename + + def _extract_album_art(self, mp3_file_path: str) -> tuple: + try: + audio_file = MP3(mp3_file_path, ID3=ID3) + except Exception as e: + logger.error(f"Error reading MP3 file {mp3_file_path}: {e}") + return (NO_COVER_ART_EXTENSION, b'') + + for tag in audio_file.tags.values(): + if isinstance(tag, APIC): + mime_type = tag.mime + file_extension = 'jpg' if mime_type == 'image/jpeg' else mime_type.split('/')[-1] + return (file_extension, tag.data) + + return (NO_COVER_ART_EXTENSION, b'') + + def process_write_requests(self): + while True: + mp3_file_path = self.write_queue.get() + try: + self._save_to_cache(mp3_file_path) + except Exception as e: + logger.error(f"Error processing write request: {e}") + self.write_queue.task_done() + + def flush_cache(self): + for path in self.cache_folder_path.iterdir(): + if path.is_file(): + path.unlink() + logger.debug(f"Deleted cached file: {path.name}") + logger.info("Cache flushed successfully.") diff --git a/src/jukebox/components/rpc_command_alias.py b/src/jukebox/components/rpc_command_alias.py index f6e238559..5a7820733 100644 --- a/src/jukebox/components/rpc_command_alias.py +++ b/src/jukebox/components/rpc_command_alias.py @@ -75,6 +75,10 @@ 'method': 'repeat', 'note': 'Repeat', 'ignore_card_removal_action': True}, + 'flush_coverart_cache': { + 'package': 'player', + 'plugin': 'ctrl', + 'method': 'flush_coverart_cache'}, # VOLUME 'set_volume': { diff --git a/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js b/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js index 75882dd0d..2c6d99180 100644 --- a/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js +++ b/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js @@ -29,7 +29,9 @@ const AlbumListItem = ({ albumartist, album, isButton = true }) => { album: album }); if (result) { - setCoverImage(`/cover-cache/${result}`); + if(result !== 'CACHE_PENDING') { + setCoverImage(`/cover-cache/${result}`); + } }; } From afd0e474af59f193992865dd7f6190d140c9b5a4 Mon Sep 17 00:00:00 2001 From: s-martin Date: Fri, 12 Apr 2024 23:00:57 +0200 Subject: [PATCH 108/121] maint: Update actions (#2334) * Update action versions * Trigger python action * Update Python action * Use correct env variable * Revert change for triggering action --- .github/workflows/codeql-analysis_v3.yml | 6 +++--- .github/workflows/pythonpackage_future3.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis_v3.yml b/.github/workflows/codeql-analysis_v3.yml index d06cce1a5..a284d7691 100644 --- a/.github/workflows/codeql-analysis_v3.yml +++ b/.github/workflows/codeql-analysis_v3.yml @@ -38,7 +38,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies @@ -51,9 +51,9 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt - # Set the `CODEQL-PYTHON` environment variable to the Python executable + # Set the `CODEQL_EXTRACTOR_PYTHON_ANALYSIS_VERSION` environment variable to the Python executable # that includes the dependencies - echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV + echo "CODEQL_EXTRACTOR_PYTHON_ANALYSIS_VERSION=$(which python)" >> $GITHUB_ENV # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index da7007b97..3bc2c1174 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -22,9 +22,9 @@ jobs: python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From a9ab571ac90cce21b94fef705855b9598c941a30 Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Fri, 12 Apr 2024 21:10:24 +0000 Subject: [PATCH 109/121] Fix PlayerMPD.prev/next() when stopped (#2326) * utils: Add get_config_action This abstracts away the functionality to resolve a given config option to an action in a pre-defined dict. Co-authored-by: Christian Hoffmann * Fix PlayerMPD.prev/next() when stopped * Avoid MPD-related crashes during all prev/next() calls. * Explicitly handle prev() in stopped state, configurable via `playermpd.stopped_prev_action`. * Explicitly handle next() in stopped state, configurable via `playermpd.stopped_next_action`. * Explicitly handle next() when reaching the end of the playlist: jukebox-daemon will now ignore the action by default (similar to v2). It can also be configured to rewind the playlist instead by setting the new config option `playermpd.end_of_playlist_next_action: rewind` or to stop playing. Fixes #2294 Fixes #2327 Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --------- Co-authored-by: pabera <1260686+pabera@users.noreply.github.com> --- .../default-settings/jukebox.default.yaml | 6 ++ src/jukebox/components/playermpd/__init__.py | 59 ++++++++++++++++++- src/jukebox/jukebox/utils.py | 13 ++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/resources/default-settings/jukebox.default.yaml b/resources/default-settings/jukebox.default.yaml index c087cc024..b8e429333 100644 --- a/resources/default-settings/jukebox.default.yaml +++ b/resources/default-settings/jukebox.default.yaml @@ -87,6 +87,12 @@ playermpd: update_on_startup: true check_user_rights: true mpd_conf: ~/.config/mpd/mpd.conf + # Must be one of: 'none', 'stop', 'rewind': + end_of_playlist_next_action: none + # Must be one of: 'none', 'prev', 'rewind': + stopped_prev_action: prev + # Must be one of: 'none', 'next', 'rewind': + stopped_next_action: next rpc: tcp_port: 5555 websocket_port: 5556 diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index dcbef2ea8..772b8c654 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -156,6 +156,28 @@ def __init__(self): self.second_swipe_action = None self.decode_2nd_swipe_option() + self.end_of_playlist_next_action = utils.get_config_action(cfg, + 'playermpd', + 'end_of_playlist_next_action', + {'rewind': self.rewind, + 'stop': self.stop, + 'none': lambda: None}, + logger) + self.stopped_prev_action = utils.get_config_action(cfg, + 'playermpd', + 'stopped_prev_action', + {'rewind': self.rewind, + 'prev': self._prev_in_stopped_state, + 'none': lambda: None}, + logger) + self.stopped_next_action = utils.get_current_song(cfg, + 'playermpd', + 'stopped_next_action', + {'rewind': self.rewind, + 'next': self._next_in_stopped_state, + 'none': lambda: None}, + logger) + self.mpd_client = mpd.MPDClient() self.coverart_cache_manager = CoverartCacheManager() @@ -327,15 +349,48 @@ def pause(self, state: int = 1): @plugs.tag def prev(self): logger.debug("Prev") + if self.mpd_status['state'] == 'stop': + logger.debug('Player is stopped, calling stopped_prev_action') + return self.stopped_prev_action() + try: + with self.mpd_lock: + self.mpd_client.previous() + except mpd.base.CommandError: + # This shouldn't happen in reality, but we still catch + # this error to avoid crashing the player thread: + logger.warning('Failed to go to previous song, ignoring') + + def _prev_in_stopped_state(self): with self.mpd_lock: - self.mpd_client.previous() + self.mpd_client.play(max(0, int(self.mpd_status['pos']) - 1)) @plugs.tag def next(self): """Play next track in current playlist""" logger.debug("Next") + if self.mpd_status['state'] == 'stop': + logger.debug('Player is stopped, calling stopped_next_action') + return self.stopped_next_action() + playlist_len = int(self.mpd_status.get('playlistlength', -1)) + current_pos = int(self.mpd_status.get('pos', 0)) + if current_pos == playlist_len - 1: + logger.debug(f'next() called during last song ({current_pos}) of ' + f'playlist (len={playlist_len}), running end_of_playlist_next_action.') + return self.end_of_playlist_next_action() + try: + with self.mpd_lock: + self.mpd_client.next() + except mpd.base.CommandError: + # This shouldn't happen in reality, but we still catch + # this error to avoid crashing the player thread: + logger.warning('Failed to go to next song, ignoring') + + def _next_in_stopped_state(self): + pos = int(self.mpd_status['pos']) + 1 + if pos > int(self.mpd_status['playlistlength']) - 1: + return self.end_of_playlist_next_action() with self.mpd_lock: - self.mpd_client.next() + self.mpd_client.play(pos) @plugs.tag def seek(self, new_time): diff --git a/src/jukebox/jukebox/utils.py b/src/jukebox/jukebox/utils.py index dbd647490..4cc0270ae 100644 --- a/src/jukebox/jukebox/utils.py +++ b/src/jukebox/jukebox/utils.py @@ -183,6 +183,19 @@ def rpc_call_to_str(cfg_rpc_call: Dict, with_args=True) -> str: return readable +def get_config_action(cfg, section, option, default, valid_actions_dict, logger): + """ + Looks up the given {section}.{option} config option and returns + the associated entry from valid_actions_dict, if valid. Falls back to the given + default otherwise. + """ + action = cfg.setndefault(section, option, value='').lower() + if action not in valid_actions_dict: + logger.error(f"Config {section}.{option} must be one of {valid_actions_dict.keys()}. Using default '{default}'") + action = default + return valid_actions_dict[action] + + def indent(doc, spaces=4): lines = doc.split('\n') for i in range(0, len(lines)): From fa110b49662cc4309c4e659fd2e2ef553820ea63 Mon Sep 17 00:00:00 2001 From: s-martin Date: Sat, 13 Apr 2024 23:05:42 +0200 Subject: [PATCH 110/121] fix a typo (#2336) --- documentation/developers/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 74d95c112..6a0af80c7 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -123,7 +123,7 @@ They can be run individually or in combination. To do that, we use load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 ``` -1. Edit `$INSTALL_DIR/etc/pulse//etc/pulse/daemon.conf`, find the +1. Edit `$INSTALL_DIR/etc/pulse/daemon.conf`, find the following line and change it to: ``` bash From 33fec6465a5ca66613172dae0d5d78f8ea87365b Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Mon, 15 Apr 2024 15:53:04 +0000 Subject: [PATCH 111/121] fix: bad utils.get_config_action invocations (#2339) --- src/jukebox/components/playermpd/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index 772b8c654..86dbc60ab 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -159,6 +159,7 @@ def __init__(self): self.end_of_playlist_next_action = utils.get_config_action(cfg, 'playermpd', 'end_of_playlist_next_action', + 'none', {'rewind': self.rewind, 'stop': self.stop, 'none': lambda: None}, @@ -166,13 +167,15 @@ def __init__(self): self.stopped_prev_action = utils.get_config_action(cfg, 'playermpd', 'stopped_prev_action', + 'prev', {'rewind': self.rewind, 'prev': self._prev_in_stopped_state, 'none': lambda: None}, logger) - self.stopped_next_action = utils.get_current_song(cfg, + self.stopped_next_action = utils.get_config_action(cfg, 'playermpd', 'stopped_next_action', + 'next', {'rewind': self.rewind, 'next': self._next_in_stopped_state, 'none': lambda: None}, From 0ab75c1d28955e85c2069503b5a8428a69967d0d Mon Sep 17 00:00:00 2001 From: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:30:46 +0200 Subject: [PATCH 112/121] hotfix pyzmq installation on bullseye (pin version <26) (#2345) * fix: pin pyzmq version to <26 * fix: add checks for installed ZMQ version and DRAFT_API * Bump version to hotfix v3.5.3 --- installation/routines/setup_jukebox_core.sh | 16 +++++++++++++++- src/jukebox/jukebox/version.py | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index cb85198be..f5ef2eec2 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -86,7 +86,7 @@ _jukebox_core_build_and_install_pyzmq() { fi ZMQ_PREFIX="${JUKEBOX_ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ - pip install -v --no-binary pyzmq pyzmq + pip install -v --no-binary pyzmq 'pyzmq<26' else print_lc " Skipping. pyzmq already installed" fi @@ -120,6 +120,20 @@ _jukebox_core_check() { local pip_modules=$(get_args_from_file "${INSTALLATION_PATH}/requirements.txt") verify_pip_modules pyzmq $pip_modules + log " Verify ZMQ version '${JUKEBOX_ZMQ_VERSION}'" + local zmq_version=$(python -c 'import zmq; print(f"{zmq.zmq_version()}")') + if [[ "${zmq_version}" != "${JUKEBOX_ZMQ_VERSION}" ]]; then + exit_on_error "ERROR: ZMQ version '${zmq_version}' differs from expected '${JUKEBOX_ZMQ_VERSION}'!" + fi + log " CHECK" + + log " Verify ZMQ has 'DRAFT-API' activated" + local zmq_hasDraftApi=$(python -c 'import zmq; print(f"{zmq.DRAFT_API}")') + if [[ "${zmq_hasDraftApi}" != "True" ]]; then + exit_on_error "ERROR: ZMQ has 'DRAFT-API' '${zmq_hasDraftApi}' differs from expected 'True'!" + fi + log " CHECK" + verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${JUKEBOX_PULSE_CONFIG}" verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${SETTINGS_PATH}/jukebox.yaml" diff --git a/src/jukebox/jukebox/version.py b/src/jukebox/jukebox/version.py index b97391207..d62ef3e93 100644 --- a/src/jukebox/jukebox/version.py +++ b/src/jukebox/jukebox/version.py @@ -1,7 +1,7 @@ VERSION_MAJOR = 3 VERSION_MINOR = 5 -VERSION_PATCH = 2 +VERSION_PATCH = 3 VERSION_EXTRA = "" # build a version string in compliance with the SemVer specification From 6003682479598e51631c79c9cff6e571a89ea2af Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sun, 21 Apr 2024 11:04:41 +0200 Subject: [PATCH 113/121] fix: docker build after pyzmq update (#2351) * fix: Add cmake as requirement to support pyzmq build * fix: Remove cmake and pin pyzmq<26 as cmake does not build well in RPI env --- docker/Dockerfile.jukebox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.jukebox b/docker/Dockerfile.jukebox index 3d63a4a02..194e2efdc 100644 --- a/docker/Dockerfile.jukebox +++ b/docker/Dockerfile.jukebox @@ -44,7 +44,7 @@ RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt ENV ZMQ_PREFIX /opt/libzmq ENV ZMQ_DRAFT_API 1 COPY --from=libzmq ${ZMQ_PREFIX} ${ZMQ_PREFIX} -RUN pip install -v pyzmq --no-binary pyzmq +RUN pip install -v "pyzmq<26" --no-binary pyzmq EXPOSE 5555 5556 WORKDIR ${INSTALLATION_PATH}/src/jukebox From aee32ff5f1653653c07274dc4077d57b5920b5ac Mon Sep 17 00:00:00 2001 From: pabera <1260686+pabera@users.noreply.github.com> Date: Sun, 21 Apr 2024 21:30:25 +0200 Subject: [PATCH 114/121] feat: Allow to enable/disable Cover Art in Web App & load from filesystem (#2352) * feat: Introduce Web App Settings * feat: Allow to enable/disable Cover Art in Web App * fix: handle Falsy mimetype and data even when APIC tag has been found my mutagen * feat: Try to load cover from filesystem when not found in audio file * fix: flake8 linting error * docs: Add documentation for Cover Art * feat: Allow show_covers setting to be managed in Web App * fix: again flake8 linting errors --- documentation/builders/README.md | 2 + documentation/builders/webapp/cover-art.md | 37 ++++++++++++ .../default-settings/jukebox.default.yaml | 4 ++ src/jukebox/components/misc.py | 19 +++++++ .../playermpd/coverart_cache_manager.py | 23 +++++++- src/webapp/public/locales/de/translation.json | 6 ++ src/webapp/public/locales/en/translation.json | 6 ++ src/webapp/src/App.js | 21 ++++--- src/webapp/src/commands/index.js | 13 +++++ .../albums/album-list/album-list-item.js | 21 +++++-- src/webapp/src/components/Player/index.js | 9 ++- .../src/components/Settings/general/index.js | 39 +++++++++++++ .../Settings/general/show-covers.js | 56 +++++++++++++++++++ src/webapp/src/components/Settings/index.js | 6 +- src/webapp/src/context/appsettings/context.js | 7 +++ src/webapp/src/context/appsettings/index.js | 33 +++++++++++ 16 files changed, 283 insertions(+), 19 deletions(-) create mode 100644 documentation/builders/webapp/cover-art.md create mode 100644 src/webapp/src/components/Settings/general/index.js create mode 100644 src/webapp/src/components/Settings/general/show-covers.js create mode 100644 src/webapp/src/context/appsettings/context.js create mode 100644 src/webapp/src/context/appsettings/index.js diff --git a/documentation/builders/README.md b/documentation/builders/README.md index 512d26ed0..29733e92b 100644 --- a/documentation/builders/README.md +++ b/documentation/builders/README.md @@ -30,6 +30,8 @@ ## Web Application +* Application + * [Cover Art](./webapp/cover-art.md) * Music * [Playlists, Livestreams and Podcasts](./webapp/playlists-livestreams-podcasts.md) diff --git a/documentation/builders/webapp/cover-art.md b/documentation/builders/webapp/cover-art.md new file mode 100644 index 000000000..09bae3a30 --- /dev/null +++ b/documentation/builders/webapp/cover-art.md @@ -0,0 +1,37 @@ +# Cover Art + +## Enable/Disable Cover Art + +The Web App automatically searches for cover art for albums and songs. If it finds cover art, it displays it; if not, it shows a placeholder image. However, you may prefer to disable cover art (e.g. in situations where device performance is low; screen space is limited; etc). There are two ways to do this: + +1. **Web App Settings**: Go to the "Settings" tab. Under the "General" section, find and toggle the "Show Cover Art" option. +1. **Configuration File**: Open the `jukebox.yaml` file. Navigate to `webapp` -> `show_covers`. Set this value to `true` to enable or `false` to disable cover art display. If this option does not exist, it assumes `true` as a default. + +## Providing Additional Cover Art + +Cover art can be provided in two ways: 1) embedded within the audio file itself, or 2) as a separate image file in the same directory as the audio file. The software searches for cover art in the order listed. + +To add cover art using the file system, place a file named `cover.jpg` in the same folder as your audio file or album. Accepted image file types are `jpg` and `png`. + +### Example + +Suppose none of your files currently include embedded cover art, the example below demonstrates how to enable cover art for an entire folder, applying the same cover art to all files within that folder. + +> [!IMPORTANT] +> You cannot assign different cover arts to different tracks within the same folder. + +#### Example Folder Structure + +```text +└── audiofolders + ├── Simone Sommerland + │ ├── 01 Aramsamsam.mp3 + │ ├── 02 Das Rote Pferd.mp3 + │ ├── 03 Hoch am Himmel.mp3 + │ └── cover.jpg <- Cover Art file as JPG + └── Bibi und Tina + ├── 01 Bibi und Tina Song.mp3 + ├── 02 Alles geht.mp3 + ├── 03 Solange dein Herz spricht.mp3 + └── cover.png <- Cover Art file as PNG +``` diff --git a/resources/default-settings/jukebox.default.yaml b/resources/default-settings/jukebox.default.yaml index b8e429333..9bb214f3d 100644 --- a/resources/default-settings/jukebox.default.yaml +++ b/resources/default-settings/jukebox.default.yaml @@ -153,3 +153,7 @@ sync_rfidcards: config_file: ../../shared/settings/sync_rfidcards.yaml webapp: coverart_cache_path: ../../src/webapp/build/cover-cache + # Load cover arts in Webapp. Change to false in case you have performance issue + # when handling a lot of music + # Defaults to true + show_covers: true diff --git a/src/jukebox/components/misc.py b/src/jukebox/components/misc.py index 9995509aa..2cc260d79 100644 --- a/src/jukebox/components/misc.py +++ b/src/jukebox/components/misc.py @@ -8,8 +8,10 @@ import jukebox.plugs as plugin import jukebox.utils from jukebox.daemon import get_jukebox_daemon +import jukebox.cfghandler logger = logging.getLogger('jb.misc') +cfg = jukebox.cfghandler.get_handler('jukebox') @plugin.register @@ -105,3 +107,20 @@ def empty_rpc_call(msg: str = ''): """ if msg: logger.warning(msg) + + +@plugin.register +def get_app_settings(): + """Return settings for web app stored in jukebox.yaml""" + show_covers = cfg.setndefault('webapp', 'show_covers', value=True) + + return { + 'show_covers': show_covers + } + + +@plugin.register +def set_app_settings(settings={}): + """Set configuration settings for the web app.""" + for key, value in settings.items(): + cfg.setn('webapp', key, value=value) diff --git a/src/jukebox/components/playermpd/coverart_cache_manager.py b/src/jukebox/components/playermpd/coverart_cache_manager.py index bb2346497..f292a2bbe 100644 --- a/src/jukebox/components/playermpd/coverart_cache_manager.py +++ b/src/jukebox/components/playermpd/coverart_cache_manager.py @@ -47,7 +47,10 @@ def save_to_cache(self, mp3_file_path: str): def _save_to_cache(self, mp3_file_path: str): base_filename = Path(mp3_file_path).stem cache_key = self.generate_cache_key(base_filename) + file_extension, data = self._extract_album_art(mp3_file_path) + if file_extension == NO_COVER_ART_EXTENSION: # Check if cover has been added as separate file in folder + file_extension, data = self._get_from_filesystem(mp3_file_path) cache_filename = f"{cache_key}.{file_extension}" full_path = self.cache_folder_path / cache_filename # Works due to Pathlib @@ -67,9 +70,23 @@ def _extract_album_art(self, mp3_file_path: str) -> tuple: for tag in audio_file.tags.values(): if isinstance(tag, APIC): - mime_type = tag.mime - file_extension = 'jpg' if mime_type == 'image/jpeg' else mime_type.split('/')[-1] - return (file_extension, tag.data) + if tag.mime and tag.data: + file_extension = 'jpg' if tag.mime == 'image/jpeg' else tag.mime.split('/')[-1] + return (file_extension, tag.data) + + return (NO_COVER_ART_EXTENSION, b'') + + def _get_from_filesystem(self, mp3_file_path: str) -> tuple: + path = Path(mp3_file_path) + directory = path.parent + cover_files = list(directory.glob('Cover.*')) + list(directory.glob('cover.*')) + + for file in cover_files: + if file.suffix.lower() in ['.jpg', '.jpeg', '.png']: + with file.open('rb') as img_file: + data = img_file.read() + file_extension = file.suffix[1:] # Get extension without dot + return (file_extension, data) return (NO_COVER_ART_EXTENSION, b'') diff --git a/src/webapp/public/locales/de/translation.json b/src/webapp/public/locales/de/translation.json index d1a4391d6..7dbdcf695 100644 --- a/src/webapp/public/locales/de/translation.json +++ b/src/webapp/public/locales/de/translation.json @@ -219,6 +219,12 @@ "why": "Warum?", "control-label": "Auto Hotspot" }, + "general": { + "title": "Allgmeine Einstellungen", + "show_covers": { + "title": "Cover anzeigen" + } + }, "timers": { "option-label-timeslot": "{{value}} min", "option-label-off": "Aus", diff --git a/src/webapp/public/locales/en/translation.json b/src/webapp/public/locales/en/translation.json index 74fd9a696..7ff66ecc4 100644 --- a/src/webapp/public/locales/en/translation.json +++ b/src/webapp/public/locales/en/translation.json @@ -219,6 +219,12 @@ "why": "Why?", "control-label": "Auto Hotspot" }, + "general": { + "title": "General Settings", + "show_covers": { + "title": "Show Cover Art" + } + }, "timers": { "option-label-timeslot": "{{value}} min", "option-label-off": "Off", diff --git a/src/webapp/src/App.js b/src/webapp/src/App.js index 99272db64..a51529381 100644 --- a/src/webapp/src/App.js +++ b/src/webapp/src/App.js @@ -2,6 +2,7 @@ import React, { Suspense } from 'react'; import Grid from '@mui/material/Grid'; +import AppSettingsProvider from './context/appsettings'; import PubSubProvider from './context/pubsub'; import PlayerProvider from './context/player'; import Router from './router'; @@ -10,15 +11,17 @@ function App() { return ( - - - + + + + + ); diff --git a/src/webapp/src/commands/index.js b/src/webapp/src/commands/index.js index 8c844d8da..f6f772875 100644 --- a/src/webapp/src/commands/index.js +++ b/src/webapp/src/commands/index.js @@ -120,6 +120,7 @@ const commands = { _package: 'volume', plugin: 'ctrl', method: 'set_volume', + argKeys: ['volume'], }, getVolume: { _package: 'volume', @@ -250,6 +251,18 @@ const commands = { argKeys: ['option'], }, + // Misc + getAppSettings: { + _package: 'misc', + plugin: 'get_app_settings' + }, + + setAppSettings: { + _package: 'misc', + plugin: 'set_app_settings', + argKeys: ['settings'], + }, + // Synchronisation 'sync_rfidcards_all': { _package: 'sync_rfidcards', diff --git a/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js b/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js index 2c6d99180..71f6ba315 100644 --- a/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js +++ b/src/webapp/src/components/Library/lists/albums/album-list/album-list-item.js @@ -1,4 +1,4 @@ -import React, { forwardRef, useEffect, useState } from 'react'; +import React, { forwardRef, useContext, useEffect, useState } from 'react'; import { Link, useLocation, @@ -15,6 +15,7 @@ import { import noCover from '../../../../../assets/noCover.jpg'; +import AppSettingsContext from '../../../../../context/appsettings/context'; import request from '../../../../../utils/request'; const AlbumListItem = ({ albumartist, album, isButton = true }) => { @@ -22,6 +23,14 @@ const AlbumListItem = ({ albumartist, album, isButton = true }) => { const { search: urlSearch } = useLocation(); const [coverImage, setCoverImage] = useState(noCover); + const { + settings, + } = useContext(AppSettingsContext); + + const { + show_covers, + } = settings; + useEffect(() => { const getCoverArt = async () => { const { result } = await request('getAlbumCoverArt', { @@ -35,7 +44,7 @@ const AlbumListItem = ({ albumartist, album, isButton = true }) => { }; } - if (albumartist && album) { + if (albumartist && album && show_covers) { getCoverArt(); } }, [albumartist, album]); @@ -61,9 +70,11 @@ const AlbumListItem = ({ albumartist, album, isButton = true }) => { key={album} > - - - + {show_covers && + + + + } { const [coverImage, setCoverImage] = useState(undefined); const [backgroundImage, setBackgroundImage] = useState('none'); + const { + settings, + } = useContext(AppSettingsContext); + + const { show_covers } = settings; + useEffect(() => { const getCoverArt = async () => { const { result } = await request('getSingleCoverArt', { song_url: file }); @@ -30,7 +37,7 @@ const Player = () => { }; } - if (file) { + if (file && show_covers) { getCoverArt(); } }, [file]); diff --git a/src/webapp/src/components/Settings/general/index.js b/src/webapp/src/components/Settings/general/index.js new file mode 100644 index 000000000..790043778 --- /dev/null +++ b/src/webapp/src/components/Settings/general/index.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useTheme } from '@mui/material/styles'; + +import { + Card, + CardContent, + CardHeader, + Divider, + Grid, +} from '@mui/material'; +import ShowCovers from './show-covers'; + +const SettingsGeneral = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const spacer = { marginBottom: theme.spacing(2) } + + return ( + + + + + .MuiGrid-root:not(:last-child)': spacer }} + > + + + + + ); +}; + +export default SettingsGeneral; diff --git a/src/webapp/src/components/Settings/general/show-covers.js b/src/webapp/src/components/Settings/general/show-covers.js new file mode 100644 index 000000000..a3b31f4e0 --- /dev/null +++ b/src/webapp/src/components/Settings/general/show-covers.js @@ -0,0 +1,56 @@ +import React, { useContext } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { + Box, + Grid, + Switch, + Typography, +} from '@mui/material'; + +import AppSettingsContext from '../../../context/appsettings/context'; +import request from '../../../utils/request'; + +const ShowCovers = () => { + const { t } = useTranslation(); + + const { + settings, + setSettings, + } = useContext(AppSettingsContext); + + const { + show_covers, + } = settings; + + const updateShowCoversSetting = async (show_covers) => { + await request('setAppSettings', { settings: { show_covers }}); + } + + const handleSwitch = (event) => { + setSettings({ show_covers: event.target.checked}); + updateShowCoversSetting(event.target.checked); + } + + return ( + + + + {t(`settings.general.show_covers.title`)} + + + + + + + ); +}; + +export default ShowCovers; diff --git a/src/webapp/src/components/Settings/index.js b/src/webapp/src/components/Settings/index.js index 1bc599fc1..75ce7840f 100644 --- a/src/webapp/src/components/Settings/index.js +++ b/src/webapp/src/components/Settings/index.js @@ -2,9 +2,10 @@ import React from 'react'; import { Grid } from '@mui/material'; +import SettingsAudio from './audio/index'; import SettingsAutoHotspot from './autohotspot'; +import SettingsGeneral from './general'; import SettingsSecondSwipe from './secondswipe'; -import SettingsAudio from './audio/index'; import SettingsStatus from './status/index'; import SettingsTimers from './timers/index'; import SystemControls from './systemcontrols'; @@ -28,6 +29,9 @@ const Settings = () => { + + + diff --git a/src/webapp/src/context/appsettings/context.js b/src/webapp/src/context/appsettings/context.js new file mode 100644 index 000000000..f2650d210 --- /dev/null +++ b/src/webapp/src/context/appsettings/context.js @@ -0,0 +1,7 @@ +import { createContext } from 'react'; + +const AppSettingsContext = createContext({ + showCovers: true, +}); + +export default AppSettingsContext; diff --git a/src/webapp/src/context/appsettings/index.js b/src/webapp/src/context/appsettings/index.js new file mode 100644 index 000000000..1fa34914d --- /dev/null +++ b/src/webapp/src/context/appsettings/index.js @@ -0,0 +1,33 @@ +import React, { useEffect, useState } from 'react'; + +import AppSettingsContext from './context'; +import request from '../../utils/request'; + +const AppSettingsProvider = ({ children }) => { + const [settings, setSettings] = useState({}); + + useEffect(() => { + const loadAppSettings = async () => { + const { result, error } = await request('getAppSettings'); + if(result) setSettings(result); + if(error) { + console.error('Error loading AppSettings'); + } + } + + loadAppSettings(); + }, []); + + const context = { + setSettings, + settings, + }; + + return( + + { children } + + ) +}; + +export default AppSettingsProvider; From 141a05fe1e676bd7951e9396bb2b53e3165d4ac0 Mon Sep 17 00:00:00 2001 From: s-martin Date: Sat, 4 May 2024 00:49:37 +0200 Subject: [PATCH 115/121] Remove setup-python-dependencies (#2363) --- .github/workflows/codeql-analysis_v3.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/codeql-analysis_v3.yml b/.github/workflows/codeql-analysis_v3.yml index a284d7691..574f2b2bd 100644 --- a/.github/workflows/codeql-analysis_v3.yml +++ b/.github/workflows/codeql-analysis_v3.yml @@ -64,7 +64,6 @@ jobs: # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main - setup-python-dependencies: false # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) From a1b2df85e63d81dc83ad47e7a079228aa5580354 Mon Sep 17 00:00:00 2001 From: s-martin Date: Sun, 5 May 2024 23:48:04 +0200 Subject: [PATCH 116/121] Fix links (#2366) --- documentation/developers/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/developers/README.md b/documentation/developers/README.md index ff21a8ceb..412cc66a9 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -4,7 +4,7 @@ * [Development Environment](./development-environment.md) * [Python Development Notes](python.md) -* [Documentation (with Markdown)](documentatíon.md) +* [Documentation (with Markdown)](documentation.md) ## Reference @@ -12,7 +12,7 @@ * [Web App](./webapp.md) * [RFID Readers](./rfid/README.md) * [Docstring API Docs (from py files)](./docstring/README.md) -* [Plugin Reference](./docstring/README.md#jukeboxplugs) +* [Plugin Reference](./docstring/README.md#jukebox.plugs) * [Feature Status](./status.md) * [Known Issues](./known-issues.md) From 2232a35e0b629a5560577242dc2a9e7cdc8ac3e4 Mon Sep 17 00:00:00 2001 From: s-martin Date: Fri, 17 May 2024 19:39:40 +0200 Subject: [PATCH 117/121] add details about cards for RC522 (#2372) * add details about cards * fix empty line * Update documentation/developers/rfid/mfrc522_spi.md Co-authored-by: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> --------- Co-authored-by: Alvin Schiller <103769832+AlvinSchiller@users.noreply.github.com> --- documentation/developers/rfid/mfrc522_spi.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/developers/rfid/mfrc522_spi.md b/documentation/developers/rfid/mfrc522_spi.md index 363df3836..be3abeaa8 100644 --- a/documentation/developers/rfid/mfrc522_spi.md +++ b/documentation/developers/rfid/mfrc522_spi.md @@ -78,3 +78,7 @@ MISO. MFRC522 boards can be picked up from many places for little money. Good quality ones can be found e.g. here + +### Cards/Tags + +Cards or tags must support 13.56 MHz. Currently, only cards/tags of the type "NXP Mifare Classic 1k(S50)", "NXP Mifare Classic 4k(S70)" and "NXP Mifare Ultralight (C)" can be used. Type "NXP Mifare NTAG2xx" or others will not work! From d93f07111ec54bf772711aac4208376041be2970 Mon Sep 17 00:00:00 2001 From: s-martin Date: Wed, 5 Jun 2024 08:10:33 +0200 Subject: [PATCH 118/121] Remove unused read of parameter (#2382) --- src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py b/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py index f6d0a5db5..9f176566d 100755 --- a/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py +++ b/src/jukebox/components/rfid/hardware/generic_usb/generic_usb.py @@ -140,8 +140,6 @@ def __init__(self, reader_cfg_key, logger=None): raise KeyError("Mandatory key 'device_name' not given in configuration!") if 'device_phys' not in config: self._logger.warning("Key 'device_phys' not given in configuration! Trying without...") - if 'key_capability' not in config: - self._logger.warning("Key 'key_capability' not given in configuration! Using default value: 'true'.") if 'name_is_unique' not in config: self._logger.warning("Key 'name_is_unique' not given in configuration! Using default value: 'true'.") if 'key_check_is_unique' not in config: From f2a1730a8df693350aef14aa6df6f563ffe6f786 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 8 Jun 2024 09:50:05 +0200 Subject: [PATCH 119/121] feat: Add INA219 battery sensor (#2380) * specify specific pip version wich works with zmq install * add INA219 sensor * average measurement for more accurate results. Use supply voltage for measurement. * Revert "specify specific pip version wich works with zmq install" This reverts commit 48dd1bfc6c73f9a6fb1329ae6528e495b07b36e5. * correct format * Update src/jukebox/components/battery_monitor/batt_mon_i2c_ina219/__init__.py Co-authored-by: s-martin * Update copyright notice * Update license * Update license * Document the INA219 * add error handling and type safety * Update batterymonitor.md * Update __init__.py * fix markdown lint for batterymonitor.md * fix markdown lint batterymonitor.md * Update documentation/builders/components/power/batterymonitor.md Co-authored-by: s-martin * Update documentation/builders/components/power/batterymonitor.md Co-authored-by: s-martin --------- Co-authored-by: Timm Co-authored-by: s-martin --- .../components/power/batterymonitor.md | 59 +++++++++++++++++-- .../batt_mon_i2c_ina219/__init__.py | 56 ++++++++++++++++++ 2 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 src/jukebox/components/battery_monitor/batt_mon_i2c_ina219/__init__.py diff --git a/documentation/builders/components/power/batterymonitor.md b/documentation/builders/components/power/batterymonitor.md index d57e14acb..151010caa 100644 --- a/documentation/builders/components/power/batterymonitor.md +++ b/documentation/builders/components/power/batterymonitor.md @@ -1,13 +1,15 @@ -# Battery Monitor based on a ADS1015 +# Battery Monitor > [!CAUTION] > Lithium and other batteries are dangerous and must be treated with care. > Rechargeable Lithium Ion batteries are potentially hazardous and can -> present a serious **FIRE HAZARD** if damaged, defective or improperly used. -> Do not use this circuit to a lithium ion battery without expertise and -> training in handling and use of batteries of this type. +> present a serious **FIRE HAZARD** if damaged, defective, or improperly used. +> Do not use this circuit for a lithium-ion battery without the expertise and +> training in handling and using batteries of this type. > Use appropriate test equipment and safety protocols during development. -> There is no warranty, this may not work as expected or at all! +> There is no warranty, this may not work as expected! + +## Battery Monitor based on a ADS1015 The script in [src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/\_\_init\_\_.py](../../../../src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/__init__.py) is intended to read out the voltage of a single Cell LiIon Battery using a [CY-ADS1015 Board](https://www.adafruit.com/product/1083): @@ -31,3 +33,50 @@ The script in [src/jukebox/components/battery_monitor/batt_mon_i2c_ads1015/\_\_i > > * the circuit is constantly draining the battery! (leak current up to: 2.1µA) > * the time between sample needs to be a minimum 1sec with this high impedance voltage divider don't use the continuous conversion method! + +## Battery Monitor based on an INA219 + +The script in [src/jukebox/components/battery_monitor/batt_mon_i2c_ina219/\_\_init\_\_.py](../../../../src/jukebox/components/battery_monitor/batt_mon_i2c_ina219/__init__.py) is intended to read out the voltage of a single cell or multiple LiIon Battery using a [INA219 Board](https://www.adafruit.com/product/904): + +```text + 3.3V + + + | + .----o----. + | | SDA + .-------------------------------o AIN o------ + | | INA219 | SCL + | .----------o AOUT o------ + --- | | | + Battery - Regulator + Raspi '----o----' + 2.9V-4.2V| | | + | | | + === === === +``` + +## Configuration example + +The battery monitoring is configured in the jukebox.yml file. + +The "battmon" module has to be added to the modules setting. + +```yaml +modules: + named: + # Do not change the order! + publishing: publishing + ... + battmon: battery_monitor.batt_mon_i2c_ina219 +``` + +The battmon module needs further configuration: + +```yaml +battmon: + scale_to_phy_num: 1 + scale_to_phy_denom: 0 + warning_action: + all_clear_action: +``` + +The setting "scale_to_phy_denom" does not influence the INA219. However, the scale can be adjusted to fit multiple LiIon cells. diff --git a/src/jukebox/components/battery_monitor/batt_mon_i2c_ina219/__init__.py b/src/jukebox/components/battery_monitor/batt_mon_i2c_ina219/__init__.py new file mode 100644 index 000000000..c9b51eece --- /dev/null +++ b/src/jukebox/components/battery_monitor/batt_mon_i2c_ina219/__init__.py @@ -0,0 +1,56 @@ +# RPi-Jukebox-RFID Version 3 +# Copyright (c) See file LICENSE in project root folder + +import logging +import jukebox.plugs as plugs +import jukebox.cfghandler +from ina219 import INA219 +from ina219 import DeviceRangeError +from components.battery_monitor import BatteryMonitorBase + +logger = logging.getLogger('jb.battmon.ina219') + +batt_mon = None + + +class battmon_ina219(BatteryMonitorBase.BattmonBase): + '''Battery Monitor based on a INA219 + + See [Battery Monitor documentation](../../builders/components/power/batterymonitor.md) + ''' + + def __init__(self, cfg): + super().__init__(cfg, logger) + + def init_batt_mon_hw(self, num: float, denom: float) -> None: + try: + self.adc = INA219(float(num) / 1000, busnum=1) + self.adc.configure(self.adc.RANGE_16V, self.adc.GAIN_AUTO, self.adc.ADC_32SAMP, self.adc.ADC_32SAMP) + except DeviceRangeError as e: + logger.error(f"Device range error: {e}") + raise + except Exception as e: + logger.error(f"Failed to initialize INA219: {e}") + raise + + def get_batt_voltage(self) -> int: + try: + batt_voltage_mV = self.adc.supply_voltage() * 1000.0 + return int(batt_voltage_mV) + except Exception as e: + logger.error(f"Failed to get supply voltage from INA219: {e}") + raise + + +@plugs.finalize +def finalize(): + global batt_mon + cfg = jukebox.cfghandler.get_handler('jukebox') + batt_mon = battmon_ina219(cfg) + plugs.register(batt_mon, name='batt_mon') + + +@plugs.atexit +def atexit(**ignored_kwargs): + global batt_mon + batt_mon.status_thread.cancel() From 75d41c4b2cf3a8b46ad0ad4199c27d14d233a3a9 Mon Sep 17 00:00:00 2001 From: votti Date: Fri, 30 Dec 2022 17:15:57 +0100 Subject: [PATCH 120/121] Add resume to play_card Adds a resume flag to play_card to resume from position in case there is already playback information for a folder. This would be important for audiobooks. In case the resume fails (eg if the folder changed), a normal playback is done and the error logged. --- src/jukebox/components/playermpd/__init__.py | 25 ++++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index 86dbc60ab..f726c15c3 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -538,7 +538,7 @@ def resume(self): self.mpd_client.play() @plugs.tag - def play_card(self, folder: str, recursive: bool = False): + def play_card(self, folder: str, recursive: bool = False, resume: bool = False): """ Main entry point for trigger music playing from RFID reader. Decodes second swipe options before playing folder content @@ -547,6 +547,7 @@ def play_card(self, folder: str, recursive: bool = False): :param folder: Folder path relative to music library path :param recursive: Add folder recursively + :param resume: Try to resume from last position? """ # Developers notes: # @@ -575,7 +576,7 @@ def play_card(self, folder: str, recursive: bool = False): # run callbacks before play_folder is invoked play_card_callbacks.run_callbacks(folder, PlayCardState.firstSwipe) - self.play_folder(folder, recursive) + self.play_folder(folder, recursive, resume=resume) @plugs.tag def get_single_coverart(self, song_url): @@ -612,7 +613,8 @@ def get_folder_content(self, folder: str): return plc.playlist @plugs.tag - def play_folder(self, folder: str, recursive: bool = False) -> None: + def play_folder(self, folder: str, recursive: bool = False, + resume: bool = False) -> None: """ Playback a music folder. @@ -621,6 +623,7 @@ def play_folder(self, folder: str, recursive: bool = False) -> None: :param folder: Folder path relative to music library path :param recursive: Add folder recursively + :param resume: Try to resume from previous state? """ # TODO: This changes the current state -> Need to save last state with self.mpd_lock: @@ -640,11 +643,23 @@ def play_folder(self, folder: str, recursive: bool = False) -> None: self.music_player_status['player_status']['last_played_folder'] = folder + # Here a reference to the folder dict is used. + # Thus any update to the current_folder_status dict will + # be reflected in the dict of the corresponding folder self.current_folder_status = self.music_player_status['audio_folder_status'].get(folder) if self.current_folder_status is None: self.current_folder_status = self.music_player_status['audio_folder_status'][folder] = {} - - self.mpd_client.play() + # Dont attempt to resume, if this is a new folder + self.mpd_client.play() + else: + if resume: + try: + self.resume() + except mpd.base.CommandError as e: + logger.exception("Failed to resume folder: %s", folder) + self.mpd_client.play() + else: + self.mpd_client.play() @plugs.tag def play_album(self, albumartist: str, album: str): From b8939b41bb40bae3d88e9690ea3d2edf3f1ce631 Mon Sep 17 00:00:00 2001 From: s-martin Date: Fri, 3 May 2024 22:06:39 +0200 Subject: [PATCH 121/121] handle exception in logger --- src/jukebox/components/playermpd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index f726c15c3..67ebdbc28 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -656,7 +656,7 @@ def play_folder(self, folder: str, recursive: bool = False, try: self.resume() except mpd.base.CommandError as e: - logger.exception("Failed to resume folder: %s", folder) + logger.exception("Failed to resume folder: %s", folder, exc_info=e) self.mpd_client.play() else: self.mpd_client.play()