diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..41332ffe2 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +omit = + **/test/* + scripts/* diff --git a/.flake8 b/.flake8 index 70f83a4a0..d1b156813 100644 --- a/.flake8 +++ b/.flake8 @@ -4,4 +4,9 @@ ignore = # continuation line over-indented for visual indent E127, # continuation line under-indented for visual indent - E128 \ No newline at end of file + E128 +per-file-ignores = + # Only in __init__files ignore imported but unused + # Not necessary, if __all__ is declared in __init__ file + # https://www.python.org/dev/peps/pep-0008/#id48 + __init__.py:F401 diff --git a/.github/ISSUE_TEMPLATE/bug_template.md b/.github/ISSUE_TEMPLATE/bug_template.md deleted file mode 100644 index 222325940..000000000 --- a/.github/ISSUE_TEMPLATE/bug_template.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -name: bug report -about: use this template to report bugs -title: "\U0001F41B | BUG SUMMARY" -labels: bug, needs triage ---- - -## Bug - -### What I did - - - -### What happened - - - -### I expected this to happen - - - -### Further information that might help - - - - -## Software - -### Base image and version - - - -### Branch / Release - - - -### Installscript - - - - -## Hardware - -### RaspberryPi version - - - -### RFID Reader - - - -### Soundcard - - - -### Other notable hardware - - diff --git a/.github/ISSUE_TEMPLATE/bug_v2.yaml b/.github/ISSUE_TEMPLATE/bug_v2.yaml new file mode 100644 index 000000000..a28189815 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_v2.yaml @@ -0,0 +1,98 @@ +name: 🐛 Bug Report v2 +description: Use this template to report bugs for version 2. +title: "🐛 | " +labels: ["bug", "legacy_v2", "needs triage"] +body: + - type: markdown + attributes: + value: > + Please fill out the form to provide essential information to your problem. + This will help us to faster figure out the root cause. + + - type: input + id: version + attributes: + label: Version + description: | + What version do you use? + See the "info" page in the Web App or the "settings/version" file. + placeholder: e.g. "2.5.0" or "2.6.0-alpha" + validations: + required: true + + - type: input + id: branch + attributes: + label: Branch + description: | + What branch did you install from? + See the "info" page in the Web App or the "settings/version" file. + placeholder: e.g. "master" or "develop" + validations: + required: true + + - type: input + id: os + attributes: + label: OS + description: | + What Operation System and Version do you use? + See `cat /etc/os-release` + placeholder: e.g. "Raspberry Pi OS Bullseye lite - 32bit" + validations: + required: true + + - type: input + id: pi-model + attributes: + label: Pi model + description: | + Which Raspberry Pi model do you use? + placeholder: e.g. "3 B+", "Zero 2" + validations: + required: true + + - type: textarea + id: hardware + attributes: + label: Hardware + description: | + What's your hardware set up? + e.g. Rfid Reader, SoundCards, ... + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: | + What you did, what you expected and what happened instead? + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs + description: | + Please provide the logs after the problem occured. + * `/home/pi/phoniebox_logs/install-jukebox.sh_*.log` -> Installation log + * `tail -n 200 /var/log/syslog` (<= Bullseye) or `journalctl -n 200` (>= Bookworm) -> General system log + + You might also want to run the analytics script: + * `/home/pi/RPi-Jukebox-RFID/scripts/helperscripts/Analytics_AfterInstallScript.sh` + + Note: The logs may contain some personal information, You want to erase before sharing. + + - type: textarea + id: config + attributes: + label: Configuration + description: | + Please provide the configuration if its related to the problem + e.g. "mpd.conf", "gpio_settings.ini", ... + + - type: textarea + id: more + attributes: + label: More info + description: Anything more you want to share? diff --git a/.github/ISSUE_TEMPLATE/bug_v3.yaml b/.github/ISSUE_TEMPLATE/bug_v3.yaml new file mode 100644 index 000000000..3dbc45bfb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_v3.yaml @@ -0,0 +1,97 @@ +name: 🐛 Bug Report v3 +description: Use this template to report bugs for version 3. +title: "🐛 | " +labels: ["bug", "future3", "needs triage"] +body: + - type: markdown + attributes: + value: > + Please fill out the form to provide essential information to your problem. + This will help us to faster figure out the root cause. + + - type: input + id: version + attributes: + label: Version + description: | + What version do you use? + See the "settings" page in the Web App or the "src/jukebox/jukebox/version.py" file. + placeholder: e.g. "3.5.0" or "3.6.0-alpha" + validations: + required: true + + - type: input + id: branch + attributes: + label: Branch + description: | + What branch did you install from? + See the "settings" page in the Web App or the "src/jukebox/jukebox/version.py" file. + placeholder: e.g. "future3/main" or "future3/develop" + validations: + required: true + + - type: input + id: os + attributes: + label: OS + description: | + What Operation System and Version do you use? + See `cat /etc/os-release` + placeholder: e.g. "Raspberry Pi OS Bullseye lite - 32bit" + validations: + required: true + + - type: input + id: pi-model + attributes: + label: Pi model + description: | + Which Raspberry Pi model do you use? + placeholder: e.g. "3 B+", "Zero 2" + validations: + required: true + + - type: textarea + id: hardware + attributes: + label: Hardware + description: | + What's your hardware set up? + e.g. Rfid Reader, SoundCards, ... + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: | + What you did, what you expected and what happened instead? + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs + description: | + Please provide the logs after the problem occured. + - `~/INSTALL-XXXXXXXXX.log` -> Installation log + - `~/RPi-Jukebox-RFID/shared/logs/*.log` -> General Jukebox app and error logs + General Jukebox logs can also be found at `http://ip.of.your.box/logs/*.log` + + Note: The logs may contain some personal information, You want to erase before sharing. + + + - type: textarea + id: config + attributes: + label: Configuration + description: | + Please provide the configuration if its related to the problem + e.g. "mpd.conf", "jukebox.yaml", ... + + - type: textarea + id: more + attributes: + label: More info + description: Anything more you want to share? diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e8ce1a65d..e9ad7441d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,5 +4,5 @@ contact_links: url: https://github.com/MiczFlor/RPi-Jukebox-RFID/discussions/new?category=q-a about: This issue tracker is not for support questions. Please refer to our Discussions. - name: đŸ’Ŧ Chat - url: https://matrix.to/#/#phoniebox_community:gitter.im + url: https://matrix.to/#/#phoniebox_community:matrix.org about: Want to discuss with others? Check out our chat. diff --git a/.github/ISSUE_TEMPLATE/feature_template.md b/.github/ISSUE_TEMPLATE/feature_template.md deleted file mode 100644 index eb0e22967..000000000 --- a/.github/ISSUE_TEMPLATE/feature_template.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: feature request -about: use this template to propose new features -title: "\U0001F680 | FEATURE SUMMARY" -labels: enhancement ---- - -## Feature Description - -### What functionality would you like to see in your phoniebox? - -i.e. `I would love to cook my breakfast eggs with the help of our phoniebox` - -### How do you envision the feature to work from a users perspective? - -i.e. `In the mornings when I stumble to the bathroom, I want the phoniebox to react on the RFID chip injected under the skin of my right hip. I envision it to walk to the kitchen, get the eggs and a pot out, and start the cooking of the eggs on the stove.` - -### Further information that might help - -i.e. `the stove is operated with cooking gas, so the implemenation has to make sure the stove is handled with the greatest possible care` diff --git a/.github/ISSUE_TEMPLATE/feature_v3.yaml b/.github/ISSUE_TEMPLATE/feature_v3.yaml new file mode 100644 index 000000000..712fa0c87 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_v3.yaml @@ -0,0 +1,38 @@ +name: 🚀 Feature Request v3 +description: Use this template to propose feature requests for version 3. +title: "🚀 | " +labels: ["enhancement", "future3"] +body: + - type: markdown + attributes: + value: > + Please describe your new functionality. + + - type: textarea + id: feature + attributes: + label: Feature + description: | + What functionality would you like to see in your phoniebox? + placeholder: | + e.g. `I would love to cook my breakfast eggs with the help of our phoniebox` + validations: + required: true + + - type: textarea + id: perspective + attributes: + label: User perspective + description: | + How do you envision the feature to work from a users perspective? + placeholder: | + e.g. `In the mornings when I stumble to the bathroom, I want the phoniebox to react on the RFID chip injected under the skin of my right hip. I envision it to walk to the kitchen, get the eggs and a pot out, and start the cooking of the eggs on the stove.` + + - type: textarea + id: further-info + attributes: + label: Further information + description: | + Further information that might help. + placeholder: | + e.g. `The stove is operated with cooking gas, so the implemenation has to make sure the stove is handled with the greatest possible care` diff --git a/.github/ISSUE_TEMPLATE/future3.md b/.github/ISSUE_TEMPLATE/future3.md deleted file mode 100644 index ab5d6bf00..000000000 --- a/.github/ISSUE_TEMPLATE/future3.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: future3 Bug Report -about: Use this template to report bugs for the upcoming version 3 -title: "ISSUE SUMMARY on future3" -labels: future3, bug, needs triage ---- - -### Describe your problem - -Core, Web application ... - -#### What's your hardware set up? - -RPi version, RFID Reader, Audio devices etc. - -#### If possible, try to attach logs from ... (paths from RPi) - - * `~/RPi-Jukebox-RFID/shared/logs` -> General Jukebox logs - * `~/INSTALL-XXXXXXXXX.log` -> The logfile being generated when installing the Jukebox code diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f895114ef..5ed98fe1b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,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. @@ -50,7 +50,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 @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 128334e9c..e2e056b57 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -20,7 +20,7 @@ jobs: - name: Setup PHP with XDebug uses: shivammathur/setup-php@v2 with: - php-version: 7.3 + php-version: 8.x coverage: xdebug tools: composer @@ -29,7 +29,7 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 6b9a3722c..7b60f2c04 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -20,7 +20,7 @@ jobs: steps: - 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 @@ -29,6 +29,7 @@ jobs: pip install wheel pip install spidev pip install -r requirements.txt + pip install -r requirements-GPIO.txt - name: Lint with flake8 run: | pip install flake8 @@ -37,15 +38,13 @@ jobs: # 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 --cov --cov-report xml + pytest --cov --cov-config=.coveragerc --cov-report xml - name: Report to Coveralls (parallel) uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} - file: ./components/gpio_control/coverage.xml + file: coverage.xml format: cobertura parallel: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d9fcf52b..4370e2d29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: check_abort: ${{ steps.vars.outputs.check_abort }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set Output vars id: vars diff --git a/.github/workflows/test_docker_debian.yml b/.github/workflows/test_docker_debian.yml index d559b40a6..7c929ae76 100644 --- a/.github/workflows/test_docker_debian.yml +++ b/.github/workflows/test_docker_debian.yml @@ -8,6 +8,7 @@ on: branches-ignore: - 'future3/**' paths: + - '.github/workflows/test_docker_debian*.yml' - 'ci/**' - 'misc/sampleconfigs/**' - 'scripts/helperscripts/setup_*' @@ -19,8 +20,9 @@ on: # The branches below must be a subset of the branches above branches: - develop - - main + - master paths: + - '.github/workflows/test_docker_debian*.yml' - 'ci/**' - 'misc/sampleconfigs/**' - 'scripts/helperscripts/setup_*' @@ -37,19 +39,48 @@ concurrency: jobs: - # Build container and run tests - run: - name: ${{ matrix.debian_codename }} - strategy: - fail-fast: false - matrix: - debian_codename: ['bookworm', 'bullseye', 'buster'] + # Build container and run tests. Duplication of job intended for better visualization. + run_bookworm_armv7: + name: 'bookworm armv7' uses: ./.github/workflows/test_docker_debian_codename_sub.yml with: - runs_on: ubuntu-latest + debian_codename: 'bookworm' platform: linux/arm/v7 - docker_image_name: rpi-jukebox-rfid - cache_scope: ${{ github.ref }}-test-debian - matrix_usernames: "['pi', 'hans']" - matrix_test_scripts: "['run_installation_tests.sh', 'run_installation_tests2.sh', 'run_installation_tests3.sh']" - debian_codename: ${{ matrix.debian_codename }} + + # # can be activate on test branches + # run_bookworm_armv6: + # name: 'bookworm armv6' + # uses: ./.github/workflows/test_docker_debian_codename_sub.yml + # with: + # debian_codename: 'bookworm' + # platform: linux/arm/v6 + + run_bullseye_armv7: + name: 'bullseye armv7' + uses: ./.github/workflows/test_docker_debian_codename_sub.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.yml + # with: + # debian_codename: 'bullseye' + # platform: linux/arm/v6 + + run_buster_armv7: + name: 'buster armv7' + uses: ./.github/workflows/test_docker_debian_codename_sub.yml + with: + debian_codename: 'buster' + platform: linux/arm/v7 + + # # can be activate on test branches, currently failing + # run_buster_armv6: + # name: 'buster armv6' + # uses: ./.github/workflows/test_docker_debian_codename_sub.yml + # with: + # debian_codename: 'buster' + # platform: linux/arm/v6 diff --git a/.github/workflows/test_docker_debian_codename_sub.yml b/.github/workflows/test_docker_debian_codename_sub.yml index b018afaf5..c41a880d4 100644 --- a/.github/workflows/test_docker_debian_codename_sub.yml +++ b/.github/workflows/test_docker_debian_codename_sub.yml @@ -3,36 +3,33 @@ name: Subworkflow Test Install Scripts Debian on: workflow_call: inputs: - runs_on: - required: true - type: string - platform: - required: true - type: string debian_codename: required: true type: string - cache_scope: + platform: required: true type: string docker_image_name: - required: true - type: string - matrix_usernames: - required: true + required: false type: string - matrix_test_scripts: - required: true + default: rpi-jukebox-rfid + cache_scope: + required: false type: string + default: ${{ github.ref }}-test-debian 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 }} + group: ${{ inputs.cache_scope }}-${{ inputs.debian_codename }}-${{ inputs.platform }} cancel-in-progress: true jobs: @@ -45,6 +42,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: @@ -60,7 +58,7 @@ jobs: uses: docker/setup-qemu-action@v3.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + uses: docker/setup-buildx-action@v3.1.0 with: # network=host driver-opt needed to push to local registry driver-opts: network=host @@ -71,15 +69,18 @@ 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 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 @@ -87,6 +88,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 @@ -124,7 +128,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 }} @@ -139,18 +143,22 @@ jobs: strategy: fail-fast: false matrix: - username: ${{ fromJSON(inputs.matrix_usernames) }} - test_script: ${{ fromJSON(inputs.matrix_test_scripts) }} + username: ['pi'] + test_script: ['run_installation_classic.sh', 'run_installation_rfid.sh', 'run_installation_spotify.sh', 'run_installation_staticip_dhcpcd.sh', 'run_installation_autohotspot_dhcpcd.sh', 'run_installation_autohotspot_NetworkManager.sh'] + include: + - username: 'hans' + test_script: 'run_installation_classic.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 + uses: docker/setup-buildx-action@v3.1.0 - name: Artifact Download Docker Image - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ needs.build.outputs.image_file_name }} @@ -163,7 +171,7 @@ jobs: uses: tj-actions/docker-run@v2 with: image: ${{ needs.build.outputs.image_tag_name }} - options: --platform ${{inputs.platform }} --user ${{ matrix.username }} + options: ${{ needs.build.outputs.docker_run_options }} --platform ${{ inputs.platform }} --user ${{ matrix.username }} --init name: ${{ matrix.test_script }} args: | ./${{ matrix.test_script }} @@ -174,9 +182,10 @@ jobs: 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 }} + - name: Artifact Delete Docker Image + uses: geekyeggo/delete-artifact@v5 + with: + name: ${{ needs.build.outputs.image_file_name }} + failOnError: false diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index dfd857be8..61119ba88 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,7 @@ # Contributor Covenant Code of Conduct -> 📝**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 +> [!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 This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 889233f70..6ac2d37c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,7 @@ # Contributing -> 📝**NOTE:** This describes contributing for **version 2.x**. For Version 3 please see +> [!NOTE] +> This describes contributing for **version 2.x**. For Version 3 please see ## How to contribute diff --git a/README.md b/README.md index 49cb2752b..c514a2436 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ [![Coverage Status](https://coveralls.io/repos/github/MiczFlor/RPi-Jukebox-RFID/badge.svg?branch=develop)](https://coveralls.io/github/MiczFlor/RPi-Jukebox-RFID?branch=develop) -[![Gitter chat](https://badges.gitter.im/join_chat.svg)](https://matrix.to/#/#phoniebox_community:gitter.im) -[![Matrix chat](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#phoniebox_community:gitter.im) - +[![Matrix chat](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#phoniebox_community:matrix.org) ## Introduction @@ -27,19 +25,19 @@ We are working to provide an integrated solution. For Version 2 there is a possible fix to reactivate spotify with a manual installation described [here](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1815#issuecomment-1666535983). For Version 3 [#2164](https://github.com/MiczFlor/RPi-Jukebox-RFID/pull/2164) is laying the groundwork. Help is greatly appreciated. -## The Phoniebox Calendar 2023 is here +## The new Phoniebox Calendar is here -Another bunch of wonderful designs! 2023 is the fifth Phoniebox calendar. If you are interested, you can see the [2019, 2020, 2021 and 2022 calendars in the docs folder](docs). Download [the printable PDF of 2023 here](https://mi.cz/static/2023-Phoniebox-Calendar.pdf). +Another bunch of wonderful designs! -![The 2023 Phoniebox Calendar](docs/2023-Phoniebox-Calendar.jpg "The 2023 Phoniebox Calendar") +To share your design or see all previous calendars and designs of the community visit the [Phoniebox Gallery](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/GALLERY). -If you want to be featured on next years calendar, please make sure to add your Phoniebox pics to the [design thread here on github](https://github.com/MiczFlor/RPi-Jukebox-RFID/discussions/2115). +![The Phoniebox Calendar](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/calendar/latest-Phoniebox-Calendar.jpg "The Phoniebox Calendar") ## Install Phoniebox ### đŸ”Ĩ Version 3 is coming -The goal for Version 3 was to tidy up the codebase, focus on a single programming language for the core (Python), establish a solid plugin system and build a responsive web client. [Read on here if you want to learn about more reasons](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/documentation/README.md). +The goal for Version 3 was to tidy up the codebase, focus on a single programming language for the core (Python), establish a solid plugin system and build a responsive web client. [Read on here if you want to learn about more reasons](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/documentation/README.md). #### 👋 Looking for adopters, testers and contributors @@ -50,27 +48,29 @@ While Version 3 is still under development, it is becoming a lot more stable! Al If you seek the adventure, your support will be more then welcome. Before contributing, check out the following references. * ⭐ **[Releases](https://github.com/MiczFlor/RPi-Jukebox-RFID/releases?q=v3&expanded=true)** -* 🚀 **[Install Jukebox Version 3 Beta](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/documentation/builders/installation.md)** -* 🐛 [Report a bug](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/new?assignees=&labels=future3%2C+bug%2C+needs+triage&template=future3.md&title=ISSUE+SUMMARY+on+future3) -* ☑ī¸ [Feature Status](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/documentation/developers/status.md) -* 📖 [Documentation](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/documentation/README.md) -* 👩‍đŸ’ģ [Development](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/documentation/developers/README.md) +* đŸŽĩ **[Install Jukebox Version 3](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/documentation/builders/installation.md)** +* 🐛 [Report a bug](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/new?assignees=&labels=bug%2Cfuture3%2Cneeds+triage&projects=&template=bug_v3.yaml&title=%F0%9F%90%9B+%7C+) +* 🚀 [Propose a feature](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/new?assignees=&labels=enhancement%2Cfuture3&projects=&template=feature_v3.yaml&title=%F0%9F%9A%80+%7C+) +* ☑ī¸ [Feature Status](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/documentation/developers/status.md) +* 📖 [Documentation](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/documentation/README.md) +* 👩‍đŸ’ģ [Development](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/documentation/developers/README.md) * đŸĻ„ Code: [Release Branch](https://github.com/MiczFlor/RPi-Jukebox-RFID/tree/future3/main), [Development Branch](https://github.com/MiczFlor/RPi-Jukebox-RFID/tree/future3/develop) --- -### đŸŽĩ Version 2 +### đŸŽļ Version 2 > [!NOTE] > Version 3 is becoming mature and will soon be the new default of Phoniebox. Therefore Version 2 is slowly going into a maintenance mode and no new features will be added to this version. Check out the following references. - * ⭐ **[Releases](https://github.com/MiczFlor/RPi-Jukebox-RFID/releases?q=v2&expanded=true)** - * 🚀 **[Install Jukebox Version 2](#quick-install-version-2)** - * 🐛 [Report a bug](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/new?assignees=&labels=bug%2C+needs+triage&projects=&template=bug_template.md&title=%F0%9F%90%9B+%7C+BUG+SUMMARY) - * ☑ī¸ [Features](#features-version-2) - * 📖 [Documentation](#documentation) +* ⭐ **[Releases](https://github.com/MiczFlor/RPi-Jukebox-RFID/releases?q=v2&expanded=true)** +* đŸŽĩ **[Install Jukebox Version 2](#quick-install-version-2)** +* 🐛 [Report a bug](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/new?assignees=&labels=bug%2Clegacy_v2%2Cneeds+triage&projects=&template=bug_v2.yaml&title=%F0%9F%90%9B+%7C+) +* ☑ī¸ [Features](#features-version-2) +* 📖 [Documentation](#documentation) +* đŸĻ„ Code: [Release Branch](https://github.com/MiczFlor/RPi-Jukebox-RFID/tree/master), [Development Branch](https://github.com/MiczFlor/RPi-Jukebox-RFID/tree/develop) #### Features Version 2 @@ -117,7 +117,7 @@ Check out the following references. * Please ask **general questions** in [Discussions](https://github.com/MiczFlor/RPi-Jukebox-RFID/discussions) * **Bugs and enhancements** should still be in [Issues](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues). -* We got ourselves a gitter community; **chat us up** at [Gitter/Matrix](https://matrix.to/#/#phoniebox_community:gitter.im) +* Want to discuss with others? Check out our [Matrix community](https://matrix.to/#/#phoniebox_community:matrix.org) ## Documentation @@ -147,20 +147,6 @@ There is a growing section of [troubleshooting](https://github.com/MiczFlor/RPi- * Everything seems to work, but I hear nothing when swiping a card. * I would like to use two cards / IDs to do the same thing. -### Reporting Bugs - -To make maintenance easier for everyone, please run the following script and post the results when reporting a bug. -(Note: the results contain some personal information like IP or SSID. -You might want to erase some of it before sharing with the bug report.) - -~~~bash -/home/pi/RPi-Jukebox-RFID/scripts/helperscripts/Analytics_AfterInstallScript.sh -~~~ - -Just copy this line and paste it into your terminal on the pi. - -If you find something that doesn't work. And you tried and tried again, but it still doesn't work, please report your issue in the ["issues" section](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues). - ## Hardware aka Shopping List Here is a list of equipment needed. You can find a lot second hand online (save money and the planet). The links below lead to Amazon, not at all because I want to support them, but because their PartnerNet program helps to support the Phoniebox maintenance (a little bit...). @@ -223,7 +209,7 @@ or donate via [PayPal](https://www.paypal.com) to using the * ### Videos and Screenshots -Prototype of the RFID jukebox +Prototype of the RFID jukebox See the Phoniebox code in action, watch this video and read the blog post from [iphone-ticker.de](https://www.iphone-ticker.de/wochenend-projekt-kontaktlose-musikbox-fuer-kinder-123063/) @@ -246,7 +232,7 @@ A new video screencast about * Connect to your Phoniebox via your wifi network or run the Phoniebox like an access point and connect directly without a router. * **Bonus:** control the Phoniebox from your phone or computer via a web app. -![The web app allows you to change the volume level, list and play audio files and folders, stop the player and shut down the RPi gracefully.](docs/img/web-app-iphone-screens.jpg "The web app allows you to change the volume level, list and play audio files and folders, stop the player and shut down the RPi gracefully.") +![The web app allows you to change the volume level, list and play audio files and folders, stop the player and shut down the RPi gracefully.](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/web-app-iphone-screens.jpg "The web app allows you to change the volume level, list and play audio files and folders, stop the player and shut down the RPi gracefully.") The **web app** runs on any device and is mobile optimised. It provides: @@ -258,11 +244,15 @@ The **web app** runs on any device and is mobile optimised. It provides: ### Phoniebox Gallery -| | | | | | | -| --- | --- | --- | --- | --- | --- | -| ![Caption](docs/img/gallery/Steph-20171215_h90-01.jpg "Caption") | ![Caption](docs/img/gallery/Elsa-20171210_h90-01.jpg "Caption") | ![Caption](docs/img/gallery/Geliras-20171228-Jukebox-01-h90.jpg "Caption") | ![Caption](docs/img/gallery/UlliH-20171210_h90-01.jpg "Caption") | ![Caption](docs/img/gallery/KingKahn-20180101-Jukebox-01-h90.jpg "Caption") | ![Caption](docs/img/gallery/hailogugo-20171222-h90-01.jpg "Caption") | +See innovation, upcycling and creativity in the [Phoniebox Gallery](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/GALLERY) + +![Caption](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/Steph-20171215_h90-01.jpg) +![Caption](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/Elsa-20171210_h90-01.jpg) +![Caption](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/Geliras-20171228-Jukebox-01-h90.jpg) +![Caption](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/UlliH-20171210_h90-01.jpg) +![Caption](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/KingKahn-20180101-Jukebox-01-h90.jpg) +![Caption](https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/hailogugo-20171222-h90-01.jpg) -**See more innovation, upcycling and creativity in the [Phoniebox Gallery](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/GALLERY) or visit and share the project's homepage at [phoniebox.de](https://phoniebox.de/). There is also an [english Phoniebox page](https://phoniebox.de/index-en.html).** ## Sustainability diff --git a/ci/Dockerfile.debian b/ci/Dockerfile.debian index eb545e6b3..45c44d350 100644 --- a/ci/Dockerfile.debian +++ b/ci/Dockerfile.debian @@ -5,7 +5,6 @@ FROM debian:${DEBIAN_CODENAME}-slim as base ARG DEBIAN_CODENAME ENV DOCKER_RUNNING=true -RUN touch /boot/cmdlinetxt RUN export DEBIAN_FRONTEND=noninteractive \ && echo "--- install packages (1) ---" \ @@ -84,6 +83,7 @@ FROM test-user as test-code ARG GIT_BRANCH ARG GIT_URL +ENV CI_RUNNING=true ENV GIT_BRANCH=$GIT_BRANCH GIT_URL=$GIT_URL COPY --chown=root:$TEST_USER_GROUP --chmod=770 packages.txt packages-raspberrypi.txt ./ diff --git a/components/audio/PirateAudioHAT/README.md b/components/audio/PirateAudioHAT/README.md index 82a02396b..ccf0d61ad 100644 --- a/components/audio/PirateAudioHAT/README.md +++ b/components/audio/PirateAudioHAT/README.md @@ -1,88 +1,16 @@ # How to setup a Pimoroni PirateAudio HAT -These instructions are for the following Pimoroni PirateAudio HATs: +> [!NOTE] +> The display of the HATs currently only work with the 'spotify' edition, not with 'classic'! +> See [here](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1109) - +These instructions are for the [Pimoroni PirateAudio HATs](https://shop.pimoroni.com/collections/audio?q=pirate%20audio) -The PirateAudio HATs use the same DAC as the hifiberry, so some of the instructions -from can be applied as well. +## Installation -The `setup_pirateAudioHAT.sh` script can be used to set it up to work with Phoniebox. +Run the `setup_pirateAudioHAT.sh` script to set it up to work with Phoniebox. -## Install steps in writing +## Troubleshooting -(Discussions regarding *Pirate Audio HAT* should take place in the same thread where the below instructions were taken from: [#950](https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/950) - -NOTE: changes to the installation should find their way into the script `setup_pirateAudioHAT.sh`. Please create pull requests *after* having tested your changes. :) - -1. Connect Pirate Audio Hat to your Raspberry Pi -2. Install Phoniebox (develop branch!) -3. Stop and disable the GPIO button service: - `sudo systemctl stop phoniebox-gpio-buttons.service` - `sudo systemctl disable phoniebox-gpio-buttons.service` -4. Add the following two lines to /boot/config.txt - `gpio=25=op,dh` - `dtoverlay=hifiberry-dac` -5. Add settings to /etc/asound.conf (create it, if it does not exist yet) - - ```bash - pcm.hifiberry { - type softvol - slave.pcm "plughw:CARD=sndrpihifiberry,DEV=0" - control.name "Master" - control.card 1 - } - pcm.!default { - type plug - slave.pcm "hifiberry" - } - ctl.!default { - type hw - card 1 - } - ``` - -6. Add the following section to /etc/mpd.conf - - ```bash - audio_output { - enabled "yes" - type "alsa" - name "HiFiBerry DAC+ Lite" - device "hifiberry" - auto_resample "no" - auto_channels "no" - auto_format "no" - dop "no" - } - ``` - -7. Set mixer_control name in /etc/mpd.conf - `mixer_control "Master"` -8. Enable SPI - `sudo raspi-config nonint do_spi 0` -9. Install Python dependencies - `sudo apt-get install python3-pil python3-numpy` -10. Install Mopidy plugins - `sudo pip3 install Mopidy-PiDi pidi-display-pil pidi-display-st7789 mopidy-raspberry-gpio` -11. Add the following sections to /etc/mopidy/mopidy.conf: - - ```bash - [raspberry-gpio] - enabled = true - bcm5 = play_pause,active_low,150 - bcm6 = volume_down,active_low,150 - bcm16 = next,active_low,150 - bcm20 = volume_up,active_low,150 - bcm24 = volume_up,active_low,150 - - [pidi] - enabled = true - display = st7789 - ``` - - **Attention:** Early revisions of PirateAudio HAT used bcm20 for Volume up, later revisions use bcm24. see also - -12. Enable access for modipy user - `sudo usermod -a -G spi,i2c,gpio,video mopidy` -13. Reboot Raspberry Pi +The PirateAudio HATs use the same DAC as the HiFiBerry, so some of the instructions +from [HiFiBerry Soundcard Details](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/HiFiBerry-Soundcard-Details) can be applied as well. diff --git a/components/audio/PirateAudioHAT/setup_pirateAudioHAT.sh b/components/audio/PirateAudioHAT/setup_pirateAudioHAT.sh index a6152a0ca..97c91194b 100755 --- a/components/audio/PirateAudioHAT/setup_pirateAudioHAT.sh +++ b/components/audio/PirateAudioHAT/setup_pirateAudioHAT.sh @@ -3,6 +3,8 @@ HOME_DIR="/home/pi" JUKEBOX_HOME_DIR="${HOME_DIR}/RPi-Jukebox-RFID" +source "${JUKEBOX_HOME_DIR}"/scripts/helperscripts/inc.systemHelper.sh + question() { local question=$1 read -p "${question} (y/n)? " choice @@ -16,22 +18,24 @@ question() { printf "Please make sure that the Pirate Audio HAT is connected...\n" question "Continue" -printf "Stopping and disabling GPIO button service...\n" +printf "Stopping and disabling GPIO control service...\n" #TODO this might not be necessary -sudo systemctl stop phoniebox-gpio-buttons.service -sudo systemctl disable phoniebox-gpio-buttons.service - -printf "Adding settings to /boot/config.txt...\n" -if [[ ! -f /boot/config.txt.bak ]]; then - sudo cp /boot/config.txt /boot/config.txt.bak +sudo systemctl stop phoniebox-gpio-control.service +sudo systemctl disable phoniebox-gpio-control.service + +boot_config_path=$(get_boot_config_path) +boot_config_path_backup="${boot_config_path}.bak" +printf "Adding settings to ${boot_config_path}...\n" +if [[ ! -f "${boot_config_path_backup}" ]]; then + sudo cp "${boot_config_path}" "${boot_config_path_backup}" fi # Only add the two lines, if they do not exist already -if ! sudo grep -qe "gpio=25=op,dh" /boot/config.txt; then - echo "gpio=25=op,dh" | sudo tee -a /boot/config.txt > /dev/null +if ! sudo grep -qe "gpio=25=op,dh" "${boot_config_path}"; then + echo "gpio=25=op,dh" | sudo tee -a "${boot_config_path}" > /dev/null fi -if ! sudo grep -qe "dtoverlay=hifiberry-dac" /boot/config.txt; then - echo "dtoverlay=hifiberry-dac" | sudo tee -a /boot/config.txt > /dev/null +if ! sudo grep -qe "dtoverlay=hifiberry-dac" "${boot_config_path}"; then + echo "dtoverlay=hifiberry-dac" | sudo tee -a "${boot_config_path}" > /dev/null fi printf "Adding settings to /etc/asound.conf...\n" diff --git a/components/bluetooth-sink-switch/README.md b/components/bluetooth-sink-switch/README.md index 8e5c9cec3..572bde2a1 100644 --- a/components/bluetooth-sink-switch/README.md +++ b/components/bluetooth-sink-switch/README.md @@ -148,7 +148,7 @@ CMDBLUETOOTHTOGGLE="1364237231134" Speakers and Headphones can have very different maximum volume levels. This sometimes leads to very strong volume level changes when switching between speakers and headphones. Restricting the maximum volume with the Phoniebox-integrated max-volume setting does no yield the desired effect, as this is a single setting and does not differentiate between different audio sinks. -The solution is adding a `softvol` component to the /etc/asound.conf. You may already have one set up, if your soundcard does not have a hardware volume control. Then it is easy! The `softvol` copmonent adds a systemwide ALSA-based volume control for a hardware soundcard. You will need to give it a name, that does **not** exist! Check with `$ amixer scontrols` first, which names are already taken. Here, I have choosen *Master*. This will work even if your soundcard has a hardware volume control. +The solution is adding a `softvol` component to the /etc/asound.conf. You may already have one set up, if your soundcard does not have a hardware volume control. Then it is easy! The `softvol` copmonent adds a systemwide ALSA-based volume control for a hardware soundcard. You will need to give it a name, that does **not** exist! Check with `$ sudo amixer scontrols` first, which names are already taken. Here, I have choosen *Master*. This will work even if your soundcard has a hardware volume control. The `softvol` component has a feature called *max_db* to limit the maximum volume, which we are going to utilize here. With that we are limiting the maximum volume of the speakers systemwide and independent of MPD or other Phoniebox settings. @@ -204,7 +204,7 @@ $ speaker-test -D hifiberry and changing the default volume control in another console ~~~bash -$ alsamixer +$ sudo alsamixer ~~~ If you are experimenting with a softvol and want to get rid of it again - that is not an easy task. Most promising approach is to insert the SD-Card into a different Linux machine delete the file `/var/lib/alsa/asound.state`. This must be done from a different computer, as this file gets written during shutdown. More infos about the softvol may be found [here](https://alsa.opensrc.org/Softvol) diff --git a/components/controls/buttons_usb_encoder/README.md b/components/controls/buttons_usb_encoder/README.md index 586850e8a..6ef670e25 100644 --- a/components/controls/buttons_usb_encoder/README.md +++ b/components/controls/buttons_usb_encoder/README.md @@ -27,27 +27,7 @@ If you make a mistake at the first install you can "remap" the buttons: ### Possible Button Mappings -* 'BluetoothToggle': Turn bluetooth on or off -* 'PlayerNext': Play Next -* 'PlayerPause': Pause -* 'PlayerPauseForce': Force Pause? -* 'PlayerPrev': Previous track -* 'PlayerRandomCard': Activate (play?) random card? -* 'PlayerRandomFolder': Play random folder -* 'PlayerRandomTrack': Play random track -* 'PlayerSeekBack': Seek backwards on playing track (x seconds) -* 'PlayerSeekFarBack': Seek backwards on playing track (x seconds) -* 'PlayerSeekFarFwd': Seek forwards on playing track (x seconds) -* 'PlayerSeekFwd': Seek forwards on playing track (x seconds) -* 'PlayerStop': Stop playing -* 'RecordPlayLatest': Play latest recording -* 'RecordStart': Start recording -* 'RecordStop': Stop recording -* 'Shutdown': Shut down the Phoniebox -* 'ToggleWifi': Turn Wifi on or off -* 'Vol0': Volume mute? -* 'VolD': Volume down -* 'VolU': Volume up +See the [GPIO documentation](../../gpio_control/README.md#functions) for possible mappings of functions and args. ## Schematics diff --git a/components/controls/buttons_usb_encoder/buttons_usb_encoder.py b/components/controls/buttons_usb_encoder/buttons_usb_encoder.py index 33d13657c..e8fa807a9 100644 --- a/components/controls/buttons_usb_encoder/buttons_usb_encoder.py +++ b/components/controls/buttons_usb_encoder/buttons_usb_encoder.py @@ -25,8 +25,9 @@ button_string = '-'.join(sorted(button_string)) try: function_name = button_map[button_string] + function_args = button_map[button_string + "_args"] try: - getattr(function_calls, function_name)() + getattr(function_calls, function_name)(function_args) except: logger.warning( "Function " + function_name + " not found in function_calls.py (mapped from button: " + button_string + ")") diff --git a/components/controls/buttons_usb_encoder/map_buttons_usb_encoder.py b/components/controls/buttons_usb_encoder/map_buttons_usb_encoder.py index 6cf0bcb28..394564de6 100644 --- a/components/controls/buttons_usb_encoder/map_buttons_usb_encoder.py +++ b/components/controls/buttons_usb_encoder/map_buttons_usb_encoder.py @@ -21,13 +21,13 @@ print("During the next step you can map your buttons to one of the following available functions:") print(list(map(lambda function_name: function_name.replace("functionCall", ""), functions))) print("") - if input('Ready to continue? (y/n)') != 'y': + if input('Ready to continue? (Y/n)') == 'n': sys.exit("Aborted mapping buttons to functions") for function_name in functions: function_name_short = function_name.replace("functionCall", "") print("") - print("Press button to map " + function_name_short + " or press ctrl+c to skip this function") + print("Press button to map '" + function_name_short + "' or press ctrl+c to skip this function") try: for event in current_device().read_loop(): if event.type == ecodes.EV_KEY: @@ -36,8 +36,14 @@ button_string = keyevent.keycode if type(button_string) is list: button_string = '-'.join(sorted(button_string)) + + function_args = input('Type argument for function (just enter for none): ').strip() + if not function_args: + function_args = None + button_map[button_string] = function_name - print("Button " + button_string + " is now mapped to " + function_name_short) + button_map[button_string + "_args"] = function_args + print("Button '" + button_string + "' is now mapped to '" + function_name_short + "' with argument '" + str(function_args) + "'") break except KeyboardInterrupt: continue diff --git a/components/gpio_control/GPIODevices/__init__.py b/components/gpio_control/GPIODevices/__init__.py index 5eaad3009..efdd6b5c5 100644 --- a/components/gpio_control/GPIODevices/__init__.py +++ b/components/gpio_control/GPIODevices/__init__.py @@ -2,5 +2,4 @@ from .two_button_control import TwoButtonControl from .shutdown_button import ShutdownButton from .simple_button import SimpleButton -from .two_button_control import TwoButtonControl -from .led import * +from .led import LED, StatusLED diff --git a/components/gpio_control/GPIODevices/led.py b/components/gpio_control/GPIODevices/led.py index 01d2d34ef..b6a51eb4c 100644 --- a/components/gpio_control/GPIODevices/led.py +++ b/components/gpio_control/GPIODevices/led.py @@ -13,9 +13,10 @@ class LED: def __init__(self, pin, initial_value=True, name='LED'): self.pin = pin self.name = name + self.initial_value = initial_value logger.debug('initialize {}(pin={}) to off'.format(self.name, self.pin)) GPIO.setup(self.pin, GPIO.OUT) - GPIO.output(self.pin, initial_value) + GPIO.output(self.pin, self.initial_value) def on(self): logger.debug('Set Output of {}(pin={}) to on'.format(self.name, self.pin)) diff --git a/components/gpio_control/GPIODevices/rotary_encoder.py b/components/gpio_control/GPIODevices/rotary_encoder.py index d3455622f..8254a37b0 100755 --- a/components/gpio_control/GPIODevices/rotary_encoder.py +++ b/components/gpio_control/GPIODevices/rotary_encoder.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python3 -# rotary volume knob - import RPi.GPIO as GPIO from timeit import default_timer as timer import ctypes import logging -from signal import pause logger = logging.getLogger(__name__) @@ -131,14 +127,16 @@ def _Callback(self, pin): logger.debug('Ignoring encoderState: "{}"'.format(self.encoderState.asByte)) -if __name__ == "__main__": - logging.basicConfig(level='INFO') - GPIO.setmode(GPIO.BCM) - pin1 = int(input('please enter first pin')) - pin2 = int(input('please enter second pin')) - func1 = lambda *args: print('Function Incr executed with {}'.format(args)) - func2 = lambda *args: print('Function Decr executed with {}'.format(args)) - rotarty_encoder = RotaryEncoder(pin1, pin2, func1, func2) +# Umcomment for manual tests +# if __name__ == "__main__": +# from signal import pause +# logging.basicConfig(level='INFO') +# GPIO.setmode(GPIO.BCM) +# pin1 = int(input('please enter first pin')) +# pin2 = int(input('please enter second pin')) +# func1 = lambda *args: print('Function Incr executed with {}'.format(args)) +# func2 = lambda *args: print('Function Decr executed with {}'.format(args)) +# rotarty_encoder = RotaryEncoder(pin1, pin2, func1, func2) - print('running') - pause() +# print('running') +# pause() diff --git a/components/gpio_control/GPIODevices/shutdown_button.py b/components/gpio_control/GPIODevices/shutdown_button.py index 3f9eec341..bd28547eb 100644 --- a/components/gpio_control/GPIODevices/shutdown_button.py +++ b/components/gpio_control/GPIODevices/shutdown_button.py @@ -34,6 +34,17 @@ def set_led(self, status): logger.debug('cannot set LED to {}: no LED pin defined'.format(status)) def callbackFunctionHandler(self, *args): + if len(args) > 0 and args[0] == self.pin and not self.callback_with_pin_argument: + logger.debug('Remove pin argument by callbackFunctionHandler - args before: {}'.format(args)) + args = args[1:] + logger.debug('args after: {}'.format(args)) + + if self.antibouncehack: + time.sleep(0.1) + inval = GPIO.input(self.pin) + if inval != GPIO.LOW: + return None + if self.is_pressed: # Should not be necessary, but handler gets called on rising edge too logger.debug('ShutdownButton pressed, ensuring long press for {} seconds, checking each {}s'.format( self.hold_time, self.iteration_time @@ -58,6 +69,7 @@ def callbackFunctionHandler(self, *args): self.set_led(GPIO.LOW) def __repr__(self): - return ''.format( - self.name, self.pin, self.hold_time, self.iteration_time, self.led_pin, print_edge_key(self.edge), self.bouncetime, self.antibouncehack, print_pull_up_down(self.pull_up_down) - ) + return ('').format( + self.name, self.pin, self.hold_time, self.iteration_time, self.led_pin, print_edge_key(self.edge), + self.bouncetime, self.antibouncehack, print_pull_up_down(self.pull_up_down)) diff --git a/components/gpio_control/GPIODevices/simple_button.py b/components/gpio_control/GPIODevices/simple_button.py index 4b7149e7e..a022a812f 100644 --- a/components/gpio_control/GPIODevices/simple_button.py +++ b/components/gpio_control/GPIODevices/simple_button.py @@ -1,5 +1,4 @@ import time -from signal import pause import logging import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) @@ -60,7 +59,8 @@ def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState): while True: time.sleep(0.1) currentState = GPIO.input(gpioChannel) - if holdingTime < (time.perf_counter() - startTime): + endTime = time.perf_counter() - startTime + if holdingTime < endTime: break # Return if state does not match holding state if (gpioHoldingState != currentState): @@ -74,15 +74,17 @@ def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState): class SimpleButton: def __init__(self, pin, action=lambda *args: None, action2=lambda *args: None, name=None, - bouncetime=500, antibouncehack=False, edge='falling', hold_time=.3, hold_mode=None, pull_up_down='pull_up'): + bouncetime=500, antibouncehack=False, edge='falling', + hold_time=.3, hold_mode=None, pull_up_down='pull_up'): + self.pin = pin + self.name = name self.edge = parse_edge_key(edge) self.hold_time = hold_time self.hold_mode = hold_mode + # TODO is pull_up always true a bug? self.pull_up = True self.pull_up_down = parse_pull_up_down(pull_up_down) - self.pin = pin - self.name = name self.bouncetime = bouncetime self.antibouncehack = antibouncehack GPIO.setup(self.pin, GPIO.IN, pull_up_down=self.pull_up_down) @@ -122,13 +124,15 @@ def when_held(self): @when_pressed.setter def when_pressed(self, func): - logger.info('{}: set when_pressed') + logger.info('{}: set when_pressed'.format(self.name)) self._action = func GPIO.remove_event_detect(self.pin) logger.info('add new action') - GPIO.add_event_detect(self.pin, edge=self.edge, callback=self.callbackFunctionHandler, bouncetime=self.bouncetime) + GPIO.add_event_detect(self.pin, edge=self.edge, callback=self.callbackFunctionHandler, + bouncetime=self.bouncetime) + @DeprecationWarning def set_callbackFunction(self, callbackFunction): self.when_pressed = callbackFunction @@ -169,19 +173,24 @@ def __del__(self): @property def is_pressed(self): + # TODO should this be 'if pull_up_down == GPIO.PUD_UP'? pull_up is always true! if self.pull_up: return not GPIO.input(self.pin) return GPIO.input(self.pin) def __repr__(self): - return ''.format( - self.name, self.pin, print_edge_key(self.edge), self.hold_mode, self.hold_time, self.bouncetime, self.antibouncehack, print_pull_up_down(self.pull_up_down) - ) - - -if __name__ == "__main__": - print('please enter pin no to test') - pin = int(input()) - func = lambda *args: print('FunctionCall with {}'.format(args)) - btn = SimpleButton(pin=pin, action=func, hold_mode='Repeat') - pause() + return ('').format( + self.name, self.pin, print_edge_key(self.edge), self.hold_mode, self.hold_time, + self.bouncetime, self.antibouncehack, print_pull_up_down(self.pull_up_down)) + +# Uncomment for manual tests +# if __name__ == "__main__": +# from signal import pause +# print('please enter pin no to test') +# pin = int(input()) +# func = lambda *args: print('FunctionCall with {}'.format(args)) +# btn = SimpleButton(pin=pin, action=func, hold_mode='Repeat') + +# print('running') +# pause() diff --git a/components/gpio_control/GPIODevices/two_button_control.py b/components/gpio_control/GPIODevices/two_button_control.py index e93584cd1..c7a1cb190 100644 --- a/components/gpio_control/GPIODevices/two_button_control.py +++ b/components/gpio_control/GPIODevices/two_button_control.py @@ -111,23 +111,24 @@ def __init__(self, def __repr__(self): two_btns_action = self.functionCallTwoBtns is not None - return ''.format( + return ('').format( self.name, self.bcmPin1, self.bcmPin2, two_btns_action, self.hold_mode, self.hold_time, print_edge_key(self.edge), self.bouncetime, self.antibouncehack, - print_pull_up_down(self.pull_up_down) - ) + print_pull_up_down(self.pull_up_down)) -if __name__ == "__main__": - logging.basicConfig(level='INFO') - pin1 = int(input('please enter first pin')) - pin2 = int(input('please enter second pin')) - func1 = lambda *args: print('Function Btn1 executed with {}'.format(args)) - func2 = lambda *args: print('Function Btn2 executed with {}'.format(args)) - func3 = lambda *args: print('Function BothBtns executed with {}'.format(args)) - two_btn_control = TwoButtonControl(pin1, pin2, func1, func2, func3) +# Uncomment for manual tests +# if __name__ == "__main__": +# from signal import pause +# logging.basicConfig(level='INFO') +# pin1 = int(input('please enter first pin')) +# pin2 = int(input('please enter second pin')) +# func1 = lambda *args: print('Function Btn1 executed with {}'.format(args)) +# func2 = lambda *args: print('Function Btn2 executed with {}'.format(args)) +# func3 = lambda *args: print('Function BothBtns executed with {}'.format(args)) +# two_btn_control = TwoButtonControl(pin1, pin2, func1, func2, func3) - print('running') - while True: - pass +# print('running') +# pause() diff --git a/components/gpio_control/README.md b/components/gpio_control/README.md index 229d120a8..8661e8d27 100644 --- a/components/gpio_control/README.md +++ b/components/gpio_control/README.md @@ -19,20 +19,20 @@ Up to now the following input devices are implemented: * **Button**: A simple button with optional long-press actions like hold and repeat functionality or delayed action. - Its main parameters are: `Pin` (use GPIO number here) and `functionCall`. For additional options, see [extended documentation below](#doc_button). + Its main parameters are: `Pin` (use GPIO number here) and `functionCall`. For additional options, see [extended documentation below](#button). * **ShutdownButton**: - A specialized implementation for a shutdown button with integrated (but optional) LED support. It initializes a shutdown if the button is pressed more than `time_pressed` seconds and a (optional) LED on GPIO `led_pin` is flashing until that time is reached. For additional information, see [extended documentation below](#doc_sdbutton). + A specialized implementation for a shutdown button with integrated (but optional) LED support. It initializes a shutdown if the button is pressed more than `time_pressed` seconds and a (optional) LED on GPIO `led_pin` is flashing until that time is reached. For additional information, see [extended documentation below](#shutdownbutton). * **RotaryEncoder**: Control of a rotary encoder, for example KY040, see also in [Wiki](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/Audio-RotaryKnobVolume). - It can be configured using `pinUp` and `PiNDown` (use GPIO numbers here), `functionCallUp`, `functionCallDown`, and `timeBase` see [extended documentation below](#doc_rotary). + It can be configured using `pinUp` and `PiNDown` (use GPIO numbers here), `functionCallUp`, `functionCallDown`, and `timeBase` see [extended documentation below](#rotaryencoder). * **TwoButtonControl**: - This Device uses two Buttons and implements a third action if both buttons are pressed together. See [extended documentation below](#doc_twobutton). + This Device uses two Buttons and implements a third action if both buttons are pressed together. See [extended documentation below](#twobuttoncontrol). * **StatusLED**: - A LED which will light up once the Phoniebox has fully booted up and is ready to be used. For additional information, see [extended documentation below](#doc_sled). + A LED which will light up once the Phoniebox has fully booted up and is ready to be used. For additional information, see [extended documentation below](#statusled). Each section needs to be activated by setting `enabled: True`. @@ -42,7 +42,7 @@ Many example files are located in `~/RPi-Jukebox-RFID/components/gpio_control/ex This section provides some extended documentation and guideline. Especially some exemplary configurations are introduced showing how these controls can be set up in the configuration file `~/RPi-Jukebox-RFID/settings/gpio_settings.ini`. -### Button +### Button At the most basic level, a button can be created using an `ini` entry like this: @@ -56,15 +56,16 @@ functionCall: functionCallPlayerPause * **enabled**: This needs to be `True` for the button to work. * **Pin**: GPIO number -* **functionCall**: The function that you want to be called on a button press. See [function documentation below](#doc_funcs). +* **functionCall**: Primary function that you want to be called on a button press. See [function documentation below](#functions). However, a button has more parameters than these. In the following comprehensive list you can also find the default values which are used automatically if you leave out these settings: +* **functionCallArgs**: Arguments for primary function, defaults to `None`. Arguments are ignored, if `functionCall` does not take any. * **hold_mode**: Specifies what shall happen if the button is held pressed for longer than `hold_time`: * `None` (Default): Nothing special will happen. * `Repeat`: The configured `functionCall` is repeated after each `hold_time` interval. * `Postpone`: The function will not be called before `hold_time`, i.e. the button needs to be pressed this long to activate the function - * `SecondFunc`: Holding the button for at least `hold_time` will additionally execute the function `functionCall2`. + * `SecondFunc`: Holding the button for at least `hold_time` will additionally execute the function `functionCall2` with `functionCall2Args`. * `SecondFuncRepeat`: Like SecondFunc, but `functionCall2` is repeated after each `hold_time` interval. In every `hold_mode` except `Postpone`, the main action `functionCall` gets executed instantly. @@ -72,7 +73,8 @@ However, a button has more parameters than these. In the following comprehensive Holding the button even longer than `hold_time` will cause no further action unless you are in the `Repeat` or `SecondFuncRepeat` mode. * **hold_time**: Reference time for this buttons `hold_mode` feature in seconds. Default is `0.3`. This setting is ignored if `hold_mode` is unset or `None` -* **functionCall2**: Secondary function; default is `None`. This setting is ignored unless `hold_mode` is set to `SecondFunc` or `SecondFuncRepeat`. +* **functionCall2**: Secondary function, default is `None`. This setting is ignored unless `hold_mode` is set to `SecondFunc` or `SecondFuncRepeat`. +* **functionCall2Args**: Arguments for secondary function, default is `None`. Arguments are ignored, if `functionCall2` does not take any. * **pull_up_down**: Configures the internal Pull up/down resistors. Valid settings: * `pull_up` (Default). Internal pull-up resistors are activated. Use this if you attached a button to `GND` to the GPIO pin without any external pull-up resistor. * `pull_down`. Use this if you need the internal pull-down resistor activated. @@ -82,11 +84,11 @@ However, a button has more parameters than these. In the following comprehensive * `rising`. Trigegrs only if the GPIO voltage goes up. * `both`. Triggers in both cases. * **bouncetime**: This is a setting of the GPIO library to limit bouncing effects during button usage. Default is `500` ms. -* **antibouncehack**: Despite the integrated bounce reduction of the GPIO library some users may notice false triggers of their buttons (e.g. unrequested / double actions when releasing the button. If you encounter such problems, try setting this setting to `True` to activate an additional countermeasure. +* **antibouncehack**: Despite the integrated bounce reduction of the GPIO library some users may notice false triggers of their buttons (e.g. unrequested / double actions when releasing the button). If you encounter such problems, try setting this setting to `True` to activate an additional countermeasure. Note: If you prefer, you may also use `Type: SimpleButton` instead of `Type: Button` - this makes no difference. -### ShutdownButton +### ShutdownButton An extended ShutdownButton can be created using an `ini` entry like these: @@ -111,9 +113,9 @@ Again, there are more parameters than these. In the following comprehensive list * **hold_time**: This parameter controls how many seconds (default: `3.0`) the button has to be hold until shutdown will be initiated. * **iteration_time**: This parameter determines the flashing speed of the LED indicator. Default value is `0.2` seconds. -* **functionCall**: While the default action is `functionCallShutdown`, you might use this button type even with other functions than system shutdown (again, see [function documentation below](#doc_funcs) for a list of available functions). +* **functionCall**: While the default action is `functionCallShutdown`, you might use this button type even with other functions than system shutdown (again, see [function documentation below](#functions) for a list of available functions). -Furthermore, the following settings can be used as described for the [regular buttons](#doc_button): **pull_up_down**, **edge**, **bouncetime**, **antibouncehack** +Furthermore, the following settings can be used as described for the [regular buttons](#doc_button): **pull_up_down**, **edge**, **bouncetime**, **antibouncehack**, **functionCallArgs** Note that using a ShutdownButton without a LED can also be implemented with a normal button like this: @@ -127,7 +129,7 @@ hold_time: 3.0 functionCall: functionCallShutdown ``` -### TwoButtonControl +### TwoButtonControl A TwoButtonControl can be created using an `ini` entry like this: @@ -161,9 +163,11 @@ functionCallTwoButtons: functionCallVol0 In this example, the volume will be in-/decreased step-wise using intervals of 0.3 seconds while the respective button is held. If both buttons are pushed simultaneously, the player is muted (volume 0). In this example, Pin1 is used for increasing the volume, while Pin2 decreases it. -Furthermore, the following settings can be used as described for the [regular buttons](#doc_button): **pull_up_down**, **edge**, **bouncetime**, **antibouncehack** +Function arguments can also be passed to this control with the settings **functionCall1Args**, **functionCall2Args**, **functionCallTwoButtonsArgs** -### RotaryEncoder +Furthermore, the following settings can be used as described for the [regular buttons](#button): **pull_up_down**, **edge**, **bouncetime**, **antibouncehack** + +### RotaryEncoder A RotaryEncoder can be created using an `ini` entry like this: @@ -181,7 +185,7 @@ functionCall2: functionCallVolD Pin1 and FunctionCall1 correspond to rotary direction "up", while Pin2 and FunctionCall2 correspond to "down". Note that the old configuration entries PinUp/PinDown and functionCallUp/functionCallDown are deprecated and might stop working in future. -### StatusLED +### StatusLED A StatusLED can be created using an `ini` entry like this: @@ -239,19 +243,47 @@ pull_up_down: pull_up hold_time: 5.0 hold_mode: SecondFuncRepeat functionCall: functionCallPlayerSeekFwd +functionCallArgs: 5 functionCall2: functionCallPlayerSeekFarFwd +functionCall2Args: 30 ``` -In this example, a short press initiates a short jump forward by 10 seconds (functionCallPlayerSeekFwd) while holding the button will cause further, longer jumps. In this case it will cause a jump of 1 minute forward (functionCallPlayerSeekFarFwd) every 5 seconds. If you wish, you can adjust these values in `components/gpio_control/function_calls.py`. -For jumping backwards, this can be done equivalently (see [function list below](#doc_funcs)). +In this example, a short press initiates a short jump forward by customized 5 seconds (functionCallPlayerSeekFwd) while holding the button will cause further, longer jumps. In this case it will cause a jump of customized 30 seconds forward (functionCallPlayerSeekFarFwd) every 5 seconds. +For jumping backwards, this can be done equivalently (see [function list below](#functions)). + +#### Use Buttons to start playlists +To use GPIO-Pins to play music, you can emulate a card scan with the function `functionCallTriggerPlayCardId`. Supply the `cardid` as `functionCallArgs`. +This behaves like a card scan, so you must link a folder to the id (on first press it will show up in the Web App as new card). + +```bash +[TriggerPlayCard1] +enabled: False +Type: Button +Pin: 4 +pull_up_down: pull_up +functionCall: functionCallTriggerPlayCardId +functionCallArgs: 1 +``` + +You can also directly link a folder to the button with `functionCallTriggerPlayFolder`. Supply the `folder` as `functionCallArgs`. + +```bash +[TriggerPlayFolderSomeRelativeFolderName] +enabled: False +Type: Button +Pin: 4 +pull_up_down: pull_up +functionCall: functionCallTriggerPlayFolder +functionCallArgs: someRelativeFolderName +``` -### Functions +### Functions The available functions are defined/implemented in `components/gpio_control/function_calls.py`: * **functionCallShutdown**: System shutdown -* **functionCallVolU**: Volume up -* **functionCallVolD**: Volume down +* **functionCallVolU**: Volume up (custom steps as optional argument) +* **functionCallVolD**: Volume down (custom steps as optional argument) * **functionCallVol0**: Mute * **functionCallPlayerNext**: Next track * **functionCallPlayerPrev**: Previous track @@ -262,15 +294,18 @@ The available functions are defined/implemented in `components/gpio_control/func * **functionCallRecordPlayLatest**: Play latest recording * **functionCallToggleWifi**: Toggle WIFI * **functionCallPlayerStop**: Stop Player -* **functionCallPlayerSeekFwd**: Seek 10 seconds forward -* **functionCallPlayerSeekBack**: Seek 10 seconds backward -* **functionCallPlayerSeekFarFwd**: Seek 1 minute forward -* **functionCallPlayerSeekFarBack**: Seek 1 minute backward +* **functionCallPlayerSeekFwd**: Seek 10 seconds forward (custom seconds as optional argument) +* **functionCallPlayerSeekBack**: Seek 10 seconds backward (custom seconds as optional argument) +* **functionCallPlayerSeekFarFwd**: Seek 1 minute forward (custom seconds as optional argument) +* **functionCallPlayerSeekFarBack**: Seek 1 minute backward (custom seconds as optional argument) * **functionCallPlayerRandomTrack**: Jumps to random track (within current playlist) * **functionCallPlayerRandomCard**: Activate a random card * **functionCallPlayerRandomFolder**: Play a random folder +* **functionCallBluetoothToggle**: Switches to the audio device (e.g. speaker, bluetooth headphones), default 'toggle' (mode as optional argument) +* **functionCallTriggerPlayCardId**: Trigger play for cardid given as argument +* **functionCallTriggerPlayFolder**: Trigger play for folder given as argument -### Troubleshooting +### Troubleshooting If you encounter bouncing effects with your buttons like unrequested/double actions after releasing a button, you can try to set `antibouncehack` to True: diff --git a/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini b/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini index 31d2df340..3e829d55e 100755 --- a/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini +++ b/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini @@ -1,5 +1,6 @@ [DEFAULT] enabled: True + [VolumeControl] enabled: True Type: RotaryEncoder diff --git a/components/gpio_control/example_configs/gpio_settings.ini b/components/gpio_control/example_configs/gpio_settings.ini index b05004c7a..4f84ba01b 100755 --- a/components/gpio_control/example_configs/gpio_settings.ini +++ b/components/gpio_control/example_configs/gpio_settings.ini @@ -3,7 +3,7 @@ enabled: True antibouncehack: False [VolumeControl] -enabled: True +enabled: False Type: TwoButtonControl ;or RotaryEncoder Pin1: 5 Pin2: 6 @@ -14,9 +14,12 @@ timeBase: 0.1 ;only for RotaryEncoder functionCall1: functionCallVolU functionCall2: functionCallVolD functionCallTwoButtons: functionCallVol0 ;only for TwoButtonControl +;functionCall1Args: 1 +;functionCall2Args: 1 +;functionCallTwoButtonsArgs: x [PrevNextControl] -enabled: True +enabled: False Type: TwoButtonControl Pin1: 22 Pin2: 23 @@ -28,7 +31,7 @@ hold_time: 0.3 hold_mode: None [PlayPause] -enabled: True +enabled: False Type: Button Pin: 27 pull_up_down: pull_up @@ -68,6 +71,7 @@ pull_up_down: pull_up hold_time: 0.3 hold_mode: Repeat functionCall: functionCallVolU +;functionCallArgs: 1 [VolumeDown] enabled: False @@ -77,6 +81,7 @@ pull_up_down: pull_up hold_time: 0.3 hold_mode: Repeat functionCall: functionCallVolD +;functionCallArgs: 1 [NextSong] enabled: False @@ -93,18 +98,20 @@ pull_up_down: pull_up functionCall: functionCallPlayerPrev [FastForward] -enabled: false +enabled: False Type: Button Pin: 7 pull_up_down: pull_up functionCall: functionCallPlayerSeekFwd +;functionCallArgs: 10 [Rewind] -enabled: false +enabled: False Type: Button Pin: 8 pull_up_down: pull_up functionCall: functionCallPlayerSeekBack +;functionCallArgs: 10 [Halt] enabled: False @@ -114,8 +121,25 @@ pull_up_down: pull_up functionCall: functionCallPlayerPauseForce [RFIDDevice] -enabled: True +enabled: False Type: Button Pin: 21 pull_up_down: pull_up functionCall: functionCallPlayerStop + +[TriggerPlayCard1] +enabled: False +Type: Button +Pin: 4 +pull_up_down: pull_up +functionCall: functionCallTriggerPlayCardId +functionCallArgs: 1 + +[TriggerPlayFolderSomeRelativeFolderName] +enabled: False +Type: Button +Pin: 4 +pull_up_down: pull_up +functionCall: functionCallTriggerPlayFolder +functionCallArgs: someRelativeFolderName + diff --git a/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini b/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini index 303d0888d..f636adbcf 100644 --- a/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini +++ b/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini @@ -1,6 +1,5 @@ [DEFAULT] enabled: True -used_pinds = [13] [VolumeControl] enabled: True diff --git a/components/gpio_control/example_configs/gpio_settings_test.ini b/components/gpio_control/example_configs/gpio_settings_test.ini deleted file mode 100644 index 79e365f61..000000000 --- a/components/gpio_control/example_configs/gpio_settings_test.ini +++ /dev/null @@ -1,85 +0,0 @@ -[DEFAULT] -enabled: True - -; The following Types exist: -; RotaryEncoder -; TwoButtonControl -; SimpleButton = Button -[VolumeControl] -enabled: True -Type: RotaryEncoder -PinUp: 16 -PinDown: 19 -pull_up: True -hold_time: 0.3 -hold_repeat: True -timeBase: 0.1 -; timeBase only for rotary encoder -functionCallDown: functionCallVolD -functionCallUp: functionCallVolU -functionCallTwoButtons: functionCallVol0 -; functionCallTwoButtons only for TwoButtonControl -; functionCallButton: functionCallPlayerPause ; only for RotaryEncoderClickable - -[PrevNextControl] -Type: TwoButtonControl -Pin1: 4 -Pin2: 3 -functionCall1: functionCallPlayerPrev -functionCall2: functionCallPlayerNext -functionCallTwoButtons: None -pull_up_down: pull_up -hold_time: 0.3 -hold_mode: None - - -[Shutdown] -enabled: True -Type: ShutdownButton -Pin: 3 -hold_time: 2 -iteration_time: 0.2 -; led_pin: , if LED used -functionCall: functionCallShutdown - -[Volume0] -enabled: False -Type: Button -Pin: 13 -pull_up_down: pull_up -functionCall: functionCallVol0 - -[VolumeUp] -enabled: False -Type: Button -Pin: 16 -pull_up_down: pull_up -hold_time: 0.3 -hold_mode: Repeat -functionCall: functionCallVolU - -[VolumeDown] -enabled: False -Type: Button -Pin: 19 -pull_up_down: pull_up -hold_time: 0.3 -hold_mode: Repeat - -[NextSong] -enabled: False -Type: Button -Pin: 26 -pull_up_down: pull_up - -[PrevSong] -enabled: False -Type: Button -Pin: 20 -pull_up_down: pull_up - -[Halt] -enabled: False -Type: Button -Pin: 21 -pull_up_down: pull_up diff --git a/components/gpio_control/function_calls.py b/components/gpio_control/function_calls.py index 5f6deb1ff..e293282c0 100644 --- a/components/gpio_control/function_calls.py +++ b/components/gpio_control/function_calls.py @@ -12,6 +12,8 @@ def __init__(self): playout_control_relative_path = "../../scripts/playout_controls.sh" function_calls_absolute_path = str(pathlib.Path(__file__).parent.absolute()) self.playout_control = os.path.abspath(os.path.join(function_calls_absolute_path, playout_control_relative_path)) + rfid_trigger_relative_path = "../../scripts/rfid_trigger_play.sh" + self.rfid_trigger = os.path.abspath(os.path.join(function_calls_absolute_path, rfid_trigger_relative_path)) def functionCallShutdown(self, *args): function_call("{command} -c=shutdown".format(command=self.playout_control), shell=True) @@ -20,7 +22,7 @@ def functionCallVolU(self, steps=None): if steps is None: function_call("{command} -c=volumeup".format(command=self.playout_control), shell=True) else: - function_call("{command} -c=volumeup -v={steps}".format(steps=steps, + function_call("{command} -c=volumeup -v={value}".format(value=steps, command=self.playout_control), shell=True) @@ -28,7 +30,7 @@ def functionCallVolD(self, steps=None): if steps is None: function_call("{command} -c=volumedown".format(command=self.playout_control), shell=True) else: - function_call("{command} -c=volumedown -v={steps}".format(steps=steps, + function_call("{command} -c=volumedown -v={value}".format(value=steps, command=self.playout_control), shell=True) @@ -63,17 +65,25 @@ def functionCallPlayerStop(self, *args): function_call("{command} -c=playerstop".format(command=self.playout_control), shell=True) - def functionCallPlayerSeekFwd(self, *args): - function_call("{command} -c=playerseek -v=+10".format(command=self.playout_control), shell=True) + def functionCallPlayerSeekFwd(self, seconds=None): + if seconds is None: + seconds = 10 + function_call("{command} -c=playerseek -v=+{value}".format(command=self.playout_control, value=seconds), shell=True) - def functionCallPlayerSeekBack(self, *args): - function_call("{command} -c=playerseek -v=-10".format(command=self.playout_control), shell=True) + def functionCallPlayerSeekBack(self, seconds=None): + if seconds is None: + seconds = 10 + function_call("{command} -c=playerseek -v=-{value}".format(command=self.playout_control, value=seconds), shell=True) - def functionCallPlayerSeekFarFwd(self, *args): - function_call("{command} -c=playerseek -v=+60".format(command=self.playout_control), shell=True) + def functionCallPlayerSeekFarFwd(self, seconds=None): + if seconds is None: + seconds = 60 + function_call("{command} -c=playerseek -v=+{value}".format(command=self.playout_control, value=seconds), shell=True) - def functionCallPlayerSeekFarBack(self, *args): - function_call("{command} -c=playerseek -v=-60".format(command=self.playout_control), shell=True) + def functionCallPlayerSeekFarBack(self, seconds=None): + if seconds is None: + seconds = 60 + function_call("{command} -c=playerseek -v=-{value}".format(command=self.playout_control, value=seconds), shell=True) def functionCallPlayerRandomTrack(self, *args): function_call("{command} -c=randomtrack".format(command=self.playout_control), shell=True) @@ -84,8 +94,16 @@ def functionCallPlayerRandomCard(self, *args): def functionCallPlayerRandomFolder(self, *args): function_call("{command} -c=randomfolder".format(command=self.playout_control), shell=True) - def functionCallBluetoothToggle(self, *args): - function_call("{command} -c=bluetoothtoggle -v=toggle".format(command=self.playout_control), shell=True) + def functionCallBluetoothToggle(self, mode=None): + if mode is None: + mode = 'toggle' + function_call("{command} -c=bluetoothtoggle -v={value}".format(command=self.playout_control, value=mode), shell=True) + + def functionCallTriggerPlayCardId(self, cardid): + function_call("{command} --cardid={value}".format(command=self.rfid_trigger, value = cardid), shell=True) + + def functionCallTriggerPlayFolder(self, folder): + function_call("{command} --dir={value}".format(command=self.rfid_trigger, value = folder), shell=True) def getFunctionCall(self, functionName): self.logger.error('Get FunctionCall: {} {}'.format(functionName, functionName in locals())) diff --git a/components/gpio_control/gpio_control.py b/components/gpio_control/gpio_control.py index 113850431..45977b89b 100755 --- a/components/gpio_control/gpio_control.py +++ b/components/gpio_control/gpio_control.py @@ -3,14 +3,19 @@ import os import logging -from GPIODevices import * -import function_calls from signal import pause from RPi import GPIO +from GPIODevices import (RotaryEncoder, + TwoButtonControl, + ShutdownButton, + SimpleButton, + LED, + StatusLED) +from function_calls import phoniebox_function_calls from config_compatibility import ConfigCompatibilityChecks -class gpio_control(): +class GPIOControl(): def __init__(self, function_calls): self.devices = [] @@ -23,10 +28,11 @@ def __init__(self, function_calls): self.logger.setLevel('INFO') self.logger.info('GPIO Started') - def getFunctionCall(self, function_name): + def getFunctionCall(self, function_name, function_args): try: - if function_name != 'None': - return getattr(self.function_calls, function_name) + if function_name is not None and function_name != 'None': + functionCall = getattr(self.function_calls, function_name) + return (lambda *args: functionCall(*args, function_args)) except AttributeError: self.logger.error('Could not find FunctionCall {function_name}'.format(function_name=function_name)) return lambda *args: None @@ -35,13 +41,13 @@ def generate_device(self, config, deviceName): print(deviceName) device_type = config.get('Type') if device_type == 'TwoButtonControl': - self.logger.info('adding TwoButtonControl') return TwoButtonControl( config.getint('Pin1'), config.getint('Pin2'), - self.getFunctionCall(config.get('functionCall1')), - self.getFunctionCall(config.get('functionCall2')), - functionCallTwoBtns=self.getFunctionCall(config.get('functionCallTwoButtons')), + self.getFunctionCall(config.get('functionCall1'), config.get('functionCall1Args', fallback=None)), + self.getFunctionCall(config.get('functionCall2'), config.get('functionCall2Args', fallback=None)), + functionCallTwoBtns=self.getFunctionCall(config.get('functionCallTwoButtons'), + config.get('functionCallTwoButtonsArgs', fallback=None)), pull_up_down=config.get('pull_up_down', fallback='pull_up'), hold_mode=config.get('hold_mode', fallback=None), hold_time=config.getfloat('hold_time', fallback=0.3), @@ -51,8 +57,10 @@ def generate_device(self, config, deviceName): name=deviceName) elif device_type in ('Button', 'SimpleButton'): return SimpleButton(config.getint('Pin'), - action=self.getFunctionCall(config.get('functionCall')), - action2=self.getFunctionCall(config.get('functionCall2', fallback='None')), + action=self.getFunctionCall(config.get('functionCall'), + config.get('functionCallArgs', fallback=None)), + action2=self.getFunctionCall(config.get('functionCall2', fallback='None'), + config.get('functionCall2Args', fallback=None)), name=deviceName, bouncetime=config.getint('bouncetime', fallback=500), antibouncehack=config.getboolean('antibouncehack', fallback=False), @@ -69,13 +77,15 @@ def generate_device(self, config, deviceName): elif device_type == 'RotaryEncoder': return RotaryEncoder(config.getint('Pin1'), config.getint('Pin2'), - self.getFunctionCall(config.get('functionCall1')), - self.getFunctionCall(config.get('functionCall2')), + self.getFunctionCall(config.get('functionCall1'), None), + self.getFunctionCall(config.get('functionCall2'), None), config.getfloat('timeBase', fallback=0.1), name=deviceName) elif device_type == 'ShutdownButton': return ShutdownButton(pin=config.getint('Pin'), - action=self.getFunctionCall(config.get('functionCall', fallback='functionCallShutdown')), + action=self.getFunctionCall(config.get('functionCall', + fallback='functionCallShutdown'), + config.get('functionCallArgs', fallback=None)), name=deviceName, bouncetime=config.getint('bouncetime', fallback=500), antibouncehack=config.getboolean('antibouncehack', fallback=False), @@ -121,9 +131,9 @@ def gpio_loop(self): ConfigCompatibilityChecks(config, config_path) - phoniebox_function_calls = function_calls.phoniebox_function_calls() - gpio_controler = gpio_control(phoniebox_function_calls) + _phoniebox_function_calls = phoniebox_function_calls() + gpio_control_class = GPIOControl(_phoniebox_function_calls) - devices = gpio_controler.get_all_devices(config) - gpio_controler.print_all_devices() - gpio_controler.gpio_loop() + devices = gpio_control_class.get_all_devices(config) + gpio_control_class.print_all_devices() + gpio_control_class.gpio_loop() diff --git a/components/gpio_control/test/conftest.py b/components/gpio_control/test/conftest.py index 94c1cc67d..ba668a835 100644 --- a/components/gpio_control/test/conftest.py +++ b/components/gpio_control/test/conftest.py @@ -1,5 +1,8 @@ +import sys +import os from mock import MagicMock, patch - +# add absolute parent path, to harmonize imports with gpio_control.py __main__ usage for tests +sys.path.insert(1, "/".join(os.path.abspath(__file__).split("/")[0:-2])) MockRPi = MagicMock() modules = { diff --git a/components/gpio_control/test/gpio_settings_test.ini b/components/gpio_control/test/gpio_settings_test.ini index d1bb8ed93..79e365f61 100644 --- a/components/gpio_control/test/gpio_settings_test.ini +++ b/components/gpio_control/test/gpio_settings_test.ini @@ -5,16 +5,18 @@ enabled: True ; RotaryEncoder ; TwoButtonControl ; SimpleButton = Button -; StatusLED [VolumeControl] enabled: True Type: RotaryEncoder PinUp: 16 PinDown: 19 +pull_up: True +hold_time: 0.3 +hold_repeat: True timeBase: 0.1 ; timeBase only for rotary encoder -functionCall1: functionCallVolU -functionCall2: functionCallVolD +functionCallDown: functionCallVolD +functionCallUp: functionCallVolU functionCallTwoButtons: functionCallVol0 ; functionCallTwoButtons only for TwoButtonControl ; functionCallButton: functionCallPlayerPause ; only for RotaryEncoderClickable @@ -81,8 +83,3 @@ enabled: False Type: Button Pin: 21 pull_up_down: pull_up - -[StatusLED] -enabled: True -Type: StatusLED -Pin: 14 diff --git a/components/gpio_control/test/test_LED.py b/components/gpio_control/test/test_LED.py new file mode 100644 index 000000000..6a2030c1b --- /dev/null +++ b/components/gpio_control/test/test_LED.py @@ -0,0 +1,72 @@ +import pytest +from mock import patch, call +from GPIODevices.led import LED, StatusLED, GPIO + + +@pytest.fixture +def led_control(): + GPIO.reset_mock() + return LED(pin=1, initial_value=True, name='TestLED') + + +class TestLED: + + def test_led_init_default(self): + GPIO.reset_mock() + _led = LED(pin=1) + assert _led.pin == 1 + assert _led.initial_value is True + assert _led.name == 'LED' + GPIO.setup.assert_called_once_with(1, GPIO.OUT) + GPIO.output.assert_called_once_with(1, True) + + def test_led_init(self): + GPIO.reset_mock() + _led = LED(pin=2, initial_value=False, name='TestLED') + assert _led.pin == 2 + assert _led.initial_value is False + assert _led.name == 'TestLED' + GPIO.setup.assert_called_once_with(2, GPIO.OUT) + GPIO.output.assert_called_once_with(2, False) + + def test_led_on(self, led_control): + GPIO.reset_mock() + led_control.on() + GPIO.output.assert_called_once_with(1, GPIO.HIGH) + + def test_led_off(self, led_control): + GPIO.reset_mock() + led_control.off() + GPIO.output.assert_called_once_with(1, GPIO.LOW) + + def test_led_status(self, led_control): + GPIO.reset_mock() + GPIO.input.side_effect = [1] + _status = led_control.status() + assert _status == 1 + + def test_statusled_init_default(self): + GPIO.reset_mock() + with patch('GPIODevices.led.system') as mock_system: + with patch('time.sleep'): + mock_system.side_effect = [False] + _led = StatusLED(pin=1) + assert _led.pin == 1 + assert _led.initial_value is False + assert _led.name == 'StatusLED' + mock_system.assert_called_with('systemctl is-active --quiet phoniebox-startup-scripts.service') + GPIO.setup.assert_called_once_with(1, GPIO.OUT) + GPIO.output.assert_has_calls([call(1, False), call(1, GPIO.HIGH)]) + + def test_statusled_init(self): + GPIO.reset_mock() + with patch('GPIODevices.led.system') as mock_system: + with patch('time.sleep'): + mock_system.side_effect = [True, False, False] + _led = StatusLED(pin=2, name='TestStatusLED') + assert _led.pin == 2 + assert _led.initial_value is False + assert _led.name == 'TestStatusLED' + mock_system.assert_called_with('systemctl is-active --quiet phoniebox-startup-scripts.service') + GPIO.setup.assert_called_once_with(2, GPIO.OUT) + GPIO.output.assert_has_calls([call(2, False), call(2, GPIO.HIGH)]) diff --git a/components/gpio_control/test/test_RotaryEncoder.py b/components/gpio_control/test/test_RotaryEncoder.py index bae83d264..3d80c5ad0 100644 --- a/components/gpio_control/test/test_RotaryEncoder.py +++ b/components/gpio_control/test/test_RotaryEncoder.py @@ -1,8 +1,6 @@ import pytest from mock import MagicMock - -from ..GPIODevices.rotary_encoder import RotaryEncoder -from RPi import GPIO +from GPIODevices.rotary_encoder import RotaryEncoder, GPIO pinA = 1 @@ -33,9 +31,6 @@ def rotaryEncoder(functionCallIncr, functionCallDecr): name='MockedGPIOInteraction') -# -# @patch("RPi", autospec=True) -# @patch("RPi.GPIO", autospec=True) class TestRotaryEncoder: def test_init(self, functionCallIncr, functionCallDecr): @@ -47,7 +42,6 @@ def test_repr(self, rotaryEncoder): assert repr(rotaryEncoder) == expected def test_start_stop(self, rotaryEncoder): - calls = GPIO.add_event_detect.call_count assert rotaryEncoder.is_active is True GPIO.remove_event_detect.assert_not_called() rotaryEncoder.stop() diff --git a/components/gpio_control/test/test_SimpleButton.py b/components/gpio_control/test/test_SimpleButton.py index 7f118ebb4..d6f03acd3 100644 --- a/components/gpio_control/test/test_SimpleButton.py +++ b/components/gpio_control/test/test_SimpleButton.py @@ -1,45 +1,98 @@ -from mock import patch, MagicMock import pytest +from mock import MagicMock +from GPIODevices.simple_button import SimpleButton, GPIO -import RPi.GPIO as GPIO -from ..GPIODevices.simple_button import SimpleButton -pin = 1 mockedAction = MagicMock() +mockedSecAction = MagicMock() @pytest.fixture def simple_button(): mockedAction.reset_mock() + mockedSecAction.reset_mock() - return SimpleButton(pin, action=mockedAction, name='TestButton', - bouncetime=500, edge=GPIO.FALLING) + return SimpleButton(pin=1, action=mockedAction, action2=mockedSecAction, name='TestButton') class TestButton: - mockedFunction = MagicMock() def test_init(self): - SimpleButton(pin, action=self.mockedFunction, name='TestButton', - bouncetime=500, edge=GPIO.FALLING) + SimpleButton(pin=1) + + def test_init_edge_valid(self): + SimpleButton(pin=1, edge='falling') + SimpleButton(pin=1, edge='rising') + SimpleButton(pin=1, edge='both') + SimpleButton(pin=1, edge=GPIO.FALLING) + SimpleButton(pin=1, edge=GPIO.RISING) + SimpleButton(pin=1, edge=GPIO.BOTH) + + def test_init_edge_invalid(self): + with pytest.raises(KeyError) as e: + SimpleButton(pin=1, edge='invalid') + assert str(e.value) == "'Unknown Edge type invalid'" + + def test_init_pullUpDown_valid(self): + SimpleButton(pin=1, pull_up_down='pull_up') + SimpleButton(pin=1, pull_up_down='pull_down') + SimpleButton(pin=1, pull_up_down='pull_off') + SimpleButton(pin=1, pull_up_down=GPIO.PUD_UP) + SimpleButton(pin=1, pull_up_down=GPIO.PUD_DOWN) + SimpleButton(pin=1, pull_up_down=GPIO.PUD_OFF) + + def test_init_pullUpDown_invalid(self): + with pytest.raises(KeyError) as e: + SimpleButton(pin=1, pull_up_down="invalid") + assert str(e.value) == "'Unknown Pull Up/Down type invalid'" + + def test_is_pressed_pullUp(self): + simple_button = SimpleButton(pin=1, pull_up_down='pull_up') + GPIO.input.side_effect = [False] + is_pressed = simple_button.is_pressed + assert is_pressed is True + GPIO.input.side_effect = [True] + is_pressed = simple_button.is_pressed + assert is_pressed is False + + # TODO is pull_up always true a bug? + # def test_is_pressed_pullDown(self): + # simple_button = SimpleButton(pin=1, pull_up_down='pull_up') + # GPIO.input.side_effect = [False] + # is_pressed = simple_button.is_pressed + # assert is_pressed is False + # GPIO.input.side_effect = [True] + # is_pressed = simple_button.is_pressed + # assert is_pressed is True + + def test_antibounce(self, simple_button): + simple_button.antibouncehack = True + GPIO.input.side_effect = lambda *args: 1 + simple_button.callbackFunctionHandler() + mockedAction.assert_not_called() + mockedSecAction.assert_not_called() def test_callback(self, simple_button): simple_button.callbackFunctionHandler(simple_button.pin) mockedAction.assert_called_once_with() + mockedSecAction.assert_not_called() def test_callback_without_pin_argument(self, simple_button): simple_button.callbackFunctionHandler() mockedAction.assert_called_once_with() + mockedSecAction.assert_not_called() def test_callback_with_wrong_pin_argument(self, simple_button): simple_button.callbackFunctionHandler(simple_button.pin + 1) mockedAction.assert_called_once_with(simple_button.pin + 1) + mockedSecAction.assert_not_called() def test_callback_with_more_arguments(self, simple_button): simple_button.callbackFunctionHandler(simple_button.pin, 5) mockedAction.assert_called_once_with(5) + mockedSecAction.assert_not_called() - def test_change_when_pressed(self, simple_button): + def test_change_new_action(self, simple_button): simple_button.callbackFunctionHandler(simple_button.pin, 5) newMockedAction = MagicMock() @@ -48,8 +101,9 @@ def test_change_when_pressed(self, simple_button): newMockedAction.assert_called_once_with() mockedAction.assert_called_once_with(5) + mockedSecAction.assert_not_called() - def test_hold(self, simple_button): + def test_hold_Repeat_longer_holdtime(self, simple_button): GPIO.LOW = 0 GPIO.input.side_effect = [False, False, False, True] simple_button.hold_time = 0 @@ -57,3 +111,81 @@ def test_hold(self, simple_button): calls = mockedAction.call_count simple_button.callbackFunctionHandler(simple_button.pin) assert mockedAction.call_count - calls == 4 + mockedSecAction.assert_not_called() + + def test_hold_Repeat_shorter_holdtime(self, simple_button): + GPIO.LOW = 0 + GPIO.input.side_effect = [False, True] + simple_button.hold_time = 0.3 + simple_button.hold_mode = 'Repeat' + calls = mockedAction.call_count + simple_button.callbackFunctionHandler(simple_button.pin) + assert mockedAction.call_count - calls == 1 + mockedSecAction.assert_not_called() + + def test_hold_Postpone_longer_holdtime(self, simple_button): + GPIO.LOW = 0 + GPIO.input.side_effect = [False, False, False, True] + simple_button.hold_time = 0 + simple_button.hold_mode = 'Postpone' + calls = mockedAction.call_count + simple_button.callbackFunctionHandler(simple_button.pin) + assert mockedAction.call_count - calls == 1 + mockedSecAction.assert_not_called() + + def test_hold_Postpone_shorter_holdtime(self, simple_button): + GPIO.LOW = 0 + GPIO.input.side_effect = [False, True, True] + simple_button.hold_time = 0.3 + simple_button.hold_mode = 'Postpone' + calls = mockedAction.call_count + simple_button.callbackFunctionHandler(simple_button.pin) + assert mockedAction.call_count - calls == 0 + mockedSecAction.assert_not_called() + + def test_hold_SecondFunc_longer_holdtime(self, simple_button): + GPIO.LOW = 0 + GPIO.input.side_effect = [False, False, False, True] + simple_button.hold_time = 0 + simple_button.hold_mode = 'SecondFunc' + calls = mockedSecAction.call_count + simple_button.callbackFunctionHandler(simple_button.pin) + mockedAction.assert_called_once() + assert mockedSecAction.call_count - calls == 1 + + def test_hold_SecondFunc_shorter_holdtime(self, simple_button): + GPIO.LOW = 0 + GPIO.input.side_effect = [False, True, True] + simple_button.hold_time = 0.3 + simple_button.hold_mode = 'SecondFunc' + calls = mockedSecAction.call_count + simple_button.callbackFunctionHandler(simple_button.pin) + mockedAction.assert_called_once() + assert mockedSecAction.call_count - calls == 0 + + def test_hold_SecondFuncRepeat_longer_holdtime(self, simple_button): + GPIO.LOW = 0 + GPIO.input.side_effect = [False, False, False, True] + simple_button.hold_time = 0 + simple_button.hold_mode = 'SecondFuncRepeat' + calls = mockedSecAction.call_count + simple_button.callbackFunctionHandler(simple_button.pin) + mockedAction.assert_called_once() + assert mockedSecAction.call_count - calls == 3 + + def test_hold_SecondFuncRepeat_shorter_holdtime(self, simple_button): + GPIO.LOW = 0 + GPIO.input.side_effect = [False, True, True] + simple_button.hold_time = 0.3 + simple_button.hold_mode = 'SecondFuncRepeat' + calls = mockedSecAction.call_count + simple_button.callbackFunctionHandler(simple_button.pin) + mockedAction.assert_called_once() + assert mockedSecAction.call_count - calls == 0 + + def test_repr(self): + button = SimpleButton(name='test_repr', pin=1, edge='rising', hold_mode=None, hold_time=2.5, + bouncetime=500, antibouncehack=True, pull_up_down='pull_down') + expected = ('') + assert repr(button) == expected diff --git a/components/gpio_control/test/test_TwoButtonControl.py b/components/gpio_control/test/test_TwoButtonControl.py index 2741430d1..1f2ed7336 100644 --- a/components/gpio_control/test/test_TwoButtonControl.py +++ b/components/gpio_control/test/test_TwoButtonControl.py @@ -1,34 +1,35 @@ -import mock import pytest from mock import MagicMock - -from ..GPIODevices import two_button_control -from ..GPIODevices.two_button_control import functionCallTwoButtons, TwoButtonControl +from GPIODevices.two_button_control import functionCallTwoButtons, TwoButtonControl, GPIO @pytest.fixture def btn1Mock(): - return mock.MagicMock() + _mock = MagicMock() + _mock.pin = 1 + return _mock @pytest.fixture def btn2Mock(): - return mock.MagicMock() + _mock = MagicMock() + _mock.pin = 2 + return _mock @pytest.fixture def functionCall1Mock(): - return mock.MagicMock() + return MagicMock() @pytest.fixture def functionCall2Mock(): - return mock.MagicMock() + return MagicMock() @pytest.fixture def functionCallBothPressedMock(): - return mock.MagicMock() + return MagicMock() def test_functionCallTwoButtonsOnlyBtn1Pressed(btn1Mock, @@ -39,16 +40,34 @@ def test_functionCallTwoButtonsOnlyBtn1Pressed(btn1Mock, btn1Mock.is_pressed = True btn2Mock.is_pressed = False func = functionCallTwoButtons(btn1Mock, - btn2Mock, - functionCall1Mock, - functionCall2Mock, - functionCallBothPressed=functionCallBothPressedMock) + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressed=functionCallBothPressedMock) func() functionCall1Mock.assert_called_once_with() functionCall2Mock.assert_not_called() functionCallBothPressedMock.assert_not_called() +def test_functionCallTwoButtonsOnlyBtn2Pressed(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressedMock): + btn1Mock.is_pressed = False + btn2Mock.is_pressed = True + func = functionCallTwoButtons(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressed=functionCallBothPressedMock) + func() + functionCall1Mock.assert_not_called() + functionCall2Mock.assert_called_once_with() + functionCallBothPressedMock.assert_not_called() + + def test_functionCallTwoButtonsBothBtnsPressedFunctionCallBothPressedExists(btn1Mock, btn2Mock, functionCall1Mock, @@ -56,8 +75,11 @@ def test_functionCallTwoButtonsBothBtnsPressedFunctionCallBothPressedExists(btn1 functionCallBothPressedMock): btn1Mock.is_pressed = True btn2Mock.is_pressed = True - func = functionCallTwoButtons(btn1Mock, btn2Mock, functionCall1Mock, functionCall2Mock, - functionCallBothPressed=functionCallBothPressedMock) + func = functionCallTwoButtons(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressed=functionCallBothPressedMock) func() functionCall1Mock.assert_not_called() functionCall2Mock.assert_not_called() @@ -70,13 +92,70 @@ def test_functionCallTwoButtonsBothBtnsPressedFunctionCallBothPressedIsNone(btn1 functionCall2Mock): btn1Mock.is_pressed = True btn2Mock.is_pressed = True - func = functionCallTwoButtons(btn1Mock, btn2Mock, functionCall1Mock, functionCall2Mock, - functionCallBothPressed=None) + func = functionCallTwoButtons(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressed=None) func() functionCall1Mock.assert_not_called() functionCall2Mock.assert_not_called() +def test_functionCallTwoButtonsOnlyBtn1Args(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressedMock): + btn1Mock.is_pressed = False + btn2Mock.is_pressed = False + func = functionCallTwoButtons(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressed=functionCallBothPressedMock) + func(btn1Mock.pin) + functionCall1Mock.assert_called_once_with() + functionCall2Mock.assert_not_called() + functionCallBothPressedMock.assert_not_called() + + +def test_functionCallTwoButtonsOnlyBtn2Args(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressedMock): + btn1Mock.is_pressed = False + btn2Mock.is_pressed = False + func = functionCallTwoButtons(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressed=functionCallBothPressedMock) + func(btn2Mock.pin) + functionCall1Mock.assert_not_called() + functionCall2Mock.assert_called_once_with() + functionCallBothPressedMock.assert_not_called() + + +def test_functionCallTwoButtonsUnknownBtnArgs(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressedMock): + btn1Mock.is_pressed = False + btn2Mock.is_pressed = False + func = functionCallTwoButtons(btn1Mock, + btn2Mock, + functionCall1Mock, + functionCall2Mock, + functionCallBothPressed=functionCallBothPressedMock) + func(9) + functionCall1Mock.assert_not_called() + functionCall2Mock.assert_not_called() + functionCallBothPressedMock.assert_not_called() + + mockedFunction1 = MagicMock() mockedFunction2 = MagicMock() mockedFunction3 = MagicMock() @@ -88,40 +167,32 @@ def two_button_controller(): mockedFunction2.reset_mock() mockedFunction3.reset_mock() return TwoButtonControl(bcmPin1=1, - bcmPin2=2, - functionCallBtn1=mockedFunction1, - functionCallBtn2=mockedFunction2, - functionCallTwoBtns=mockedFunction3, - pull_up_down='pull_up', - hold_mode=None, - hold_time=0.3, - name='TwoButtonControl') + bcmPin2=2, + functionCallBtn1=mockedFunction1, + functionCallBtn2=mockedFunction2, + functionCallTwoBtns=mockedFunction3, + pull_up_down='pull_up', + hold_mode=None, + hold_time=0.3, + name='TwoButtonControl') class TestTwoButtonControl: def test_init(self): TwoButtonControl(bcmPin1=1, - bcmPin2=2, - functionCallBtn1=mockedFunction1, - functionCallBtn2=mockedFunction2, - functionCallTwoBtns=mockedFunction3, - pull_up_down='pull_up', - hold_mode=None, - hold_time=0.3, - name='TwoButtonControl') + bcmPin2=2, + functionCallBtn1=mockedFunction1, + functionCallBtn2=mockedFunction2, + functionCallTwoBtns=mockedFunction3, + pull_up_down='pull_up', + hold_mode=None, + hold_time=0.3, + name='TwoButtonControl') def test_btn1_pressed(self, two_button_controller): pinA = two_button_controller.bcmPin1 pinB = two_button_controller.bcmPin2 - - def func(pin): - values = {pinA: False, pinB: True} - if pin in values: - return values[pin] - else: - print('Cannot find pin {} in values: {}'.format(pin, values)) - return None - two_button_control.GPIO.input.side_effect = func + GPIO.input.side_effect = lambda pin: {pinA: False, pinB: True}[pin] two_button_controller.action() mockedFunction1.assert_called_once() mockedFunction2.assert_not_called() @@ -132,7 +203,7 @@ def func(pin): def test_btn2_pressed(self, two_button_controller): pinA = two_button_controller.bcmPin1 pinB = two_button_controller.bcmPin2 - two_button_control.GPIO.input.side_effect = lambda pin: {pinA: True, pinB: False}[pin] + GPIO.input.side_effect = lambda pin: {pinA: True, pinB: False}[pin] two_button_controller.action() mockedFunction1.assert_not_called() mockedFunction2.assert_called_once() @@ -143,7 +214,7 @@ def test_btn2_pressed(self, two_button_controller): def test_btn1_and_btn2_pressed(self, two_button_controller): pinA = two_button_controller.bcmPin1 pinB = two_button_controller.bcmPin2 - two_button_control.GPIO.input.side_effect = lambda pin: {pinA: False, pinB: False}[pin] + GPIO.input.side_effect = lambda pin: {pinA: False, pinB: False}[pin] two_button_controller.action() mockedFunction1.assert_not_called() mockedFunction2.assert_not_called() @@ -152,5 +223,6 @@ def test_btn1_and_btn2_pressed(self, two_button_controller): assert mockedFunction3.call_count == 2 def test_repr(self, two_button_controller): - expected = "" + expected = ('') assert repr(two_button_controller) == expected diff --git a/components/gpio_control/test/test_gpio_control.py b/components/gpio_control/test/test_gpio_control.py index 5d5a1c107..4c139b67e 100644 --- a/components/gpio_control/test/test_gpio_control.py +++ b/components/gpio_control/test/test_gpio_control.py @@ -1,40 +1,254 @@ +import pytest +from mock import patch, call +from gpio_control import GPIOControl +from GPIODevices import (StatusLED, + LED, + RotaryEncoder, + SimpleButton, + ShutdownButton, + TwoButtonControl) + import configparser -import logging -from mock import patch, MagicMock -with patch('os.system', return_value=0): - from gpio_control import gpio_control -import function_calls -# def test_functionCallTwoButtonsOnlyBtn2Pressed(btn1Mock, btn2Mock, functionCall1Mock, functionCall2Mock, -# functionCallBothPressedMock): -# btn1Mock.is_pressed = False -# btn2Mock.is_pressed = True -# func = functionCallTwoButtons(btn1Mock, btn2Mock, functionCall1Mock, -# functionCallBothPressed=functionCallBothPressedMock) -# func() -# functionCall1Mock.assert_not_called() -# functionCall2Mock.assert_called_once_with() -# functionCallBothPressedMock.assert_not_called() +@pytest.fixture +def gpio_control_class(): + + class MockFunctionCalls: + + def funcTestWithoutParameter(self, *args): + return "funcTestWithoutParameter" + + def funcTestWithParameter(self, param1): + return f"funcTestWithParameter({param1})" -mockedFunction1 = MagicMock() -mockedFunction2 = MagicMock() -mockedFunction3 = MagicMock() + _gpio_control_class = GPIOControl(MockFunctionCalls) # function_calls will be mocked + return _gpio_control_class -mockedFunction1.side_effect = lambda *args: print('MockFunction1 called') -mockedFunction2.side_effect = lambda *args: print('MockFunction2 called') -mockedFunction3.side_effect = lambda *args: print('MockFunction3 called') -logging.basicConfig(level='DEBUG') +# Mock function 'getFunctionCall' and just return given parameter +def mock_gpio_control_getFunctionCall(self, func_name, func_args): + return str(func_name) + '-' + str(func_args) -def testMain(): +@patch.object(GPIOControl, 'getFunctionCall', mock_gpio_control_getFunctionCall) +def func_test_generateDevice_type(gpio_control_class, name, configArray, type): config = configparser.ConfigParser() - config.read('./test/gpio_settings_test.ini') + config[name] = configArray + with patch.object(type, '__init__', return_value=None) as mock_init: + with patch.object(type, '__del__', return_value=None, create=True): + device = gpio_control_class.generate_device(config[name], name) + assert isinstance(device, type) is True + del device + return mock_init + + +class TestGPIOControl: + + def test_getAllDevices_empty(self, gpio_control_class): + config = configparser.ConfigParser() + devices = gpio_control_class.get_all_devices(config) + assert not devices + + def test_getAllDevices_empty_device(self, gpio_control_class): + name = 'TestDevice' + config = configparser.ConfigParser() + config[name] = {} + devices = gpio_control_class.get_all_devices(config) + assert not devices + + def test_getAllDevices_type_unknown(self, gpio_control_class): + name = 'TestDevice' + config = configparser.ConfigParser() + config[name] = {'enabled': 'true'} + with patch.object(gpio_control_class, gpio_control_class.generate_device.__name__, + return_value=None) as mock_generate_device: + devices = gpio_control_class.get_all_devices(config) + mock_generate_device.assert_called_once_with(config[name], name) + assert not devices + + def test_getAllDevices_type_known(self, gpio_control_class): + name = 'TestDevice' + config = configparser.ConfigParser() + config[name] = {'enabled': 'true'} + with patch.object(gpio_control_class, gpio_control_class.generate_device.__name__, + return_value='TestDeviceResult') as mock_generate_device: + devices = gpio_control_class.get_all_devices(config) + mock_generate_device.assert_called_once_with(config[name], name) + assert len(devices) == 1 + assert devices[0] == 'TestDeviceResult' + + # --------------- + + def test_generateDevice_unknown(self, gpio_control_class): + name = 'TEST_unknown' + config = configparser.ConfigParser() + config[name] = {'Type': 'unknown', 'Pin': '5'} + device = gpio_control_class.generate_device(config[name], name) + assert device is None + + def test_generateDevice_StatusLED(self, gpio_control_class): + name = 'TEST_StatusLED' + configArray = {'Type': StatusLED.__name__, 'Pin': '5'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, StatusLED) + mock_init.assert_called_once_with(5, name=name) + + def test_generateDevice_MPDStatusLED(self, gpio_control_class): + name = 'TEST_MPDStatusLED' + configArray = {'Type': 'MPDStatusLED', 'Pin': '5'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, StatusLED) + mock_init.assert_called_once_with(5, name=name) + + def test_generateDevice_LED_default(self, gpio_control_class): + name = 'TEST_LED' + configArray = {'Type': LED.__name__, 'Pin': '5', } + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, LED) + mock_init.assert_called_once_with(5, name=name, initial_value=True) + + def test_generateDevice_LED(self, gpio_control_class): + name = 'TEST_LED' + configArray = {'Type': 'LED', 'Pin': '5', 'initial_value': 'False'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, LED) + mock_init.assert_called_once_with(5, name=name, initial_value=False) + + def test_generateDevice_RotaryEncoder_default(self, gpio_control_class): + name = 'TEST_RotaryEncoder' + configArray = {'Type': RotaryEncoder.__name__, 'Pin1': '5', 'Pin2': '6', + 'functionCall1': 'test_funcCall1', 'functionCall2': 'test_funcCall2'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, RotaryEncoder) + mock_init.assert_called_once_with(5, 6, "test_funcCall1-None", "test_funcCall2-None", 0.1, name=name) + + def test_generateDevice_RotaryEncoder(self, gpio_control_class): + name = 'TEST_RotaryEncoder' + configArray = {'Type': RotaryEncoder.__name__, 'Pin1': '5', 'Pin2': '6', + 'functionCall1': 'test_funcCall1', 'functionCall2': 'test_funcCall2', + 'timeBase': '1.1'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, RotaryEncoder) + mock_init.assert_called_once_with(5, 6, "test_funcCall1-None", "test_funcCall2-None", 1.1, name=name) + + def test_generateDevice_SimpleButton_default(self, gpio_control_class): + name = 'TEST_SimpleButton' + configArray = {'Type': SimpleButton.__name__, 'Pin': '5', + 'functionCall': 'test_funcCall1'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, SimpleButton) + mock_init.assert_called_once_with(5, action="test_funcCall1-None", + action2="None-None", name=name, bouncetime=500, + antibouncehack=False, edge='falling', hold_mode=None, + hold_time=0.3, pull_up_down='pull_up') + + def test_generateDevice_SimpleButton(self, gpio_control_class): + name = 'TEST_SimpleButton' + configArray = {'Type': SimpleButton.__name__, 'Pin': '5', + 'functionCall': 'test_funcCall1', 'functionCallArgs': 'test_funcCall1Args', + 'functionCall2': 'test_funcCall2', 'functionCall2Args': 'test_funcCall2Args', + 'bouncetime': 299, 'antibouncehack': 'True', 'edge': 'test_edge', + 'hold_mode': 'test_holdmode', 'hold_time': 1.3, 'pull_up_down': 'test_pull_up_down'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, SimpleButton) + mock_init.assert_called_once_with(5, action="test_funcCall1-test_funcCall1Args", + action2="test_funcCall2-test_funcCall2Args", name=name, bouncetime=299, + antibouncehack=True, edge='test_edge', hold_mode='test_holdmode', + hold_time=1.3, pull_up_down='test_pull_up_down') + + def test_generateDevice_Button_default(self, gpio_control_class): + name = 'TEST_Button' + configArray = {'Type': 'Button', 'Pin': '5', + 'functionCall': 'test_funcCall1'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, SimpleButton) + mock_init.assert_called_once_with(5, action="test_funcCall1-None", + action2="None-None", name=name, bouncetime=500, + antibouncehack=False, edge='falling', hold_mode=None, + hold_time=0.3, pull_up_down='pull_up') + + def test_generateDevice_Button(self, gpio_control_class): + name = 'TEST_Button' + configArray = {'Type': 'Button', 'Pin': '5', + 'functionCall': 'test_funcCall1', 'functionCallArgs': 'test_funcCall1Args', + 'functionCall2': 'test_funcCall2', 'functionCall2Args': 'test_funcCall2Args', + 'bouncetime': 299, 'antibouncehack': 'True', 'edge': 'test_edge', + 'hold_mode': 'test_holdmode', 'hold_time': 1.3, 'pull_up_down': 'test_pull_up_down'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, SimpleButton) + mock_init.assert_called_once_with(5, action="test_funcCall1-test_funcCall1Args", + action2="test_funcCall2-test_funcCall2Args", name=name, bouncetime=299, + antibouncehack=True, edge='test_edge', hold_mode='test_holdmode', + hold_time=1.3, pull_up_down='test_pull_up_down') + + def test_generateDevice_ShutdownButton_default(self, gpio_control_class): + name = 'TEST_ShutdownButton' + configArray = {'Type': ShutdownButton.__name__, 'Pin': '5'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, ShutdownButton) + mock_init.assert_called_once_with(pin=5, action="functionCallShutdown-None", + name=name, bouncetime=500, antibouncehack=False, edge='falling', + hold_time=3.0, iteration_time=0.2, led_pin=None, + pull_up_down='pull_up') + + def test_generateDevice_ShutdownButton(self, gpio_control_class): + name = 'TEST_ShutdownButton' + configArray = {'Type': ShutdownButton.__name__, 'Pin': '5', + 'functionCall': 'test_funcCall1', 'functionCallArgs': 'test_funcCall1Args', + 'bouncetime': 299, 'antibouncehack': 'True', 'edge': 'test_edge', + 'hold_time': 1.3, 'iteration_time': 1.2, 'led_pin': 9, + 'pull_up_down': 'test_pull_up_down'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, ShutdownButton) + mock_init.assert_called_once_with(pin=5, action="test_funcCall1-test_funcCall1Args", + name=name, bouncetime=299, antibouncehack=True, edge='test_edge', + hold_time=1.3, iteration_time=1.2, led_pin=9, + pull_up_down='test_pull_up_down') + + def test_generateDevice_TwoButtonControl_default(self, gpio_control_class): + name = 'TEST_TwoButtonControl' + configArray = {'Type': TwoButtonControl.__name__, 'Pin1': '5', 'Pin2': '6', + 'functionCall1': 'test_funcCall1', + 'functionCall2': 'test_funcCall2', + 'functionCallTwoButtons': 'test_funcCallTwoButtons'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, TwoButtonControl) + mock_init.assert_called_once_with(5, 6, "test_funcCall1-None", "test_funcCall2-None", + functionCallTwoBtns="test_funcCallTwoButtons-None", + pull_up_down='pull_up', hold_mode=None, hold_time=0.3, + bouncetime=500, edge='falling', antibouncehack=False, name=name) + + def test_generateDevice_TwoButtonControl(self, gpio_control_class): + name = 'TEST_TwoButtonControl' + configArray = {'Type': TwoButtonControl.__name__, 'Pin1': '5', 'Pin2': '6', + 'functionCall1': 'test_funcCall1', 'functionCall1Args': 'test_funcCall1Args', + 'functionCall2': 'test_funcCall2', 'functionCall2Args': 'test_funcCall2Args', + 'functionCallTwoButtons': 'test_funcCallTwoButtons', + 'functionCallTwoButtonsArgs': 'test_funcCallTwoButtonsArgs', + 'pull_up_down': 'test_pull_up_down', 'hold_mode': 'test_holdmode', 'hold_time': 1.3, + 'bouncetime': 299, 'edge': 'test_edge', 'antibouncehack': 'True'} + mock_init = func_test_generateDevice_type(gpio_control_class, name, configArray, TwoButtonControl) + mock_init.assert_called_once_with(5, 6, + "test_funcCall1-test_funcCall1Args", "test_funcCall2-test_funcCall2Args", + functionCallTwoBtns="test_funcCallTwoButtons-test_funcCallTwoButtonsArgs", + pull_up_down='test_pull_up_down', hold_mode='test_holdmode', hold_time=1.3, + bouncetime=299, edge='test_edge', antibouncehack=True, name=name) + + # --------------- + + def test_getFunctionCall_None_None(self, gpio_control_class): + result = gpio_control_class.getFunctionCall(None, None) + assert result(()) is None + result = gpio_control_class.getFunctionCall('None', None) + assert result(()) is None + result = gpio_control_class.getFunctionCall("nonExisting", None) + assert result(()) is None + + def test_getFunctionCall_withoutParam(self, gpio_control_class): + result = gpio_control_class.getFunctionCall("funcTestWithoutParameter", None) + assert result(()) == "funcTestWithoutParameter" + + def test_getFunctionCall_withParam(self, gpio_control_class): + result = gpio_control_class.getFunctionCall("funcTestWithParameter", "param1") + assert result(()) == "funcTestWithParameter(param1)" + + # --------------- - phoniebox_function_calls = function_calls.phoniebox_function_calls() - gpio_controler = gpio_control(phoniebox_function_calls) + def test_printAllDevices_empty(self, gpio_control_class): + with patch('builtins.print') as mock_print: + gpio_control_class.print_all_devices() + mock_print.assert_not_called - devices = gpio_controler.get_all_devices(config) - gpio_controler.print_all_devices() - pass + def test_printAllDevices_list(self, gpio_control_class): + with patch('builtins.print') as mock_print: + gpio_control_class.devices = ["test1", "test2"] + gpio_control_class.print_all_devices() + mock_print.assert_has_calls([call("test1"), call("test2")]) diff --git a/components/gpio_control/test/test_shutdown_button.py b/components/gpio_control/test/test_shutdown_button.py index 18ae10672..d8bf630fb 100644 --- a/components/gpio_control/test/test_shutdown_button.py +++ b/components/gpio_control/test/test_shutdown_button.py @@ -1,24 +1,33 @@ import pytest - from mock import Mock, patch -import mock - -from ..GPIODevices.shutdown_button import ShutdownButton, GPIO +from GPIODevices.shutdown_button import ShutdownButton, GPIO mock_time = Mock() - mocked_function = Mock() @pytest.fixture def shutdown_button(): mocked_function.reset_mock() - return ShutdownButton(pin=1, action=mocked_function) + return ShutdownButton(pin=1, action=mocked_function, led_pin=5) class TestShutDownButton(): - def test_init(self): - ShutdownButton(pin=1) + + @patch('time.sleep', mock_time) + def test_noled(self): + mocked_function_local = Mock() + shutdown_button_local = ShutdownButton(pin=1, action=mocked_function_local) + GPIO.input.side_effect = lambda *args: 0 + shutdown_button_local.callbackFunctionHandler() + mocked_function_local.assert_called_once() + + @patch('time.sleep', mock_time) + def test_antibounce(self, shutdown_button): + shutdown_button.antibouncehack = True + GPIO.input.side_effect = lambda *args: 1 + shutdown_button.callbackFunctionHandler() + mocked_function.assert_not_called() @patch('time.sleep', mock_time) def test_action_too_short_press(self, shutdown_button): @@ -40,3 +49,16 @@ def test_action_valid_press(self, shutdown_button): GPIO.input.side_effect = lambda *args: 0 shutdown_button.callbackFunctionHandler() mocked_function.assert_called_once() + + @patch('time.sleep', mock_time) + def test_callback(self, shutdown_button): + GPIO.input.side_effect = lambda *args: 0 + shutdown_button.callbackFunctionHandler(shutdown_button.pin, shutdown_button.pin) + mocked_function.assert_called_once_with(shutdown_button.pin) + + def test_repr(self): + button = ShutdownButton(name='test_repr', pin=1, hold_time=2.5, iteration_time=.8, led_pin=5, edge='rising', + bouncetime=200, antibouncehack=True, pull_up_down='pull_down') + expected = ('') + assert repr(button) == expected diff --git a/components/rfid-reader/RC522/setup_rc522.sh b/components/rfid-reader/RC522/setup_rc522.sh index ece9a9144..43ab60fa6 100644 --- a/components/rfid-reader/RC522/setup_rc522.sh +++ b/components/rfid-reader/RC522/setup_rc522.sh @@ -16,6 +16,13 @@ question() { printf "Please make sure that the RC522 reader is wired up correctly to the GPIO ports before continuing...\n" question "Continue" +printf "Use backward-compatible card ID (not suggested for new installations)?\n" +read -p "(y/N) " choice +case "$choice" in + y|Y ) printf "OFF" > "${JUKEBOX_HOME_DIR}"/settings/Rfidreader_Rc522_Readmode_UID;; + * ) printf "ON" > "${JUKEBOX_HOME_DIR}"/settings/Rfidreader_Rc522_Readmode_UID;; +esac + printf "Installing Python requirements for RC522...\n" sudo python3 -m pip install --upgrade --force-reinstall -q -r "${JUKEBOX_HOME_DIR}"/components/rfid-reader/RC522/requirements.txt @@ -29,6 +36,6 @@ sudo chown pi:www-data "${JUKEBOX_HOME_DIR}"/scripts/deviceName.txt sudo chmod 644 "${JUKEBOX_HOME_DIR}"/scripts/deviceName.txt printf "Restarting phoniebox-rfid-reader service...\n" -sudo systemctl start phoniebox-rfid-reader.service +sudo systemctl restart phoniebox-rfid-reader.service printf "Done.\n" diff --git a/components/smart-home-automation/MQTT-protocol/daemon_mqtt_client.py b/components/smart-home-automation/MQTT-protocol/daemon_mqtt_client.py index 6ffe2e1ad..583ad0c9b 100644 --- a/components/smart-home-automation/MQTT-protocol/daemon_mqtt_client.py +++ b/components/smart-home-automation/MQTT-protocol/daemon_mqtt_client.py @@ -10,6 +10,7 @@ import inotify.adapters import paho.mqtt.client as mqtt +import paho.mqtt.enums as mqtt_enum # ---------------------------------------------------------- # Prerequisites @@ -590,7 +591,7 @@ def fetchData(): # create client instance -client = mqtt.Client(config.get("mqttClientId")) +client = mqtt.Client(callback_api_version=mqtt_enum.CallbackAPIVersion.VERSION1, client_id=config.get("mqttClientId")) # configure authentication if config.get("mqttUsername") and config.get("mqttPassword"): diff --git a/composer.json b/composer.json index b0661a42f..dbe854805 100644 --- a/composer.json +++ b/composer.json @@ -3,9 +3,8 @@ "description": "A Raspberry Pi jukebox, playing local music, podcasts, web radio and streams triggered by RFID cards, web app or home automation. All plug and play via USB. GPIO scripts available.", "type": "project", "require-dev": { - "phpunit/phpunit": "^9", - "php-mock/php-mock-phpunit": "^2.6", - "mpyw/phpunit-patch-serializable-comparison": "^0.0.2" + "phpunit/phpunit": "^11", + "php-mock/php-mock-phpunit": "^2" }, "license": "MIT", "authors": [ @@ -15,7 +14,7 @@ ], "minimum-stability": "stable", "scripts": { - "test": "vendor/bin/phpunit ./tests/htdocs --exclude real-env", + "test": "vendor/bin/phpunit ./tests/htdocs --exclude-group real-env", "test-all": "vendor/bin/phpunit ./tests/htdocs" } } diff --git a/composer.lock b/composer.lock index 41ff99f13..c93e6544e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,131 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "890e6b393f255fb50b55d3866c724feb", + "content-hash": "171f8f92901ab01411dbb172c150ccee", "packages": [], "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:15:36+00:00" - }, - { - "name": "mpyw/phpunit-patch-serializable-comparison", - "version": "v0.0.2", - "source": { - "type": "git", - "url": "https://github.com/mpyw/phpunit-patch-serializable-comparison.git", - "reference": "262180fb1fc529b3735f147f754d5ba1f05e541b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mpyw/phpunit-patch-serializable-comparison/zipball/262180fb1fc529b3735f147f754d5ba1f05e541b", - "reference": "262180fb1fc529b3735f147f754d5ba1f05e541b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/comparator": "^1.0 || ^2.0 || ^3.0 || ^4.0" - }, - "type": "library", - "autoload": { - "files": [ - "./files/ComparisonFailure.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "mpyw", - "email": "ryosuke_i_628@yahoo.co.jp" - } - ], - "description": "Fixes assertSame/assertEquals serialization errors running in separate processes.", - "keywords": [ - "bug", - "fix", - "isolation", - "patch", - "phpunit", - "process", - "processes", - "separate", - "serialization" - ], - "support": { - "issues": "https://github.com/mpyw/phpunit-patch-serializable-comparison/issues", - "source": "https://github.com/mpyw/phpunit-patch-serializable-comparison/tree/v0.0.2" - }, - "time": "2021-07-04T05:15:38+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.11.1", @@ -190,25 +68,27 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -216,7 +96,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -240,9 +120,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2024-01-07T17:17:35+00:00" }, { "name": "phar-io/manifest", @@ -357,28 +237,28 @@ }, { "name": "php-mock/php-mock", - "version": "2.4.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d" + "reference": "fff1a621ebe54100fa3bd852e7be57773a0c0127" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d", - "reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/fff1a621ebe54100fa3bd852e7be57773a0c0127", + "reference": "fff1a621ebe54100fa3bd852e7be57773a0c0127", "shasum": "" }, "require": { "php": "^5.6 || ^7.0 || ^8.0", - "phpunit/php-text-template": "^1 || ^2 || ^3" + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4" }, "replace": { "malkusch/php-mock": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0", + "squizlabs/php_codesniffer": "^3.8" }, "suggest": { "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." @@ -421,7 +301,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock/issues", - "source": "https://github.com/php-mock/php-mock/tree/2.4.1" + "source": "https://github.com/php-mock/php-mock/tree/2.5.0" }, "funding": [ { @@ -429,29 +309,29 @@ "type": "github" } ], - "time": "2023-06-12T20:48:52+00:00" + "time": "2024-02-10T21:07:01+00:00" }, { "name": "php-mock/php-mock-integration", - "version": "2.2.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-integration.git", - "reference": "04f4a8d5442ca457b102b5204673f77323e3edb5" + "reference": "ec6a00a8129d50ed0f07907c91e3274ca4ade877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/04f4a8d5442ca457b102b5204673f77323e3edb5", - "reference": "04f4a8d5442ca457b102b5204673f77323e3edb5", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/ec6a00a8129d50ed0f07907c91e3274ca4ade877", + "reference": "ec6a00a8129d50ed0f07907c91e3274ca4ade877", "shasum": "" }, "require": { "php": ">=5.6", - "php-mock/php-mock": "^2.4", - "phpunit/php-text-template": "^1 || ^2 || ^3" + "php-mock/php-mock": "^2.5", + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10" + "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11" }, "type": "library", "autoload": { @@ -484,7 +364,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-integration/issues", - "source": "https://github.com/php-mock/php-mock-integration/tree/2.2.1" + "source": "https://github.com/php-mock/php-mock-integration/tree/2.3.0" }, "funding": [ { @@ -492,26 +372,26 @@ "type": "github" } ], - "time": "2023-02-13T09:51:29+00:00" + "time": "2024-02-10T21:37:25+00:00" }, { "name": "php-mock/php-mock-phpunit", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "3dabfd474d43da4d1d2fee5260c634457c5da344" + "reference": "e1f7e795990b00937376e345883ea68ca3bda7e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/3dabfd474d43da4d1d2fee5260c634457c5da344", - "reference": "3dabfd474d43da4d1d2fee5260c634457c5da344", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/e1f7e795990b00937376e345883ea68ca3bda7e0", + "reference": "e1f7e795990b00937376e345883ea68ca3bda7e0", "shasum": "" }, "require": { "php": ">=7", - "php-mock/php-mock-integration": "^2.2.1", - "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17" + "php-mock/php-mock-integration": "^2.3", + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11" }, "require-dev": { "mockery/mockery": "^1.3.6" @@ -552,7 +432,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-phpunit/issues", - "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.9.0" + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.10.0" }, "funding": [ { @@ -560,39 +440,39 @@ "type": "github" } ], - "time": "2023-12-01T21:50:22+00:00" + "time": "2024-02-11T07:24:16+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "11.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "5e238e4b982cb272bf9faeee6f33af83d465d0e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5e238e4b982cb272bf9faeee6f33af83d465d0e2", + "reference": "5e238e4b982cb272bf9faeee6f33af83d465d0e2", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", + "nikic/php-parser": "^5.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-text-template": "^4.0", + "sebastian/code-unit-reverse-lookup": "^4.0", + "sebastian/complexity": "^4.0", + "sebastian/environment": "^7.0", + "sebastian/lines-of-code": "^3.0", + "sebastian/version": "^5.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -601,7 +481,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "11.0-dev" } }, "autoload": { @@ -630,7 +510,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.0" }, "funding": [ { @@ -638,32 +518,32 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2024-02-02T06:03:46+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/99e95c94ad9500daca992354fa09d7b99abe2210", + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -690,7 +570,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.0" }, "funding": [ { @@ -698,28 +579,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2024-02-02T06:05:04+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5d8d9355a16d8cc5a1305b0a85342cfa420612be", + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -727,7 +608,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -753,7 +634,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.0" }, "funding": [ { @@ -761,32 +643,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2024-02-02T06:05:50+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/d38f6cbff1cdb6f40b03c9811421561668cc133e", + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -812,7 +694,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.0" }, "funding": [ { @@ -820,32 +703,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2024-02-02T06:06:56+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8a59d9e25720482ee7fcdf296595e08795b84dc5", + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -871,7 +754,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.0" }, "funding": [ { @@ -879,24 +763,23 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2024-02-02T06:08:01+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.15", + "version": "11.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" + "reference": "de24e7e7c67fbf437f7b6cd7bc919f2dc6fd89d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de24e7e7c67fbf437f7b6cd7bc919f2dc6fd89d4", + "reference": "de24e7e7c67fbf437f7b6cd7bc919f2dc6fd89d4", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -906,27 +789,25 @@ "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-invoker": "^5.0", + "phpunit/php-text-template": "^4.0", + "phpunit/php-timer": "^7.0", + "sebastian/cli-parser": "^3.0", + "sebastian/code-unit": "^3.0", + "sebastian/comparator": "^6.0", + "sebastian/diff": "^6.0", + "sebastian/environment": "^7.0", + "sebastian/exporter": "^6.0", + "sebastian/global-state": "^7.0", + "sebastian/object-enumerator": "^6.0", + "sebastian/type": "^5.0", + "sebastian/version": "^5.0" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -934,7 +815,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "11.0-dev" } }, "autoload": { @@ -966,7 +847,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.0.3" }, "funding": [ { @@ -982,32 +863,32 @@ "type": "tidelift" } ], - "time": "2023-12-01T16:55:19+00:00" + "time": "2024-02-10T06:31:16+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "efd6ce5bb8131fe981e2f879dbd47605fbe0cc6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efd6ce5bb8131fe981e2f879dbd47605fbe0cc6f", + "reference": "efd6ce5bb8131fe981e2f879dbd47605fbe0cc6f", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1030,7 +911,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.0" }, "funding": [ { @@ -1038,32 +920,32 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-02-02T05:48:04+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6634549cb8d702282a04a774e36a7477d2bd9015", + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1086,7 +968,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.0" }, "funding": [ { @@ -1094,32 +977,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2024-02-02T05:50:41+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/df80c875d3e459b45c6039e4d9b71d4fbccae25d", + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1141,7 +1024,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.0" }, "funding": [ { @@ -1149,34 +1033,36 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2024-02-02T05:52:17+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bd0f2fa5b9257c69903537b266ccb80fcf940db8", + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1215,7 +1101,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.0" }, "funding": [ { @@ -1223,33 +1110,33 @@ "type": "github" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2024-02-02T05:53:45+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "88a434ad86150e11a606ac4866b09130712671f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/88a434ad86150e11a606ac4866b09130712671f0", + "reference": "88a434ad86150e11a606ac4866b09130712671f0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1272,7 +1159,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.0" }, "funding": [ { @@ -1280,33 +1168,33 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2024-02-02T05:55:19+00:00" }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "3e3f502419518897a923aa1c64d51f9def2e0aff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3e3f502419518897a923aa1c64d51f9def2e0aff", + "reference": "3e3f502419518897a923aa1c64d51f9def2e0aff", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1338,7 +1226,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.0" }, "funding": [ { @@ -1346,27 +1235,27 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-02-02T05:56:35+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "100d8b855d7180f79f9a9a5c483f2d960581c3ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/100d8b855d7180f79f9a9a5c483f2d960581c3ea", + "reference": "100d8b855d7180f79f9a9a5c483f2d960581c3ea", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-posix": "*" @@ -1374,7 +1263,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1393,7 +1282,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -1401,7 +1290,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.0.0" }, "funding": [ { @@ -1409,34 +1299,34 @@ "type": "github" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2024-02-02T05:57:54+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "d0c0a93fc746b0c066037f1e7d09104129e868ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d0c0a93fc746b0c066037f1e7d09104129e868ff", + "reference": "d0c0a93fc746b0c066037f1e7d09104129e868ff", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1478,7 +1368,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.0.0" }, "funding": [ { @@ -1486,38 +1377,35 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-02-02T05:58:52+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "590e7cbc6565fa2e26c3df4e629a34bb0bc00c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/590e7cbc6565fa2e26c3df4e629a34bb0bc00c17", + "reference": "590e7cbc6565fa2e26c3df4e629a34bb0bc00c17", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1536,13 +1424,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.0" }, "funding": [ { @@ -1550,33 +1439,33 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-02-02T05:59:33+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/376c5b3f6b43c78fdc049740bca76a7c846706c0", + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1599,7 +1488,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.0" }, "funding": [ { @@ -1607,34 +1497,34 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2024-02-02T06:00:36+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1656,7 +1546,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.0" }, "funding": [ { @@ -1664,32 +1555,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2024-02-02T06:01:29+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/bb2a6255d30853425fd38f032eb64ced9f7f132d", + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1711,7 +1602,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.0" }, "funding": [ { @@ -1719,32 +1611,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2024-02-02T06:02:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b75224967b5a466925c6d54e68edd0edf8dd4ed4", + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1774,62 +1666,8 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.0" }, "funding": [ { @@ -1837,32 +1675,32 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-02-02T06:08:48+00:00" }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8502785eb3523ca0dd4afe9ca62235590020f3f", + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1885,7 +1723,8 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.0.0" }, "funding": [ { @@ -1893,29 +1732,29 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2024-02-02T06:09:34+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/13999475d2cb1ab33cb73403ba356a814fdbb001", + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1938,7 +1777,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.0" }, "funding": [ { @@ -1946,7 +1786,7 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2024-02-02T06:10:47+00:00" }, { "name": "theseer/tokenizer", diff --git a/docs/2019-Phoniebox-Calendar.jpg b/docs/2019-Phoniebox-Calendar.jpg deleted file mode 100644 index 6e020f606..000000000 Binary files a/docs/2019-Phoniebox-Calendar.jpg and /dev/null differ diff --git a/docs/2020-Phoniebox-Calendar.jpg b/docs/2020-Phoniebox-Calendar.jpg deleted file mode 100644 index af486c583..000000000 Binary files a/docs/2020-Phoniebox-Calendar.jpg and /dev/null differ diff --git a/docs/2021-Phoniebox-Calendar.jpg b/docs/2021-Phoniebox-Calendar.jpg deleted file mode 100644 index b62591cf1..000000000 Binary files a/docs/2021-Phoniebox-Calendar.jpg and /dev/null differ diff --git a/docs/2022-Phoniebox-Calendar.jpg b/docs/2022-Phoniebox-Calendar.jpg deleted file mode 100644 index a7541ed35..000000000 Binary files a/docs/2022-Phoniebox-Calendar.jpg and /dev/null differ diff --git a/docs/2023-Phoniebox-Calendar.jpg b/docs/2023-Phoniebox-Calendar.jpg deleted file mode 100644 index cd42a55b1..000000000 Binary files a/docs/2023-Phoniebox-Calendar.jpg and /dev/null differ diff --git a/docs/img/gallery/Alpha-20170310_h90-01.jpg b/docs/img/gallery/Alpha-20170310_h90-01.jpg deleted file mode 100644 index 734079970..000000000 Binary files a/docs/img/gallery/Alpha-20170310_h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/Elsa-20171210_h90-01.jpg b/docs/img/gallery/Elsa-20171210_h90-01.jpg deleted file mode 100644 index b13794e04..000000000 Binary files a/docs/img/gallery/Elsa-20171210_h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/Geliras-20171228-Jukebox-01-h90.jpg b/docs/img/gallery/Geliras-20171228-Jukebox-01-h90.jpg deleted file mode 100644 index 7cfc8685b..000000000 Binary files a/docs/img/gallery/Geliras-20171228-Jukebox-01-h90.jpg and /dev/null differ diff --git a/docs/img/gallery/Jens-Braeuer-Jan-2018-h90-01.jpg b/docs/img/gallery/Jens-Braeuer-Jan-2018-h90-01.jpg deleted file mode 100644 index cb4aff21f..000000000 Binary files a/docs/img/gallery/Jens-Braeuer-Jan-2018-h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/KingKahn-20180101-Jukebox-01-h90.jpg b/docs/img/gallery/KingKahn-20180101-Jukebox-01-h90.jpg deleted file mode 100644 index 5a8c23d28..000000000 Binary files a/docs/img/gallery/KingKahn-20180101-Jukebox-01-h90.jpg and /dev/null differ diff --git a/docs/img/gallery/Markus-20171218_h90-01.jpg b/docs/img/gallery/Markus-20171218_h90-01.jpg deleted file mode 100644 index c4bbf8adb..000000000 Binary files a/docs/img/gallery/Markus-20171218_h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/Steph-20171215_h90-01.jpg b/docs/img/gallery/Steph-20171215_h90-01.jpg deleted file mode 100644 index f6850bc9c..000000000 Binary files a/docs/img/gallery/Steph-20171215_h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/UlliH-20171210_h90-01.jpg b/docs/img/gallery/UlliH-20171210_h90-01.jpg deleted file mode 100644 index e7c2f5b85..000000000 Binary files a/docs/img/gallery/UlliH-20171210_h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/hailogugo-20171222-h90-01.jpg b/docs/img/gallery/hailogugo-20171222-h90-01.jpg deleted file mode 100644 index b13131ff1..000000000 Binary files a/docs/img/gallery/hailogugo-20171222-h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/tullm-jan2018_h90-01.jpg b/docs/img/gallery/tullm-jan2018_h90-01.jpg deleted file mode 100644 index 41ed900c6..000000000 Binary files a/docs/img/gallery/tullm-jan2018_h90-01.jpg and /dev/null differ diff --git a/docs/img/gallery/tullm-jan2018_h90-02.jpg b/docs/img/gallery/tullm-jan2018_h90-02.jpg deleted file mode 100644 index 126228d1e..000000000 Binary files a/docs/img/gallery/tullm-jan2018_h90-02.jpg and /dev/null differ diff --git a/docs/img/iFun-YouTube.jpg b/docs/img/iFun-YouTube.jpg deleted file mode 100644 index 7cc0bd41c..000000000 Binary files a/docs/img/iFun-YouTube.jpg and /dev/null differ diff --git a/docs/img/web-app-iphone-screens.jpg b/docs/img/web-app-iphone-screens.jpg deleted file mode 100644 index c7ca44be8..000000000 Binary files a/docs/img/web-app-iphone-screens.jpg and /dev/null differ diff --git a/htdocs/inc.setWifi.php b/htdocs/inc.setWifi.php index a7a77987f..691e8a247 100755 --- a/htdocs/inc.setWifi.php +++ b/htdocs/inc.setWifi.php @@ -1,112 +1,83 @@ $post_value ) { + $tempPOST = $_POST; + $_POST=array(); //clear + foreach ( $tempPOST as $post_key => $post_value ) { + unset($temp_ssid); + unset($temp_pass); + unset($temp_prio); if ( substr(trim($post_key), 0, 9) == "WIFIssid_" ) { - $WIFIssid = trim($post_value); + $temp_ssid = trim($post_value); $post_key = "WIFIpass_".substr(trim($post_key), 9); - $post_value = $_POST[$post_key]; - $WIFIpass = trim($post_value); + $post_value = $tempPOST[$post_key]; + $temp_pass = trim($post_value); $post_key = "WIFIprio_".substr(trim($post_key), 9); - $post_value = $_POST[$post_key]; - $WIFIprio = trim($post_value); + $post_value = $tempPOST[$post_key]; + $temp_prio = trim($post_value); - if ( isset($WIFIssid) && $WIFIssid != "") { - if(isset($WIFIpass) && strlen($WIFIpass) >= 8) { - $networks[$WIFIssid] = $WIFIpass; - } - if(isset($WIFIprio) && $WIFIprio != "") { - $priorities[$WIFIssid] = $WIFIprio; + if (isset($temp_ssid) && $temp_ssid != "" && isset($temp_pass) && strlen($temp_pass) >= 8) { + if(!isset($temp_prio) || !is_numeric($temp_prio)) { + $temp_prio = 0; } + $exec .= "add_wireless_network wlan0 ".$temp_ssid." ".$temp_pass." ".$temp_prio."\n"; } } } - $_POST=array(); //clear - // make multiline bash - $exec = "bash -e <<'END'\n"; - $exec .= "echo 'ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\ncountry=DE\n\n' | sudo tee /etc/wpa_supplicant/wpa_supplicant.conf\n"; - foreach ( $networks as $WIFIssid => $WIFIpass ) { - $WIFIprio = $priorities[$WIFIssid]; - if (strlen($WIFIpass) < 64) { - $WIFIpass = trim(exec("wpa_passphrase '".$WIFIssid."' '".$WIFIpass."' | grep -v -F '#psk' | grep -F 'psk' | cut -d= -f2")); + $exec .= "END\n"; + exec("sudo bash -c '". $exec . "'"); +} + +/* +* get all configured wifis +*/ +$network_confs_shell = shell_exec("sudo bash -c 'source ".$conf['scripts_abs']."/helperscripts/inc.networkHelper.sh && get_wireless_networks'"); +$network_confs = explode(' ',$network_confs_shell); + +$networks = array(); +foreach($network_confs as $line){ + unset($temp_ssid); + unset($temp_pass); + unset($temp_prio); + unset($temp_active); + + $network_conf = explode(':',$line); + $temp_ssid = trim($network_conf[0]); + $temp_pass = trim($network_conf[1]); + $temp_prio = trim($network_conf[2]); + $temp_active = isset($active_essid) && $temp_ssid == $active_essid; + + if(isset($temp_ssid) && $temp_ssid != "" && isset($temp_pass) && $temp_pass != "") { + if(!isset($temp_prio) || !is_numeric($temp_prio)) { + $temp_prio = 0; + } + $temp_entry = array($temp_ssid => [ $temp_pass, $temp_prio, $temp_active ]); + # use different methods to have the same behavior: the data of the first appearance are kept, following will be ignored + if($temp_active) { + $networks = array_merge($temp_entry, $networks); + } else { + $networks = $networks + $temp_entry; } - $exec .= "echo 'network={\n\tssid=\"".$WIFIssid."\"\n\tpsk=".$WIFIpass."\n\tpriority=".$WIFIprio."\n}' | sudo tee -a /etc/wpa_supplicant/wpa_supplicant.conf\n"; } - - $exec .= "sudo chown root:netdev /etc/wpa_supplicant/wpa_supplicant.conf\n"; - $exec .= "sudo chmod 664 /etc/wpa_supplicant/wpa_supplicant.conf\n"; - $exec .= "END\n"; - exec($exec); } +unset($temp_ssid); +unset($temp_pass); +unset($temp_prio); + ?> -
'> +'>
@@ -127,16 +98,19 @@
    $WIFIpass ) { - $WIFIprio = $priorities[$WIFIssid]; + foreach ( $networks as $WIFIssid => $WIFIconf ) { + $WIFIpass = $WIFIconf[0]; + $WIFIprio = $WIFIconf[1]; + $WIFIactive = $WIFIconf[2]; + $WIFIindex = $network_index++; ?>
  • -
    " id="WIFIssid_" name="WIFIssid_" placeholder="" class="form-control input-md" type="text"> + print $WIFIssid; + ?>" id="WIFIssid_" name="WIFIssid_" placeholder="" class="form-control input-md" type="text" >
    - +
    " id="WIFIpass_" name="WIFIpass_" placeholder="" class="form-control input-md" type="password" minlength="8" maxlength="63"> + print $WIFIpass; + ?>" id="WIFIpass_" name="WIFIpass_" placeholder="" class="form-control input-md" type="password" minlength="8" maxlength="63" >
    - +
    " id="WIFIprio_" name="WIFIprio_" placeholder="" class="form-control input-md" type="number" min="0" max="100"> + print $WIFIprio; + ?>" id="WIFIprio_" name="WIFIprio_" placeholder="" class="form-control input-md" type="number" min="0" max="100" >
  • diff --git a/htdocs/userScripts.php b/htdocs/userScripts.php index 536c06743..2cac71279 100755 --- a/htdocs/userScripts.php +++ b/htdocs/userScripts.php @@ -198,7 +198,7 @@ /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/misc/sampleconfigs/autohotspot/NetworkManager/autohotspot.service b/misc/sampleconfigs/autohotspot/NetworkManager/autohotspot.service new file mode 100644 index 000000000..746b960e1 --- /dev/null +++ b/misc/sampleconfigs/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/misc/sampleconfigs/autohotspot/NetworkManager/autohotspot.timer b/misc/sampleconfigs/autohotspot/NetworkManager/autohotspot.timer new file mode 100644 index 000000000..e3a52baf5 --- /dev/null +++ b/misc/sampleconfigs/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/misc/sampleconfigs/autohotspot.sh.stretch-default2-Hotspot.sample b/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot similarity index 86% rename from misc/sampleconfigs/autohotspot.sh.stretch-default2-Hotspot.sample rename to misc/sampleconfigs/autohotspot/dhcpcd/autohotspot index cdb56bad6..0f5699097 100644 --- a/misc/sampleconfigs/autohotspot.sh.stretch-default2-Hotspot.sample +++ b/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #version 0.962-N/HS #You may share this script on the condition all references to RaspberryConnect.com @@ -12,19 +12,25 @@ #Additions where made for the Phoniebox project #https://github.com/MiczFlor/RPi-Jukebox-RFID -if [ $# -gt 0 ] ; then - if [ $# -eq 1 ] && [ "$1" == "--force-hotspot" ]; then - FORCE_HOTSPOT=1 - else - echo "ignoring unrecognized parameter: $*" - fi -fi +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="wlan0" #device name to use. Default is wlan0. +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 @@ -50,7 +56,7 @@ CreateAdHocNetwork() { echo "Creating Hotspot" ip link set dev "$wifidev" down - ip a add %AUTOHOTSPOT_IP%/24 brd + dev "$wifidev" + 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 @@ -81,7 +87,7 @@ CheckWifiUp() } InitWPA() { - wpa_supplicant -B -i "$wifidev" -c /etc/wpa_supplicant/wpa_supplicant.conf >/dev/null 2>&1 + systemctl restart "$daemon_service" } CheckServices() @@ -108,28 +114,29 @@ CheckServices() CheckDevice() { + echo "Device availability check" local j=0 while [ true ] #wait for wifi if busy, usb wifi is slower. do - echo "Device availability check: try $j" + 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 connected" + echo "No wifi device '$wifidev' connected" InitWPA exit 1 elif (iw dev "$wifidev" info 2>&1 >/dev/null) ; then - echo "wifi device available" + 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" + echo "Wifi is down. Setting up" ip link set dev "$wifidev" up sleep 2 fi return else - echo "wifi is deactivated" + echo "Wifi is deactivated" exit 0 fi else @@ -144,22 +151,21 @@ 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:") - echo "SSID availability check: try $j" if [ $j -ge 5 ]; then ssidreply="" i=1 elif [ -z "$ssidreply" ] ; then - echo "Error scan SSID's: $scanreply" + echo "Error scan SSID's, try $j: $scanreply" j=$((j + 1)) sleep 2 else - echo "SSID's in range:" - echo "$ssidreply" + #success i=1 fi done @@ -168,12 +174,15 @@ FindSSID() do if (echo "$ssidreply" | grep "$ssid") >/dev/null 2>&1 then - #Valid SSid found, passing to script - echo "Valid SSID Detected, assesing Wifi status" + echo "Valid SSID detected, assessing Wifi status" ssidChk=$ssid - return 0 + break fi done + + if [ "$ssidChk" == "$NO_SSID" ]; then + echo "No Valid SSID detected" + fi } CheckSSID() diff --git a/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot-daemon.service b/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot-daemon.service new file mode 100644 index 000000000..d0381e5ce --- /dev/null +++ b/misc/sampleconfigs/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/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot.service b/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot.service new file mode 100644 index 000000000..746b960e1 --- /dev/null +++ b/misc/sampleconfigs/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/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot.timer b/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot.timer new file mode 100644 index 000000000..e3a52baf5 --- /dev/null +++ b/misc/sampleconfigs/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/misc/sampleconfigs/dnsmasq.conf.stretch-default2-Hotspot.sample b/misc/sampleconfigs/autohotspot/dhcpcd/dnsmasq.conf similarity index 86% rename from misc/sampleconfigs/dnsmasq.conf.stretch-default2-Hotspot.sample rename to misc/sampleconfigs/autohotspot/dhcpcd/dnsmasq.conf index 96674a303..b8b525407 100644 --- a/misc/sampleconfigs/dnsmasq.conf.stretch-default2-Hotspot.sample +++ b/misc/sampleconfigs/autohotspot/dhcpcd/dnsmasq.conf @@ -2,6 +2,6 @@ #stop DNSmasq from using resolv.conf no-resolv #Interface to use -interface=wlan0 +interface=%WIFI_INTERFACE% bind-interfaces dhcp-range=%IP_WITHOUT_LAST_SEGMENT%.100,%IP_WITHOUT_LAST_SEGMENT%.200,12h diff --git a/misc/sampleconfigs/hostapd.stretch-default2-Hotspot.sample b/misc/sampleconfigs/autohotspot/dhcpcd/hostapd old mode 100755 new mode 100644 similarity index 100% rename from misc/sampleconfigs/hostapd.stretch-default2-Hotspot.sample rename to misc/sampleconfigs/autohotspot/dhcpcd/hostapd diff --git a/misc/sampleconfigs/hostapd.conf.stretch-default2-Hotspot.sample b/misc/sampleconfigs/autohotspot/dhcpcd/hostapd.conf old mode 100755 new mode 100644 similarity index 93% rename from misc/sampleconfigs/hostapd.conf.stretch-default2-Hotspot.sample rename to misc/sampleconfigs/autohotspot/dhcpcd/hostapd.conf index 6b2195dc6..014b86e6b --- a/misc/sampleconfigs/hostapd.conf.stretch-default2-Hotspot.sample +++ b/misc/sampleconfigs/autohotspot/dhcpcd/hostapd.conf @@ -1,5 +1,5 @@ #2.4GHz setup wifi 80211 b,g,n -interface=wlan0 +interface=%WIFI_INTERFACE% driver=nl80211 ssid=%AUTOHOTSPOTssid% hw_mode=g diff --git a/misc/sampleconfigs/dhcpcd.conf.buster-default-noHotspot.sample b/misc/sampleconfigs/dhcpcd.conf.buster-default-noHotspot.sample index f00ed04f4..ef1bcb741 100755 --- a/misc/sampleconfigs/dhcpcd.conf.buster-default-noHotspot.sample +++ b/misc/sampleconfigs/dhcpcd.conf.buster-default-noHotspot.sample @@ -1,4 +1,4 @@ -interface wlan0 +interface %WIFIinterface% static ip_address=%WIFIip%/24 static routers=%WIFIipRouter% -static domain_name_servers=8.8.8.8 %WIFIipRouter% +static domain_name_servers=%WIFIipRouter% %WIFIipExtDNS% diff --git a/misc/sampleconfigs/gpio_settings.ini.sample b/misc/sampleconfigs/gpio_settings.ini.sample index e14444991..4f84ba01b 100644 --- a/misc/sampleconfigs/gpio_settings.ini.sample +++ b/misc/sampleconfigs/gpio_settings.ini.sample @@ -14,6 +14,9 @@ timeBase: 0.1 ;only for RotaryEncoder functionCall1: functionCallVolU functionCall2: functionCallVolD functionCallTwoButtons: functionCallVol0 ;only for TwoButtonControl +;functionCall1Args: 1 +;functionCall2Args: 1 +;functionCallTwoButtonsArgs: x [PrevNextControl] enabled: False @@ -68,6 +71,7 @@ pull_up_down: pull_up hold_time: 0.3 hold_mode: Repeat functionCall: functionCallVolU +;functionCallArgs: 1 [VolumeDown] enabled: False @@ -77,6 +81,7 @@ pull_up_down: pull_up hold_time: 0.3 hold_mode: Repeat functionCall: functionCallVolD +;functionCallArgs: 1 [NextSong] enabled: False @@ -98,6 +103,7 @@ Type: Button Pin: 7 pull_up_down: pull_up functionCall: functionCallPlayerSeekFwd +;functionCallArgs: 10 [Rewind] enabled: False @@ -105,6 +111,7 @@ Type: Button Pin: 8 pull_up_down: pull_up functionCall: functionCallPlayerSeekBack +;functionCallArgs: 10 [Halt] enabled: False @@ -119,3 +126,20 @@ Type: Button Pin: 21 pull_up_down: pull_up functionCall: functionCallPlayerStop + +[TriggerPlayCard1] +enabled: False +Type: Button +Pin: 4 +pull_up_down: pull_up +functionCall: functionCallTriggerPlayCardId +functionCallArgs: 1 + +[TriggerPlayFolderSomeRelativeFolderName] +enabled: False +Type: Button +Pin: 4 +pull_up_down: pull_up +functionCall: functionCallTriggerPlayFolder +functionCallArgs: someRelativeFolderName + diff --git a/misc/sampleconfigs/mpd.conf.buster-default.sample b/misc/sampleconfigs/mpd.conf.buster-default.sample deleted file mode 100755 index 229882a2a..000000000 --- a/misc/sampleconfigs/mpd.conf.buster-default.sample +++ /dev/null @@ -1,409 +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 "%DIRaudioFolders%" -# -# 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 "/home/pi/RPi-Jukebox-RFID/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 "/var/lib/mpd/tag_cache" -# -# 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 "syslog" -# -# 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 "/run/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 "/var/lib/mpd/state" -# -# The location of the sticker database. This is a database which -# manages dynamic information attached to songs. -# -sticker_file "/var/lib/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 "localhost" -# -# 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" -} - -# -############################################################################### - -# 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 "My ALSA Device" -# device "hw:0,0" # optional -# mixer_type "hardware" # optional -# mixer_device "default" # optional - mixer_control "%AUDIOiFace%" # optional -# mixer_index "0" # optional -} -# -# 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/misc/sampleconfigs/mpd.conf.sample b/misc/sampleconfigs/mpd.conf.sample old mode 100755 new mode 100644 index 812d43b21..ccf639b21 --- a/misc/sampleconfigs/mpd.conf.sample +++ b/misc/sampleconfigs/mpd.conf.sample @@ -1,12 +1,12 @@ # An example configuration file for MPD. # Read the user manual for documentation: http://www.musicpd.org/doc/user/ -# or /usr/share/doc/mpd/user-manual.html +# 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 +# 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. @@ -14,29 +14,30 @@ music_directory "%DIRaudioFolders%" # # This setting sets the MPD internal playlist directory. The purpose of this -# directory is storage for playlists created by MPD. The server will use +# 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 root: +# playlists are inside the Phoniebox path: playlist_directory "/home/pi/RPi-Jukebox-RFID/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 +# 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 "/var/lib/mpd/tag_cache" -# +# # 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, otherwise logging is disabled. +# setting defaults to logging to syslog, or to journal if mpd was started as +# a systemd service. # -log_file "/var/log/mpd/mpd.log" +log_file "syslog" # # 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 @@ -46,7 +47,7 @@ pid_file "/run/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 +# it was brought down. This setting is disabled by default and the server # state will be reset on server start up. # state_file "/var/lib/mpd/state" @@ -78,7 +79,8 @@ user "root" # 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 +# to have mpd listen on every address. Not effective if systemd socket +# activation is in use. # # For network bind_to_address "localhost" @@ -91,20 +93,12 @@ bind_to_address "localhost" # #port "6600" # -# This setting controls the type of information which is logged. Available +# 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" -# -# If you have a problem with your MP3s ending abruptly it is recommended that -# you set this argument to "no" to attempt to fix the problem. If this solves -# the problem, it is highly recommended to fix the MP3 files with vbrfix -# (available as vbrfix in the debian archive), at which -# point gapless MP3 playback can be enabled. -# -#gapless_mp3_playback "yes" +log_level "default" # # Setting "restore_paused" to "yes" puts MPD into pause mode instead # of starting playback after startup. @@ -116,12 +110,16 @@ bind_to_address "localhost" # #save_absolute_paths_in_playlists "no" # -# This setting defines a list of tag types that will be extracted during the +# 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 mpd.conf man page. +# found in the user manual. #metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc" # -# This setting enables automatic update of MPD's database when files in +# 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" @@ -136,7 +134,7 @@ auto_update_depth "10" # Symbolic link behavior ###################################################### # -# If this setting is set to "yes", MPD will discover audio files by following +# 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" @@ -157,9 +155,9 @@ auto_update_depth "10" #zeroconf_enabled "yes" # # The argument to this setting will be the Zeroconf / Avahi unique name for -# this MPD server on the network. +# this MPD server on the network. %h will be replaced with the hostname. # -#zeroconf_name "Music Player" +#zeroconf_name "Music Player @ %h" # ############################################################################### @@ -167,11 +165,11 @@ auto_update_depth "10" # Permissions ################################################################# # # If this setting is set, MPD will require password authorization. The password -# can setting can be specified multiple times for different password profiles. +# 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. +# This setting specifies the permissions a user has who has not yet logged in. # #default_permissions "read,add,control,admin" # @@ -197,13 +195,43 @@ input { # 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" +} + # ############################################################################### # 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 +# 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. # @@ -227,14 +255,14 @@ audio_output { # device "/dev/dsp" # optional # mixer_type "hardware" # optional # mixer_device "/dev/mixer" # optional -# mixer_control "%AUDIOiFace%" # optional +# mixer_control "PCM" # optional #} # # An example of a shout output (for streaming to Icecast): # #audio_output { # type "shout" -# encoding "ogg" # optional +# encoder "vorbis" # optional # name "My Shout Stream" # host "localhost" # port "8000" @@ -330,13 +358,6 @@ audio_output { # mixer_type "none" # optional #} # -# If MPD has been compiled with libsamplerate support, this setting specifies -# the sample rate converter to use. Possible values can be found in the -# mpd.conf man page or the libsamplerate documentation. By default, this is -# setting is disabled. -# -#samplerate_converter "Fastest Sinc Interpolator" -# ############################################################################### @@ -371,49 +392,23 @@ audio_output { #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 +# 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 "no" +volume_normalization "yes" # ############################################################################### - # Character Encoding ########################################################## # -# If file or directory names do not display correctly for your locale then you +# If file or directory names do not display correctly for your locale then you # may need to modify this setting. # filesystem_charset "UTF-8" # # This setting controls the encoding that ID3v1 tags should be converted from. # -id3v1_encoding "UTF-8" +#id3v1_encoding "UTF-8" # -############################################################################### - - -# SIDPlay decoder ############################################################# -# -# songlength_database: -# Location of your songlengths file, as distributed with the HVSC. -# The sidplay plugin checks this for matching MD5 fingerprints. -# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq -# -# default_songlength: -# This is the default playing time in seconds for songs not in the -# songlength database, or in case you're not using a database. -# A value of 0 means play indefinitely. -# -# filter: -# Turns the SID filter emulation on or off. -# -#decoder { -# plugin "sidplay" -# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt" -# default_songlength "120" -# filter "true" -#} # ############################################################################### - diff --git a/misc/sampleconfigs/wpa_supplicant.conf.buster-default.sample b/misc/sampleconfigs/wpa_supplicant.conf.sample old mode 100755 new mode 100644 similarity index 59% rename from misc/sampleconfigs/wpa_supplicant.conf.buster-default.sample rename to misc/sampleconfigs/wpa_supplicant.conf.sample index 0971968bd..32029bd31 --- a/misc/sampleconfigs/wpa_supplicant.conf.buster-default.sample +++ b/misc/sampleconfigs/wpa_supplicant.conf.sample @@ -1,9 +1,3 @@ ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 country=%WIFIcountryCode% - -network={ - ssid="%WIFIssid%" - psk="%WIFIpass%" - key_mgmt=WPA-PSK -} diff --git a/packages-autohotspot_NetworkManager.txt b/packages-autohotspot_NetworkManager.txt new file mode 100644 index 000000000..bd4dbde9c --- /dev/null +++ b/packages-autohotspot_NetworkManager.txt @@ -0,0 +1,4 @@ +# Define packages for apt-get. These can be installed with +# 'sed 's/#.*//g' packages.txt | xargs sudo apt-get install' + +iw diff --git a/packages-autohotspot.txt b/packages-autohotspot_dhcpcd.txt similarity index 100% rename from packages-autohotspot.txt rename to packages-autohotspot_dhcpcd.txt diff --git a/requirements-spotify.txt b/requirements-spotify.txt index b521b6ac6..83c411544 100644 --- a/requirements-spotify.txt +++ b/requirements-spotify.txt @@ -1,3 +1,3 @@ # Spotify related requirements # You need to install these with `sudo pip install --upgrade --force-reinstall -r requirements-spotify.txt` - Mopidy-Iris==3.69.2 + Mopidy-Iris==3.69.3 diff --git a/requirements.txt b/requirements.txt index e15872843..eba1cdd62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ # related libraries. evdev git+https://github.com/lthiery/SPI-Py.git#egg=spi-py -youtube-dl +git+https://github.com/ytdl-org/youtube-dl@master#egg=youtube-dl pyserial RPi.GPIO diff --git a/scripts/Reader.py b/scripts/Reader.py index 3b583da4b..d3c09f2b6 100755 --- a/scripts/Reader.py +++ b/scripts/Reader.py @@ -29,10 +29,8 @@ def get_devices(): class Reader: - reader = None def __init__(self): - self.reader = self path = os.path.dirname(os.path.realpath(__file__)) self.keys = "X^1234567890XXXXqwertzuiopXXXXasdfghjklXXXXXyxcvbnmXXXXXXXXXXXXXXXXXXXXXXX" if not os.path.isfile(path + '/deviceName.txt'): diff --git a/scripts/Reader.py.Multi b/scripts/Reader.py.Multi index e727d7b5d..5e98b4d0f 100644 --- a/scripts/Reader.py.Multi +++ b/scripts/Reader.py.Multi @@ -29,10 +29,8 @@ def get_devices(): class Reader: - reader = None def __init__(self): - self.reader = self devs = list() path = os.path.dirname(os.path.realpath(__file__)) self.keys = "X^1234567890XXXXqwertzuiopXXXXasdfghjklXXXXXyxcvbnmXXXXXXXXXXXXXXXXXXXXXXX" diff --git a/scripts/Reader.py.experimental b/scripts/Reader.py.experimental index b34479565..e3854f629 100755 --- a/scripts/Reader.py.experimental +++ b/scripts/Reader.py.experimental @@ -53,10 +53,15 @@ class Mfrc522Reader(object): def __init__(self): import pirc522 self.device = pirc522.RFID() + path = os.path.dirname(os.path.realpath(__file__)) + readmode_uid = False + if os.path.isfile(path + '/../settings/Rfidreader_Rc522_Readmode_UID'): + with open(path + '/../settings/Rfidreader_Rc522_Readmode_UID', 'r') as f: + readmode_uid = f.read().rstrip().split(';', 1)[0] == 'ON' + self._read_function = self._readCard_normal if readmode_uid else self._readCard_legacy - def readCard(self): + def _readCard_legacy(self): # Scan for cards - self.device.wait_for_tag() (error, tag_type) = self.device.request() if not error: @@ -70,6 +75,22 @@ class Mfrc522Reader(object): logger.debug("No Device ID found.") return None + def _readCard_normal(self): + # Scan for cards + uid = self.device.read_id(as_number=True) + if not uid: + logger.debug("No Device ID found.") + return None + card_id = str(uid) + logger.info("Card detected.") + logger.info(card_id) + return card_id + + def readCard(self): + # Scan for cards + self.device.wait_for_tag() + return self._read_function() + @staticmethod def cleanup(): GPIO.cleanup() @@ -203,3 +224,6 @@ class Reader(object): self.reader = UsbReader(device) except IndexError: sys.exit('Could not find the device %s.\n Make sure it is connected' % device_name) + + def readCard(self): + return self.reader.readCard() diff --git a/scripts/Reader.py.experimental.Multi b/scripts/Reader.py.experimental.Multi index a4626eb5f..b7c4fcb5f 100644 --- a/scripts/Reader.py.experimental.Multi +++ b/scripts/Reader.py.experimental.Multi @@ -70,11 +70,17 @@ class UsbReader(object): class Mfrc522Reader(object): def __init__(self): + import pirc522 self.device = pirc522.RFID() + path = os.path.dirname(os.path.realpath(__file__)) + readmode_uid = False + if os.path.isfile(path + '/../settings/Rfidreader_Rc522_Readmode_UID'): + with open(path + '/../settings/Rfidreader_Rc522_Readmode_UID', 'r') as f: + readmode_uid = f.read().rstrip().split(';', 1)[0] == 'ON' + self._read_function = self._readCard_normal if readmode_uid else self._readCard_legacy - def readCard(self): + def _readCard_legacy(self): # Scan for cards - self.device.wait_for_tag() (error, tag_type) = self.device.request() if not error: @@ -88,6 +94,22 @@ class Mfrc522Reader(object): logger.debug("No Device ID found.") return None + def _readCard_normal(self): + # Scan for cards + uid = self.device.read_id(as_number=True) + if not uid: + logger.debug("No Device ID found.") + return None + card_id = str(uid) + logger.info("Card detected.") + logger.info(card_id) + return card_id + + def readCard(self): + # Scan for cards + self.device.wait_for_tag() + return self._read_function() + @staticmethod def cleanup(): GPIO.cleanup() @@ -158,7 +180,6 @@ class Pn532Reader: class Reader(object): def __init__(self): - self.reader = self self.devs = list() path = os.path.dirname(os.path.realpath(__file__)) if not os.path.isfile(path + '/deviceName.txt'): diff --git a/scripts/Reader.py.original b/scripts/Reader.py.original index 49f1957bc..80a4fabf3 100755 --- a/scripts/Reader.py.original +++ b/scripts/Reader.py.original @@ -30,11 +30,9 @@ def get_devices(): class Reader: - reader = None def __init__(self): logger.debug('Initialize Reader') - self.reader = self path = os.path.dirname(os.path.realpath(__file__)) self.keys = "X^1234567890XXXXqwertzuiopXXXXasdfghjklXXXXXyxcvbnmXXXXXXXXXXXXXXXXXXXXXXX" deviceNameFile = os.path.join(path, 'deviceName.txt') diff --git a/scripts/Reader.py.pcsc b/scripts/Reader.py.pcsc index e0d2cc374..792b29f01 100644 --- a/scripts/Reader.py.pcsc +++ b/scripts/Reader.py.pcsc @@ -12,10 +12,8 @@ from smartcard.util import * class Reader: - reader = None - def __init__(self): - self.reader = self + pass def readCard(self): diff --git a/scripts/daemon_rfid_reader.py b/scripts/daemon_rfid_reader.py index cc643f26d..b37c96bba 100755 --- a/scripts/daemon_rfid_reader.py +++ b/scripts/daemon_rfid_reader.py @@ -85,14 +85,7 @@ def handler(signum, frame): signal.alarm(1) # reading the card id - # NOTE: it's been reported that KKMOON Reader might need the following line altered. - # Instead of: - # cardid = reader.reader.readCard() - # change the line to: - # cardid = reader.readCard() - # See here for (German ;) details: - # https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/551 - cardid = reader.reader.readCard() + cardid = reader.readCard() # disable the alarm after a successful read signal.alarm(0) diff --git a/scripts/helperscripts/inc.networkHelper.sh b/scripts/helperscripts/inc.networkHelper.sh new file mode 100644 index 000000000..ff0df181e --- /dev/null +++ b/scripts/helperscripts/inc.networkHelper.sh @@ -0,0 +1,123 @@ +_get_service_enablement() { + local service="$1" + local option="${2:+$2 }" # optional, dont't quote in 'systemctl' call! + + if [[ -z "${service}" ]]; then + echo "ERROR: at least one parameter value is missing!" + exit 1 + 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() { + if [[ $(is_service_enabled "dhcpcd.service") == true || "${CI_TEST_DHCPCD}" == true ]]; then + echo true + else + echo false + fi +} + +is_NetworkManager_enabled() { + if [[ $(is_service_enabled "NetworkManager.service") == true || "${CI_TEST_NETWORKMANAGER}" == true ]]; then + echo true + else + echo false + fi +} + +WPA_CONF="/etc/wpa_supplicant/wpa_supplicant.conf" +clear_wireless_networks() { + if [[ $(is_dhcpcd_enabled) == true ]]; then + sudo sed -i -e '/^[[:space:]]*$/d' -e '/^network={/,/^}/d' $WPA_CONF + fi + + if [[ $(is_NetworkManager_enabled) == true ]]; then + nmcli -g UUID,TYPE,ACTIVE connection show | grep "^.*:.*:no$" | grep -F "wireless" | cut -d : -f 1 | \ + while read uuid; do sudo nmcli connection delete "$uuid"; done + fi +} + +_get_passphrase_for_config() { + local ssid="$1" + local pass="$2" + if [[ "${#pass}" -lt 64 ]]; then + pass=$(wpa_passphrase "$ssid" "$pass" | grep -vF '#psk' | grep -F "psk=" | cut -d = -f 2) + fi + echo $pass +} + +add_wireless_network() { + local interface="$1" + local ssid="$2" + local pass="$3" + local prio="$4" + + pass=$(_get_passphrase_for_config "$ssid" "$pass") + + if [[ $(is_dhcpcd_enabled) == true ]]; then + if ! sudo cat "$WPA_CONF" | grep -qF "ssid=\"${ssid}\"" ; then + local wpa_network_with_dummy_psk=$(wpa_passphrase "$ssid" "dummypsk") + if echo "$wpa_network_with_dummy_psk" | grep -qF 'network='; then + local wpa_network=$(echo "$wpa_network_with_dummy_psk" | sed -e '/#psk/d' -e "s/psk=.*$/psk=${pass}/" -e "/^}/i\\\tpriority=${prio}" ) + sudo bash -c "echo '${wpa_network}' >> $WPA_CONF" + fi + fi + fi + + if [[ $(is_NetworkManager_enabled) == true ]]; then + if ! nmcli -g NAME,TYPE connection show | grep -F "wireless" | grep -qwF "$ssid"; then + sudo nmcli connection add type wifi con-name "$ssid" ifname "$interface" autoconnect yes mode infrastructure ssid "$ssid" + sudo nmcli connection modify "$ssid" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$pass" conn.autoconnect-p "$prio" + fi + fi +} + +# gets the configured wireless networks. Returns an array with the format "ssid:pass:prio ssid:pass:prio". +get_wireless_networks() { + networks=() + + if [[ $(is_dhcpcd_enabled) == true ]]; then + local wpa_networks=$(sudo sed '/^network={/,/^}/!d' $WPA_CONF) + local wpa_networks_perline=$(echo "${wpa_networks//$'\n'/\\n}" | sed -e 's/[[:space:]]//g' -e 's/\\nnetwork=/\nnetwork=/g') + for wpa_network in $wpa_networks_perline + do + local wpa_network_multiline="${wpa_network//\\n/$'\n'}" + local ssid=$(echo "$wpa_network_multiline" | grep -F "ssid=" | cut -d = -f 2 | tr -d '"') + local pass=$(echo "$wpa_network_multiline" | grep -F "psk=" | cut -d = -f 2) + local prio=$(echo "$wpa_network_multiline" | grep -F "priority=" | cut -d = -f 2) + + networks+=("$ssid":"$pass":"$prio") + done + fi + + if [[ $(is_NetworkManager_enabled) == true ]]; then + local network_profiles=$(nmcli -g UUID,TYPE connection show | grep -F "wireless" | cut -d : -f 1) + + for network_profile in $network_profiles + do + local result=$(sudo nmcli --show-secrets -t -f 802-11-wireless.ssid,802-11-wireless-security.psk,connection.autoconnect-priority con show $network_profile) + local ssid=$(echo "$result" | grep -F "802-11-wireless.ssid" | cut -d : -f 2) + local pass=$(echo "$result" | grep -F "802-11-wireless-security.psk" | cut -d : -f 2) + local prio=$(echo "$result" | grep -F "connection.autoconnect-priority" | cut -d : -f 2) + + networks+=("$ssid":"$pass":"$prio") + done + fi + + echo "${networks[@]}" +} diff --git a/scripts/helperscripts/inc.systemHelper.sh b/scripts/helperscripts/inc.systemHelper.sh new file mode 100644 index 000000000..0a3168713 --- /dev/null +++ b/scripts/helperscripts/inc.systemHelper.sh @@ -0,0 +1,35 @@ +is_raspbian() { + 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_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/${filename}" + # Bookworm and higher + elif [ "$debian_version_number" -ge 12 ]; then + echo "/boot/firmware/${filename}" + else + echo "unknown" + fi + else + echo "unknown" + fi +} + +get_boot_config_path() { + echo $(_get_boot_file_path "config.txt") +} \ No newline at end of file diff --git a/scripts/helperscripts/setup_autohotspot.sh b/scripts/helperscripts/setup_autohotspot.sh index 5f9067894..2ae9cd361 100644 --- a/scripts/helperscripts/setup_autohotspot.sh +++ b/scripts/helperscripts/setup_autohotspot.sh @@ -26,29 +26,67 @@ call_with_args_from_file () { sed 's/#.*//g' ${package_file} | xargs "$@" } +_get_last_ip_segment() { + local ip="$1" + echo $ip | cut -d'.' -f1-3 +} + +# 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 +} + apt_get="sudo apt-get -qq --yes" systemd_dir="/etc/systemd/system" +autohotspot_script="/usr/bin/autohotspot" +autohotspot_service_daemon="autohotspot-daemon.service" +autohotspot_service_daemon_path="${systemd_dir}/${autohotspot_service_daemon}" autohotspot_service="autohotspot.service" autohotspot_service_path="${systemd_dir}/${autohotspot_service}" -autohotspot_script="/usr/bin/autohotspot" +autohotspot_timer="autohotspot.timer" +autohotspot_timer_path="${systemd_dir}/${autohotspot_timer}" +interfaces_conf_file="/etc/network/interfaces" dnsmasq_conf=/etc/dnsmasq.conf hostapd_conf=/etc/hostapd/hostapd.conf hostapd_deamon=/etc/default/hostapd dhcpcd_conf=/etc/dhcpcd.conf dhcpcd_conf_nohook_wpa_supplicant="nohook wpa_supplicant" +networkManager_connections_path="/etc/NetworkManager/system-connections" -if [ "${AUTOHOTSPOTconfig}" == "YES" ]; then +wifi_interface=wlan0 +ip_without_last_segment=$(_get_last_ip_segment $AUTOHOTSPOTip) +autohotspot_profile="Phoniebox_Hotspot" - # adapted from https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection +source "${JUKEBOX_HOME_DIR}"/scripts/helperscripts/inc.networkHelper.sh - # power management of wifi: switch off to avoid disconnecting - sudo iwconfig wlan0 power off +_install_autohotspot_dhcpcd() { + # adapted from https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection # required packages - call_with_args_from_file "${JUKEBOX_HOME_DIR}"/packages-autohotspot.txt ${apt_get} install + call_with_args_from_file "${JUKEBOX_HOME_DIR}"/packages-autohotspot_dhcpcd.txt ${apt_get} install sudo systemctl unmask hostapd sudo systemctl disable hostapd sudo systemctl stop hostapd @@ -56,31 +94,25 @@ if [ "${AUTOHOTSPOTconfig}" == "YES" ]; then sudo systemctl disable dnsmasq sudo systemctl stop dnsmasq + # configure interface conf + config_file_backup "${interfaces_conf_file}" + sudo rm "${interfaces_conf_file}" + sudo touch "${interfaces_conf_file}" + # configure DNS - # create flag file or copy present conf to orig file - # to correctly handling future deactivation of autohotspot - if [ ! -f "${dnsmasq_conf}" ]; then - sudo touch "${dnsmasq_conf}.remove" - elif [ ! -f "${dnsmasq_conf}.orig" ] && [ ! -f "${dnsmasq_conf}.remove" ]; then - sudo cp "${dnsmasq_conf}" "${dnsmasq_conf}.orig" - fi + config_file_backup "${dnsmasq_conf}" - ip_without_last_segment=$(echo $AUTOHOTSPOTip | cut -d'.' -f1-3) - sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/dnsmasq.conf.stretch-default2-Hotspot.sample "${dnsmasq_conf}" + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/dhcpcd/dnsmasq.conf "${dnsmasq_conf}" + sudo sed -i "s|%WIFI_INTERFACE%|${wifi_interface}|g" "${dnsmasq_conf}" sudo sed -i "s|%IP_WITHOUT_LAST_SEGMENT%|${ip_without_last_segment}|g" "${dnsmasq_conf}" sudo chown root:root "${dnsmasq_conf}" sudo chmod 644 "${dnsmasq_conf}" # configure hostapd conf - # create flag file or copy present conf to orig file - # to correctly handling future deactivation of autohotspot - if [ ! -f "${hostapd_conf}" ]; then - sudo touch "${hostapd_conf}.remove" - elif [ ! -f "${hostapd_conf}.orig" ] && [ ! -f "${hostapd_conf}.remove" ]; then - sudo cp "${hostapd_conf}" "${hostapd_conf}.orig" - fi + config_file_backup "${hostapd_conf}" - sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/hostapd.conf.stretch-default2-Hotspot.sample "${hostapd_conf}" + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/dhcpcd/hostapd.conf "${hostapd_conf}" + sudo sed -i "s|%WIFI_INTERFACE%|${wifi_interface}|g" "${hostapd_conf}" sudo sed -i "s|%AUTOHOTSPOTssid%|${AUTOHOTSPOTssid}|g" "${hostapd_conf}" sudo sed -i "s|%AUTOHOTSPOTpass%|${AUTOHOTSPOTpass}|g" "${hostapd_conf}" sudo sed -i "s|%AUTOHOTSPOTcountryCode%|${AUTOHOTSPOTcountryCode}|g" "${hostapd_conf}" @@ -88,29 +120,19 @@ if [ "${AUTOHOTSPOTconfig}" == "YES" ]; then sudo chmod 644 "${hostapd_conf}" # configure hostapd daemon - # create flag file or copy present conf to orig file - # to correctly handling future deactivation of autohotspot - if [ ! -f "${hostapd_deamon}" ]; then - sudo touch "${hostapd_deamon}.remove" - elif [ ! -f "${hostapd_deamon}.orig" ] && [ ! -f "${hostapd_deamon}.remove" ]; then - sudo cp "${hostapd_deamon}" "${hostapd_deamon}.orig" - fi + config_file_backup "${hostapd_deamon}" - sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/hostapd.stretch-default2-Hotspot.sample "${hostapd_deamon}" + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/dhcpcd/hostapd "${hostapd_deamon}" sudo sed -i "s|%HOSTAPD_CONF%|${hostapd_conf}|g" "${hostapd_deamon}" sudo chown root:root "${hostapd_deamon}" sudo chmod 644 "${hostapd_deamon}" # configure dhcpcd conf - # create flag file or copy present conf to orig file - # to correctly handling future deactivation of autohotspot + config_file_backup "${dhcpcd_conf}" if [ ! -f "${dhcpcd_conf}" ]; then - sudo touch "${dhcpcd_conf}.remove" sudo touch "${dhcpcd_conf}" sudo chown root:netdev "${dhcpcd_conf}" sudo chmod 664 "${dhcpcd_conf}" - elif [ ! -f "${dhcpcd_conf}.orig" ] && [ ! -f "${dhcpcd_conf}.remove" ]; then - sudo cp "${dhcpcd_conf}" "${dhcpcd_conf}.orig" fi if [[ ! $(grep -w "${dhcpcd_conf_nohook_wpa_supplicant}" ${dhcpcd_conf}) ]]; then @@ -118,64 +140,140 @@ if [ "${AUTOHOTSPOTconfig}" == "YES" ]; then fi # create service to trigger hotspot - sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot.sh.stretch-default2-Hotspot.sample "${autohotspot_script}" + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot "${autohotspot_script}" + sudo sed -i "s|%WIFI_INTERFACE%|${wifi_interface}|g" "${autohotspot_script}" sudo sed -i "s|%AUTOHOTSPOT_IP%|${AUTOHOTSPOTip}|g" "${autohotspot_script}" + sudo sed -i "s|%AUTOHOTSPOT_SERVICE_DAEMON%|${autohotspot_service_daemon}|g" "${autohotspot_script}" sudo chmod +x "${autohotspot_script}" - sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot.service.stretch-default2-Hotspot.sample "${autohotspot_service_path}" + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot-daemon.service "${autohotspot_service_daemon_path}" + sudo sed -i "s|%WIFI_INTERFACE%|${wifi_interface}|g" "${autohotspot_service_daemon_path}" + sudo chown root:root "${autohotspot_service_daemon_path}" + sudo chmod 644 "${autohotspot_service_daemon_path}" + + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot.service "${autohotspot_service_path}" sudo sed -i "s|%AUTOHOTSPOT_SCRIPT%|${autohotspot_script}|g" "${autohotspot_service_path}" sudo chown root:root "${autohotspot_service_path}" sudo chmod 644 "${autohotspot_service_path}" - sudo systemctl enable "${autohotspot_service}" + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/dhcpcd/autohotspot.timer "${autohotspot_timer_path}" + sudo sed -i "s|%AUTOHOTSPOT_SERVICE%|${autohotspot_service}|g" "${autohotspot_timer_path}" + sudo chown root:root "${autohotspot_timer_path}" + sudo chmod 644 "${autohotspot_timer_path}" - # create crontab entry - crontab_user=$(crontab -l 2>/dev/null) - if [[ -z "${crontab_user}" || ! $(echo "${crontab_user}" | grep -w "${autohotspot_script}") ]]; then - (echo "${crontab_user}"; echo "*/5 * * * * sudo ${autohotspot_script} >/dev/null 2>&1") | crontab - - fi + sudo systemctl enable "${autohotspot_service_daemon}" + sudo systemctl disable "${autohotspot_service}" + sudo systemctl enable "${autohotspot_timer}" +} -else +_uninstall_autohotspot_dhcpcd() { # clear autohotspot configurations made from past installation + # remove crontab entry and script from old version installations + crontab_user=$(crontab -l 2>/dev/null) + if [[ ! -z "${crontab_user}" && $(echo "${crontab_user}" | grep -w "${autohotspot_script}") ]]; then + echo "${crontab_user}" | sed "s|^.*\s${autohotspot_script}\s.*$||g" | crontab - + fi + # stop services 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_timer_path}" sudo rm "${autohotspot_service_path}" + sudo rm "${autohotspot_service_daemon_path}" fi - # remove crontab entry and script - crontab_user=$(crontab -l 2>/dev/null) - if [[ ! -z "${crontab_user}" && $(echo "${crontab_user}" | grep -w "${autohotspot_script}") ]]; then - echo "${crontab_user}" | sed "s|^.*\s${autohotspot_script}\s.*$||g" | crontab - + if [ -f "${autohotspot_script}" ]; then + sudo rm "${autohotspot_script}" + fi + + # remove config files + config_file_revert "${dnsmasq_conf}" + config_file_revert "${hostapd_conf}" + config_file_revert "${hostapd_deamon}" + config_file_revert "${dhcpcd_conf}" + config_file_revert "${interfaces_conf_file}" +} + +_install_autohotspot_NetworkManager() { + # required packages + call_with_args_from_file "${JUKEBOX_HOME_DIR}"/packages-autohotspot_NetworkManager.txt ${apt_get} install + + # configure interface conf + config_file_backup "${interfaces_conf_file}" + sudo rm "${interfaces_conf_file}" + sudo touch "${interfaces_conf_file}" + + # create service to trigger hotspot + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/NetworkManager/autohotspot "${autohotspot_script}" + sudo sed -i "s|%WIFI_INTERFACE%|${wifi_interface}|g" "${autohotspot_script}" + sudo sed -i "s|%AUTOHOTSPOT_PROFILE%|${autohotspot_profile}|g" "${autohotspot_script}" + sudo sed -i "s|%AUTOHOTSPOT_SSID%|${AUTOHOTSPOTssid}|g" "${autohotspot_script}" + sudo sed -i "s|%AUTOHOTSPOT_PASSWORD%|${AUTOHOTSPOTpass}|g" "${autohotspot_script}" + sudo sed -i "s|%AUTOHOTSPOT_IP%|${AUTOHOTSPOTip}|g" "${autohotspot_script}" + sudo sed -i "s|%IP_WITHOUT_LAST_SEGMENT%|${ip_without_last_segment}|g" "${autohotspot_script}" + sudo sed -i "s|%AUTOHOTSPOT_TIMER_NAME%|${autohotspot_timer}|g" "${autohotspot_script}" + sudo chmod +x "${autohotspot_script}" + + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/NetworkManager/autohotspot.service "${autohotspot_service_path}" + sudo sed -i "s|%AUTOHOTSPOT_SCRIPT%|${autohotspot_script}|g" "${autohotspot_service_path}" + sudo chown root:root "${autohotspot_service_path}" + sudo chmod 644 "${autohotspot_service_path}" + + sudo cp "${JUKEBOX_HOME_DIR}"/misc/sampleconfigs/autohotspot/NetworkManager/autohotspot.timer "${autohotspot_timer_path}" + sudo sed -i "s|%AUTOHOTSPOT_SERVICE%|${autohotspot_service}|g" "${autohotspot_timer_path}" + sudo chown root:root "${autohotspot_timer_path}" + sudo chmod 644 "${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_script}" ]; then sudo rm "${autohotspot_script}" fi + sudo rm -f "${networkManager_connections_path}/${autohotspot_profile}*" + # remove config files - if [ -f "${dnsmasq_conf}.remove" ]; then - sudo rm "${dnsmasq_conf}.remove" "${dnsmasq_conf}" - elif [ -f "${dnsmasq_conf}.orig" ]; then - sudo mv "${dnsmasq_conf}.orig" "${dnsmasq_conf}" + config_file_revert "${interfaces_conf_file}" +} + + +if [ "${AUTOHOTSPOTconfig}" == "YES" ]; then + if [[ $(is_dhcpcd_enabled) == true ]]; then + _install_autohotspot_dhcpcd fi - if [ -f "${hostapd_conf}.remove" ]; then - sudo rm "${hostapd_conf}.remove" "${hostapd_conf}" - elif [ -f "${hostapd_conf}.orig" ]; then - sudo mv "${hostapd_conf}.orig" "${hostapd_conf}" + if [[ $(is_NetworkManager_enabled) == true ]]; then + _install_autohotspot_NetworkManager fi - if [ -f "${hostapd_deamon}.remove" ]; then - sudo rm "${hostapd_deamon}.remove" "${hostapd_deamon}" - elif [ -f "${hostapd_deamon}.orig" ]; then - sudo mv "${hostapd_deamon}.orig" "${hostapd_deamon}" +else + if [[ $(is_dhcpcd_enabled) == true ]]; then + _uninstall_autohotspot_dhcpcd fi - if [ -f "${dhcpcd_conf}.remove" ]; then - sudo rm "${dhcpcd_conf}.remove" "${dhcpcd_conf}" - elif [ -f "${dhcpcd_conf}.orig" ]; then - sudo mv "${dhcpcd_conf}.orig" "${dhcpcd_conf}" + if [[ $(is_NetworkManager_enabled) == true ]]; then + _uninstall_autohotspot_NetworkManager fi fi diff --git a/scripts/inc.writeGlobalConfig.sh b/scripts/inc.writeGlobalConfig.sh index 856c54fb4..84aa559ef 100755 --- a/scripts/inc.writeGlobalConfig.sh +++ b/scripts/inc.writeGlobalConfig.sh @@ -109,6 +109,16 @@ fi # 2. then|or read value from file SECONDSWIPEPAUSECONTROLS=`cat $PATHDATA/../settings/Second_Swipe_Pause_Controls` +############################################## +# RFID reader rc522 readmode UID +# 1. create a default if file does not exist +if [ ! -f $PATHDATA/../settings/Rfidreader_Rc522_Readmode_UID ]; then + echo "OFF" > $PATHDATA/../settings/Rfidreader_Rc522_Readmode_UID + chmod 777 $PATHDATA/../settings/Rfidreader_Rc522_Readmode_UID +fi +# 2. then|or read value from file +RFIDREADERRC522READMODEUID=`cat $PATHDATA/../settings/Rfidreader_Rc522_Readmode_UID` + ############################################## # Audio_iFace_Name # 1. create a default if file does not exist @@ -332,6 +342,7 @@ CMDSEEKBACK=`grep 'CMDSEEKBACK' $PATHDATA/../settings/rfid_trigger_play.conf|tai # SECONDSWIPE # SECONDSWIPEPAUSE # SECONDSWIPEPAUSECONTROLS +# RFIDREADERRC522READMODEUID # AUDIOIFACENAME # AUDIOIFACEACTIVE # VOLUMEMANAGER @@ -369,6 +380,7 @@ echo "SWIPEORPLACE=\"${SWIPEORPLACE}\"" >> "${PATHDATA}/../settings/global.conf" echo "SECONDSWIPE=\"${SECONDSWIPE}\"" >> "${PATHDATA}/../settings/global.conf" echo "SECONDSWIPEPAUSE=\"${SECONDSWIPEPAUSE}\"" >> "${PATHDATA}/../settings/global.conf" echo "SECONDSWIPEPAUSECONTROLS=\"${SECONDSWIPEPAUSECONTROLS}\"" >> "${PATHDATA}/../settings/global.conf" +echo "RFIDREADERRC522READMODEUID=\"${RFIDREADERRC522READMODEUID}\"" >> "${PATHDATA}/../settings/global.conf" echo "AUDIOIFACENAME=\"${AUDIOIFACENAME}\"" >> "${PATHDATA}/../settings/global.conf" echo "AUDIOIFACEACTIVE=\"${AUDIOIFACEACTIVE}\"" >> "${PATHDATA}/../settings/global.conf" echo "VOLUMEMANAGER=\"${VOLUMEMANAGER}\"" >> "${PATHDATA}/../settings/global.conf" diff --git a/scripts/installscripts/install-jukebox.sh b/scripts/installscripts/install-jukebox.sh index 54da685e3..751a2ef06 100644 --- a/scripts/installscripts/install-jukebox.sh +++ b/scripts/installscripts/install-jukebox.sh @@ -40,6 +40,8 @@ JUKEBOX_BACKUP_DIR="${HOME_DIR}/BACKUP" OS_CODENAME="$( . /etc/os-release; printf '%s\n' "$VERSION_CODENAME"; )" printf "Used Raspberry Pi OS: ${OS_CODENAME}\n" +WIFI_INTERFACE="wlan0" + INTERACTIVE=true usage() { @@ -181,18 +183,10 @@ config_wifi() { # to assign to your Phoniebox. # (Note: can be done manually later, if you are unsure.) " -read -rp "Do you want to configure your WiFi? [Y/n] " response +read -rp "Do you want to configure your WiFi? [y/N] " response echo "" case "$response" in - [nN][oO]|[nN]) - WIFIconfig=NO - echo "You want to configure WiFi later." - # append variables to config file - echo "WIFIconfig=$WIFIconfig" >> "${HOME_DIR}/PhonieboxInstall.conf" - # make a fallback for WiFi Country Code, because we need that even without WiFi config - echo "WIFIcountryCode=DE" >> "${HOME_DIR}/PhonieboxInstall.conf" - ;; - *) + [yY][eE][sS]|[yY]) WIFIconfig=YES #Ask for SSID read -rp "* Type SSID name: " WIFIssid @@ -231,6 +225,14 @@ case "$response" in ;; esac ;; + *) + WIFIconfig=NO + echo "You want to configure WiFi later." + # append variables to config file + echo "WIFIconfig=$WIFIconfig" >> "${HOME_DIR}/PhonieboxInstall.conf" + # make a fallback for WiFi Country Code, because we need that even without WiFi config + echo "WIFIcountryCode=DE" >> "${HOME_DIR}/PhonieboxInstall.conf" + ;; esac read -rp "Hit ENTER to proceed to the next step." INPUT } @@ -535,23 +537,30 @@ config_audio_interface() { clear + local amixer_scontrols=$(sudo amixer scontrols) + local audio_interfaces=$(echo "${amixer_scontrols}" | sed "s|.*'\(.*\)'.*|\1|g") + local first_audio_interface=$(echo "${audio_interfaces}" | head -1) + local default_audio_interface="${first_audio_interface:-PCM}" + echo "##################################################### # # CONFIGURE AUDIO INTERFACE (iFace) # -# The default RPi audio interface is 'Headphone'. -# But this does not work for every setup. Here a list of -# available iFace names: +# The default RPi audio interface is '${default_audio_interface}'. +# But this does not work for every setup. +# Here a list of available iFace names: + +${audio_interfaces} " - amixer scontrols + echo " " - read -rp "Use Headphone as iFace? [Y/n] " response + read -rp "Use '${default_audio_interface}' as iFace? [Y/n] " response case "$response" in [nN][oO]|[nN]) read -rp "Type the iFace name you want to use:" AUDIOiFace ;; *) - AUDIOiFace="Headphone" + AUDIOiFace="${default_audio_interface}" ;; esac # append variables to config file @@ -583,13 +592,9 @@ config_spotify() { # * client_secret " - read -rp "Do you want to enable Spotify? [Y/n] " response + read -rp "Do you want to enable Spotify? [y/N] " response case "$response" in - [nN][oO]|[nN]) - SPOTinstall=NO - echo "You don't want spotify support." - ;; - *) + [yY][eE][sS]|[yY]) SPOTinstall=YES clear echo "##################################################### @@ -614,6 +619,10 @@ config_spotify() { read -rp "Type your client_id: " SPOTIclientid read -rp "Type your client_secret: " SPOTIclientsecret ;; + *) + SPOTinstall=NO + echo "You don't want spotify support." + ;; esac # append variables to config file { @@ -626,36 +635,6 @@ config_spotify() { read -rp "Hit ENTER to proceed to the next step." INPUT } -config_mpd() { - ##################################################### - # Configure MPD - - clear - - echo "##################################################### -# -# CONFIGURE MPD -# -# MPD (Music Player Daemon) runs the audio output and must -# be configured. Do it now, if you are unsure. -# (Note: can be done manually later.) -" - read -rp "Do you want to configure MPD? [Y/n] " response - case "$response" in - [nN][oO]|[nN]) - MPDconfig=NO - echo "You want to configure MPD later." - ;; - *) - MPDconfig=YES - echo "MPD will be set up with default values." - ;; - esac - # append variables to config file - echo "MPDconfig=\"$MPDconfig\"" >> "${HOME_DIR}/PhonieboxInstall.conf" - read -rp "Hit ENTER to proceed to the next step." INPUT -} - config_audio_folder() { local jukebox_dir="$1" @@ -779,7 +758,6 @@ check_config_file() { check_variable "SPOTIclientsecret" fi fi - check_variable "MPDconfig" check_variable "DIRaudioFolders" check_variable "GPIOconfig" @@ -917,7 +895,7 @@ install_main() { . "${HOME_DIR}/PhonieboxInstall.conf" # power management of wifi: switch off to avoid disconnecting - sudo iwconfig wlan0 power off + sudo iwconfig "$WIFI_INTERFACE" power off # in the docker test env fiddling with resolv.conf causes issues, see https://stackoverflow.com/a/60576223 if [ "$DOCKER_RUNNING" != "true" ]; then @@ -1051,6 +1029,7 @@ install_main() { sudo rm "${systemd_dir}"/phoniebox-rotary-encoder.service sudo rm "${systemd_dir}"/phoniebox-gpio-buttons.service echo "### Done with erasing old daemons. Stop ignoring errors!" + # 2. install new ones - this is version > 1.1.8-beta RFID_READER_SERVICE="${systemd_dir}/phoniebox-rfid-reader.service" sudo cp "${jukebox_dir}"/misc/sampleconfigs/phoniebox-rfid-reader.service.stretch-default.sample "${RFID_READER_SERVICE}" @@ -1088,33 +1067,31 @@ install_main() { cp "${jukebox_dir}"/misc/sampleconfigs/startupsound.mp3.sample "${jukebox_dir}"/shared/startupsound.mp3 cp "${jukebox_dir}"/misc/sampleconfigs/shutdownsound.mp3.sample "${jukebox_dir}"/shared/shutdownsound.mp3 - if [ "${MPDconfig}" == "YES" ]; then - local mpd_conf="/etc/mpd.conf" - echo "Configuring MPD..." - # MPD configuration - # -rw-r----- 1 mpd audio 14043 Jul 17 20:16 /etc/mpd.conf - sudo cp "${jukebox_dir}"/misc/sampleconfigs/mpd.conf.buster-default.sample ${mpd_conf} - # Change vars to match install config - sudo sed -i 's/%AUDIOiFace%/'"$AUDIOiFace"'/' "${mpd_conf}" - # for $DIRaudioFolders using | as alternate regex delimiter because of the folder path slash - sudo sed -i 's|%DIRaudioFolders%|'"$DIRaudioFolders"'|' "${mpd_conf}" - # Replace homedir; double quotes for variable expansion - sudo sed -i "s%/home/pi%${HOME_DIR}%g" "${mpd_conf}" - sudo chown mpd:audio "${mpd_conf}" - sudo chmod 640 "${mpd_conf}" - - # start mpd - echo "Starting mpd service..." - sudo service mpd restart - sudo systemctl enable mpd - fi + echo "Configuring MPD..." + local mpd_conf="/etc/mpd.conf" + # MPD configuration + # -rw-r----- 1 mpd audio 14043 Jul 17 20:16 /etc/mpd.conf + sudo cp "${jukebox_dir}"/misc/sampleconfigs/mpd.conf.sample ${mpd_conf} + # Change vars to match install config + sudo sed -i 's/%AUDIOiFace%/'"$AUDIOiFace"'/' "${mpd_conf}" + # for $DIRaudioFolders using | as alternate regex delimiter because of the folder path slash + sudo sed -i 's|%DIRaudioFolders%|'"$DIRaudioFolders"'|' "${mpd_conf}" + # Replace homedir; double quotes for variable expansion + sudo sed -i "s%/home/pi%${HOME_DIR}%g" "${mpd_conf}" + sudo chown mpd:audio "${mpd_conf}" + sudo chmod 640 "${mpd_conf}" + + # start mpd + echo "Starting mpd service..." + sudo service mpd restart + sudo systemctl enable mpd # Spotify config if [ "${SPOTinstall}" == "YES" ]; then + echo "Configuring Spotify support..." local etc_mopidy_conf="/etc/mopidy/mopidy.conf" local mopidy_conf="${HOME_DIR}/.config/mopidy/mopidy.conf" - echo "Configuring Spotify support..." sudo systemctl disable mpd sudo service mpd stop sudo systemctl enable mopidy @@ -1170,6 +1147,7 @@ install_main() { wifi_settings() { local jukebox_dir="$1" + local wifiExtDNS="8.8.8.8" ############################### # WiFi settings (SSID password) @@ -1181,39 +1159,46 @@ wifi_settings() { # $WIFIip # $WIFIipRouter if [ "${WIFIconfig}" == "YES" ]; then + echo "Setting up wifi..." + + if [[ $(is_dhcpcd_enabled) == true ]]; then + echo "... for dhcpcd" + + local wpa_supplicant_conf="/etc/wpa_supplicant/wpa_supplicant.conf" + # -rw-rw-r-- 1 root netdev 137 Jul 16 08:53 /etc/wpa_supplicant/wpa_supplicant.conf + sudo cp "${jukebox_dir}"/misc/sampleconfigs/wpa_supplicant.conf.sample "${wpa_supplicant_conf}" + sudo sed -i 's/%WIFIcountryCode%/'"$WIFIcountryCode"'/' "${wpa_supplicant_conf}" + sudo chown root:netdev "${wpa_supplicant_conf}" + sudo chmod 664 "${wpa_supplicant_conf}" + + # add network with high priority + add_wireless_network "$WIFI_INTERFACE" "$WIFIssid" "$WIFIpass" 99 + + # DHCP configuration settings + local dhcpcd_conf="/etc/dhcpcd.conf" + #-rw-rw-r-- 1 root netdev 0 Apr 17 11:25 /etc/dhcpcd.conf + sudo cp "${jukebox_dir}"/misc/sampleconfigs/dhcpcd.conf.buster-default-noHotspot.sample "${dhcpcd_conf}" + # Change IP for router and Phoniebox + sudo sed -i 's/%WIFIinterface%/'"$WIFI_INTERFACE"'/' "${dhcpcd_conf}" + sudo sed -i 's/%WIFIip%/'"$WIFIip"'/' "${dhcpcd_conf}" + sudo sed -i 's/%WIFIipRouter%/'"$WIFIipRouter"'/' "${dhcpcd_conf}" + sudo sed -i 's/%WIFIipExtDNS%/'"$wifiExtDNS"'/' "${dhcpcd_conf}" + sudo sed -i 's/%WIFIcountryCode%/'"$WIFIcountryCode"'/' "${dhcpcd_conf}" + # Change user:group and access mod + sudo chown root:netdev "${dhcpcd_conf}" + sudo chmod 664 "${dhcpcd_conf}" + fi - # DHCP configuration settings - local dhcpcd_conf="/etc/dhcpcd.conf" - echo "Setting ${dhcpcd_conf}..." - #-rw-rw-r-- 1 root netdev 0 Apr 17 11:25 /etc/dhcpcd.conf - sudo cp "${jukebox_dir}"/misc/sampleconfigs/dhcpcd.conf.buster-default-noHotspot.sample "${dhcpcd_conf}" - # Change IP for router and Phoniebox - sudo sed -i 's/%WIFIip%/'"$WIFIip"'/' "${dhcpcd_conf}" - sudo sed -i 's/%WIFIipRouter%/'"$WIFIipRouter"'/' "${dhcpcd_conf}" - sudo sed -i 's/%WIFIcountryCode%/'"$WIFIcountryCode"'/' "${dhcpcd_conf}" - # Change user:group and access mod - sudo chown root:netdev "${dhcpcd_conf}" - sudo chmod 664 "${dhcpcd_conf}" - - # WiFi SSID & Password - local wpa_supplicant_conf="/etc/wpa_supplicant/wpa_supplicant.conf" - echo "Setting ${wpa_supplicant_conf}..." - # -rw-rw-r-- 1 root netdev 137 Jul 16 08:53 /etc/wpa_supplicant/wpa_supplicant.conf - sudo cp "${jukebox_dir}"/misc/sampleconfigs/wpa_supplicant.conf.buster-default.sample "${wpa_supplicant_conf}" - sudo sed -i 's/%WIFIssid%/'"$WIFIssid"'/' "${wpa_supplicant_conf}" - sudo sed -i 's/%WIFIpass%/'"$WIFIpass"'/' "${wpa_supplicant_conf}" - sudo sed -i 's/%WIFIcountryCode%/'"$WIFIcountryCode"'/' "${wpa_supplicant_conf}" - sudo chown root:netdev "${wpa_supplicant_conf}" - sudo chmod 664 "${wpa_supplicant_conf}" - fi - - # start DHCP - echo "Starting dhcpcd service..." - sudo service dhcpcd start - sudo systemctl enable dhcpcd + if [[ $(is_NetworkManager_enabled) == true ]]; then + echo "... for NetworkManager" + # add network with high priority + add_wireless_network "$WIFI_INTERFACE" "$WIFIssid" "$WIFIpass" 99 -# / WiFi settings (SSID password) -############################### + sudo nmcli connection modify "$WIFIssid" ipv4.method manual ipv4.address "$WIFIip"/24 ipv4.gateway "$WIFIipRouter" ipv4.dns "$WIFIipRouter $wifiExtDNS" + fi + fi + # / WiFi settings (SSID password) + ############################### } existing_assets() { @@ -1355,6 +1340,7 @@ autohotspot() { if [ "${AUTOHOTSPOTconfig}" == "YES" ]; then local setup_script="${jukebox_dir}/scripts/helperscripts/setup_autohotspot.sh" sudo chmod +x "${setup_script}" + "${setup_script}" "${jukebox_dir}" "NO" # Uninstall present old versions first "${setup_script}" "${jukebox_dir}" "${AUTOHOTSPOTconfig}" "${AUTOHOTSPOTssid}" "${AUTOHOTSPOTcountryCode}" "${AUTOHOTSPOTpass}" "${AUTOHOTSPOTip}" fi } @@ -1452,7 +1438,6 @@ main() { config_autohotspot config_audio_interface config_spotify - config_mpd config_audio_folder "${JUKEBOX_HOME_DIR}" config_gpio else @@ -1460,6 +1445,9 @@ main() { check_config_file fi install_main "${JUKEBOX_HOME_DIR}" + + source "${JUKEBOX_HOME_DIR}"/scripts/helperscripts/inc.networkHelper.sh + wifi_settings "${JUKEBOX_HOME_DIR}" autohotspot "${JUKEBOX_HOME_DIR}" existing_assets "${JUKEBOX_HOME_DIR}" "${JUKEBOX_BACKUP_DIR}" diff --git a/scripts/installscripts/tests/run_installation_autohotspot_NetworkManager.sh b/scripts/installscripts/tests/run_installation_autohotspot_NetworkManager.sh new file mode 100644 index 000000000..0979e7c72 --- /dev/null +++ b/scripts/installscripts/tests/run_installation_autohotspot_NetworkManager.sh @@ -0,0 +1,51 @@ +#!/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 + +# Print current path +echo $PWD + +# Preparations +# No interactive frontend +export DEBIAN_FRONTEND=noninteractive +echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections + +# Run installation (in interactive mode) +# y confirm interactive mode +# n configure wifi (extra ENTER) +# y configure autohotspot +# y use autohotspot default config (extra ENTER) +# y use default audio iface (extra ENTER) +# n spotify (extra ENTER) +# y audio default location (extra ENTER) +# y config gpio (extra ENTER) +# y start installation +# n RFID registration +# n reboot +export CI_TEST_DHCPCD="false" +export CI_TEST_NETWORKMANAGER="true" +./../install-jukebox.sh <<< "y +n + +y +y + +y + +n + +y + +y + +y +n +n +" +INSTALLATION_EXITCODE=$? + +# Test installation +./test_installation.sh $INSTALLATION_EXITCODE diff --git a/scripts/installscripts/tests/run_installation_autohotspot_dhcpcd.sh b/scripts/installscripts/tests/run_installation_autohotspot_dhcpcd.sh new file mode 100644 index 000000000..7091399bb --- /dev/null +++ b/scripts/installscripts/tests/run_installation_autohotspot_dhcpcd.sh @@ -0,0 +1,51 @@ +#!/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 + +# Print current path +echo $PWD + +# Preparations +# No interactive frontend +export DEBIAN_FRONTEND=noninteractive +echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections + +# Run installation (in interactive mode) +# y confirm interactive mode +# n configure wifi (extra ENTER) +# y configure autohotspot +# y use autohotspot default config (extra ENTER) +# y use default audio iface (extra ENTER) +# n spotify (extra ENTER) +# y audio default location (extra ENTER) +# y config gpio (extra ENTER) +# y start installation +# n RFID registration +# n reboot +export CI_TEST_DHCPCD="true" +export CI_TEST_NETWORKMANAGER="false" +./../install-jukebox.sh <<< "y +n + +y +y + +y + +n + +y + +y + +y +n +n +" +INSTALLATION_EXITCODE=$? + +# Test installation +./test_installation.sh $INSTALLATION_EXITCODE diff --git a/scripts/installscripts/tests/run_installation_tests.sh b/scripts/installscripts/tests/run_installation_classic.sh similarity index 58% rename from scripts/installscripts/tests/run_installation_tests.sh rename to scripts/installscripts/tests/run_installation_classic.sh index 2f75d063a..434efc360 100644 --- a/scripts/installscripts/tests/run_installation_tests.sh +++ b/scripts/installscripts/tests/run_installation_classic.sh @@ -15,18 +15,34 @@ echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selecti # Run installation (in interactive mode) # y confirm interactive -# n dont configure wifi -# y configure autohotspot -# y use autohotspot default config -# y Headphone as iface -# n no spotify -# y configure mpd -# y audio default location -# y config gpio -# n no RFID registration -# n No reboot - -./../install-jukebox.sh <<< $'y\nn\n\ny\ny\n\ny\n\nn\n\ny\n\ny\n\ny\n\ny\nn\nn\n' +# n configure wifi (extra ENTER) +# n configure autohotspot (extra ENTER) +# - use autohotspot default config +# y use default audio iface (extra ENTER) +# n spotify (extra ENTER) +# y audio default location (extra ENTER) +# y config gpio (extra ENTER) +# y start installation +# n RFID registration +# n reboot + +./../install-jukebox.sh <<< "y +n + +n + +y + +n + +y + +y + +y +n +n +" INSTALLATION_EXITCODE=$? # Test installation diff --git a/scripts/installscripts/tests/run_installation_tests2.sh b/scripts/installscripts/tests/run_installation_rfid.sh similarity index 60% rename from scripts/installscripts/tests/run_installation_tests2.sh rename to scripts/installscripts/tests/run_installation_rfid.sh index 17b51f893..d9f150ad8 100644 --- a/scripts/installscripts/tests/run_installation_tests2.sh +++ b/scripts/installscripts/tests/run_installation_rfid.sh @@ -15,19 +15,39 @@ echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selecti # Run installation (in interactive mode) # y confirm interactive -# n dont configure wifi -# n dont configure autohotspot -# y PCM as iface -# n no spotify -# y configure mpd -# y audio default location -# y use gpio +# n configure wifi (extra ENTER) +# n configure autohotspot (extra ENTER) +# y use default audio iface (extra ENTER) +# n spotify (extra ENTER) +# y audio default location (extra ENTER) +# y use gpio (extra ENTER) +# y start installation # y RFID registration # 2 use RC522 reader -# yes, reader is connected -# n No reboot +# y, reader is connected +# y, use legacy readermode +# n reboot -./../install-jukebox.sh <<< $'y\nn\n\nn\n\ny\n\nn\n\ny\n\ny\n\ny\n\ny\ny\n2\ny\nn\n' +./../install-jukebox.sh <<< "y +n + +n + +y + +n + +y + +y + +y +y +2 +y +y +n +" INSTALLATION_EXITCODE=$? # Test installation diff --git a/scripts/installscripts/tests/run_installation_tests3.sh b/scripts/installscripts/tests/run_installation_spotify.sh similarity index 61% rename from scripts/installscripts/tests/run_installation_tests3.sh rename to scripts/installscripts/tests/run_installation_spotify.sh index dc7e889d6..e63263a5a 100644 --- a/scripts/installscripts/tests/run_installation_tests3.sh +++ b/scripts/installscripts/tests/run_installation_spotify.sh @@ -15,17 +15,37 @@ echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selecti # Run installation (in interactive mode) # y confirm interactive -# n dont configure wifi -# n dont configure autohotspot -# y Headphone as iface -# y spotify with myuser, mypassword, myclient_id, myclient_secret -# y configure mpd -# y audio default location -# y config gpio -# n no RFID registration -# n No reboot - -./../install-jukebox.sh <<< $'y\nn\n\nn\n\ny\n\ny\nmyuser\nmypassword\nmyclient_id\nmyclient_secret\n\ny\n\ny\n\ny\n\ny\nn\nn\n' +# n configure wifi (extra ENTER) +# n configure autohotspot (extra ENTER) +# y use default audio iface (extra ENTER) +# y spotify with myuser, mypassword, myclient_id, myclient_secret (extra ENTER) +# y audio default location (extra ENTER) +# y config gpio (extra ENTER) +# y start installation +# n RFID registration +# n reboot + +./../install-jukebox.sh <<< "y +n + +n + +y + +y +myuser +mypassword +myclient_id +myclient_secret + +y + +y + +y +n +n +" INSTALLATION_EXITCODE=$? # Test installation diff --git a/scripts/installscripts/tests/run_installation_staticip_dhcpcd.sh b/scripts/installscripts/tests/run_installation_staticip_dhcpcd.sh new file mode 100644 index 000000000..a01610170 --- /dev/null +++ b/scripts/installscripts/tests/run_installation_staticip_dhcpcd.sh @@ -0,0 +1,57 @@ +#!/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 + +# Print current path +echo $PWD + +# Preparations +# No interactive frontend +export DEBIAN_FRONTEND=noninteractive +echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections + +# Run installation (in interactive mode) +# y confirm interactive mode +# y configure wifi +# y use wifi data (extra ENTER) +# n configure autohotspot +# - use autohotspot default config (extra ENTER) +# y use default audio iface (extra ENTER) +# n spotify (extra ENTER) +# y audio default location (extra ENTER) +# y config gpio (extra ENTER) +# y start installation +# n RFID registration +# n reboot +export CI_TEST_DHCPCD="true" +export CI_TEST_NETWORKMANAGER="false" +./../install-jukebox.sh <<< "y +y +TestWifi +DE +TestWifiPW +192.168.100.2 +192.168.100.1 +y + +n + +y + +n + +y + +y + +y +n +n +" +INSTALLATION_EXITCODE=$? + +# Test installation +./test_installation.sh $INSTALLATION_EXITCODE diff --git a/scripts/installscripts/tests/test_installation.sh b/scripts/installscripts/tests/test_installation.sh index ee2d9aaf6..93fd63315 100755 --- a/scripts/installscripts/tests/test_installation.sh +++ b/scripts/installscripts/tests/test_installation.sh @@ -16,6 +16,7 @@ tests=0 failed_tests=0 # Tool functions +source "${JUKEBOX_HOME_DIR}"/scripts/helperscripts/inc.networkHelper.sh check_chmod_chown() { local mod_expected=$1 @@ -35,6 +36,16 @@ check_chmod_chown() { done } +check_file_exists() { + local file="$1" + + if [[ ! -f "${file}" ]]; then + echo " ERROR: '${file}' does not exists or is not a file!" + ((failed_tests++)) + fi + ((tests++)) +} + check_file_contains_string() { local string="$1" local file="$2" @@ -136,7 +147,6 @@ verify_conf_file() { check_variable "SPOTIclientsecret" fi fi - check_variable "MPDconfig" check_variable "DIRaudioFolders" check_variable "GPIOconfig" @@ -159,66 +169,129 @@ verify_conf_file() { } verify_wifi_settings() { - local dhcpcd_conf="/etc/dhcpcd.conf" - local wpa_supplicant_conf="/etc/wpa_supplicant/wpa_supplicant.conf" - printf "\nTESTING WiFi settings...\n" - - # check conf files - check_file_contains_string "static ip_address=${WIFIip}/24" "${dhcpcd_conf}" - check_file_contains_string "static routers=${WIFIipRouter}" "${dhcpcd_conf}" - check_file_contains_string "static domain_name_servers=8.8.8.8 ${WIFIipRouter}" "${dhcpcd_conf}" - - check_file_contains_string "country=${WIFIcountryCode}" "${wpa_supplicant_conf}" - check_file_contains_string "ssid=\"${WIFIssid}\"" "${wpa_supplicant_conf}" - check_file_contains_string "psk=\"${WIFIpass}\"" "${wpa_supplicant_conf}" - - # check owner and permissions - check_chmod_chown 664 root netdev "/etc" "dhcpcd.conf" - check_chmod_chown 664 root netdev "/etc/wpa_supplicant" "wpa_supplicant.conf" + if [[ "$WIFIconfig" == "YES" ]]; then + if [[ $(is_dhcpcd_enabled) == true ]]; then + printf "\nTESTING WiFi settings (dhcpcd)...\n" + local dhcpcd_conf="/etc/dhcpcd.conf" + local wpa_supplicant_conf="/etc/wpa_supplicant/wpa_supplicant.conf" + + # check conf files + check_file_contains_string "static ip_address=${WIFIip}/24" "${dhcpcd_conf}" + check_file_contains_string "static routers=${WIFIipRouter}" "${dhcpcd_conf}" + check_file_contains_string "static domain_name_servers=${WIFIipRouter} 8.8.8.8" "${dhcpcd_conf}" + + check_file_contains_string "country=${WIFIcountryCode}" "${wpa_supplicant_conf}" + check_file_contains_string "ssid=\"${WIFIssid}\"" "${wpa_supplicant_conf}" + local _pass=$(_get_passphrase_for_config "$WIFIssid" "$WIFIpass") + check_file_contains_string "psk=${_pass}" "${wpa_supplicant_conf}" + check_file_contains_string "priority=99" "${wpa_supplicant_conf}" + + # check owner and permissions + check_chmod_chown 664 root netdev "/etc" "dhcpcd.conf" + check_chmod_chown 664 root netdev "/etc/wpa_supplicant" "wpa_supplicant.conf" + fi - # check that dhcpcd service is enabled and started - check_service_state dhcpcd active - check_service_enablement dhcpcd enabled + if [[ $(is_NetworkManager_enabled) == true ]]; then + printf "\nTESTING WiFi settings (NetworkManager)...\n" + local active_profile_path="/etc/NetworkManager/system-connections/${WIFIssid}.nmconnection" + + check_file_exists "${active_profile_path}" + check_file_contains_string "ssid=${WIFIssid}" "${active_profile_path}" + local _pass=$(_get_passphrase_for_config "$WIFIssid" "$WIFIpass") + check_file_contains_string "psk=${_pass}" "${active_profile_path}" + check_file_contains_string "address1=${WIFIip}" "${active_profile_path}" + check_file_contains_string "gateway=${WIFIipRouter}" "${active_profile_path}" + check_file_contains_string "dns=${WIFIipRouter}" "${active_profile_path}" + check_file_contains_string "autoconnect-priority=99" "${active_profile_path}" + fi + fi } verify_autohotspot_settings() { if [[ "$AUTOHOTSPOTconfig" == "YES" ]]; then printf "\nTESTING autohotspot settings...\n\n" - local autohotspot_service="autohotspot.service" - local autohotspot_script="/usr/bin/autohotspot" + local systemd_dir="/etc/systemd/system" + local interfaces_conf_file="/etc/network/interfaces" - local dnsmasq_conf=/etc/dnsmasq.conf - local hostapd_conf=/etc/hostapd/hostapd.conf - local hostapd_deamon=/etc/default/hostapd - local dhcpcd_conf=/etc/dhcpcd.conf + local autohotspot_script="/usr/bin/autohotspot" + local autohotspot_service_daemon="autohotspot-daemon.service" + local autohotspot_service_daemon_path="${systemd_dir}/${autohotspot_service_daemon}" + local autohotspot_service="autohotspot.service" + local autohotspot_service_path="${systemd_dir}/${autohotspot_service}" + local autohotspot_timer="autohotspot.timer" + local autohotspot_timer_path="${systemd_dir}/${autohotspot_timer}" - check_file_contains_string "${AUTOHOTSPOTip}" "${autohotspot_script}" + local autohotspot_wifi_interface=wlan0 local ip_without_last_segment=$(echo $AUTOHOTSPOTip | cut -d'.' -f1-3) - check_file_contains_string "dhcp-range=${ip_without_last_segment}.100,${ip_without_last_segment}.200,12h" "${dnsmasq_conf}" - check_file_contains_string "ssid=${AUTOHOTSPOTssid}" "${hostapd_conf}" - check_file_contains_string "wpa_passphrase=${AUTOHOTSPOTpass}" "${hostapd_conf}" - check_file_contains_string "country_code=${AUTOHOTSPOTcountryCode}" "${hostapd_conf}" - check_file_contains_string "DAEMON_CONF=\"${hostapd_conf}\"" "${hostapd_deamon}" - check_file_contains_string "nohook wpa_supplicant" "${dhcpcd_conf}" - check_file_contains_string "ExecStart=${AUTOHOTSPOT_SCRIPT}" "/etc/systemd/system/${autohotspot_service}" - - local crontab_user=$(crontab -l 2>/dev/null) - if [[ ! $(echo "${crontab_user}" | grep -w "${autohotspot_script}") ]]; then - echo " ERROR: crontab for user not installed" - ((failed_tests++)) + local autohotspot_profile="Phoniebox_Hotspot" + + if [[ $(is_dhcpcd_enabled) == true ]]; then + local dnsmasq_conf=/etc/dnsmasq.conf + local hostapd_conf=/etc/hostapd/hostapd.conf + local hostapd_deamon=/etc/default/hostapd + local dhcpcd_conf=/etc/dhcpcd.conf + + check_file_exists "${interfaces_conf_file}" + + check_file_contains_string "interface=${autohotspot_wifi_interface}" "${dnsmasq_conf}" + check_file_contains_string "dhcp-range=${ip_without_last_segment}.100,${ip_without_last_segment}.200,12h" "${dnsmasq_conf}" + check_file_contains_string "interface=${autohotspot_wifi_interface}" "${hostapd_conf}" + check_file_contains_string "ssid=${AUTOHOTSPOTssid}" "${hostapd_conf}" + check_file_contains_string "wpa_passphrase=${AUTOHOTSPOTpass}" "${hostapd_conf}" + check_file_contains_string "country_code=${AUTOHOTSPOTcountryCode}" "${hostapd_conf}" + check_file_contains_string "DAEMON_CONF=\"${hostapd_conf}\"" "${hostapd_deamon}" + check_file_contains_string "nohook wpa_supplicant" "${dhcpcd_conf}" + + check_file_exists "${autohotspot_script}" + check_file_contains_string "wifidev=\"${autohotspot_wifi_interface}\"" "${autohotspot_script}" + check_file_contains_string "hotspot_ip=${AUTOHOTSPOTip}" "${autohotspot_script}" + check_file_contains_string "daemon_service=\"${autohotspot_service_daemon}\"" "${autohotspot_script}" + + check_file_exists "${autohotspot_service_daemon_path}" + check_file_contains_string "\-i \"${autohotspot_wifi_interface}\"" "${autohotspot_service_daemon_path}" + + check_file_exists "${autohotspot_service_path}" + check_file_contains_string "ExecStart=${autohotspot_script}" "${autohotspot_service_path}" + + check_file_exists "${autohotspot_timer_path}" + check_file_contains_string "Unit=${autohotspot_service}" "${autohotspot_timer_path}" + + # check owner and permissions + check_chmod_chown 644 root root "/etc" "dnsmasq.conf hostapd/hostapd.conf default/hostapd" + check_chmod_chown 664 root netdev "/etc" "dhcpcd.conf" + check_chmod_chown 644 root root "${systemd_dir}" "${autohotspot_service_daemon} ${autohotspot_service} ${autohotspot_timer}" + + # check the services state + check_service_enablement "${autohotspot_service_daemon}" enabled + check_service_enablement "${autohotspot_service}" disabled + check_service_enablement "${autohotspot_timer}" enabled + check_service_enablement hostapd disabled + check_service_enablement dnsmasq disabled fi - ((tests++)) - # check owner and permissions - check_chmod_chown 644 root root "/etc" "dnsmasq.conf hostapd/hostapd.conf default/hostapd" - check_chmod_chown 664 root netdev "/etc" "dhcpcd.conf" - check_chmod_chown 644 root root "/etc/systemd/system" "${autohotspot_service}" + if [[ $(is_NetworkManager_enabled) == true ]]; then + check_file_exists "${interfaces_conf_file}" - # check that the services are activ - check_service_enablement "${autohotspot_service}" enabled - check_service_enablement hostapd disabled - check_service_enablement dnsmasq disabled + check_file_exists "${autohotspot_script}" + check_file_contains_string "wdev0='${autohotspot_wifi_interface}'" "${autohotspot_script}" + check_file_contains_string "ap_profile_name='${autohotspot_profile}'" "${autohotspot_script}" + check_file_contains_string "ap_ssid='${AUTOHOTSPOTssid}'" "${autohotspot_script}" + check_file_contains_string "ap_pw='${AUTOHOTSPOTpass}'" "${autohotspot_script}" + check_file_contains_string "ap_ip='${AUTOHOTSPOTip}" "${autohotspot_script}" #intentional "open end" + check_file_contains_string "ap_gate='${ip_without_last_segment}" "${autohotspot_script}" #intentional "open end" + check_file_contains_string "timer_service_name='${autohotspot_timer}'" "${autohotspot_script}" + + check_file_exists "${autohotspot_service_path}" + check_file_contains_string "ExecStart=${autohotspot_script}" "${autohotspot_service_path}" + + check_file_exists "${autohotspot_timer_path}" + check_file_contains_string "Unit=${autohotspot_service}" "${autohotspot_timer_path}" + + # check the services state + check_service_enablement "${autohotspot_service}" disabled + check_service_enablement "${autohotspot_timer}" enabled + fi fi } @@ -239,7 +312,8 @@ verify_apt_packages() { local packages=$(call_with_args_from_file "${jukebox_dir}"/packages.txt echo) local packages_raspberrypi=$(call_with_args_from_file "${jukebox_dir}"/packages-raspberrypi.txt echo) local packages_spotify=$(call_with_args_from_file "${jukebox_dir}"/packages-spotify.txt echo) - local packages_autohotspot=$(call_with_args_from_file "${jukebox_dir}"/packages-autohotspot.txt echo) + local packages_autohotspot_dhcpcd=$(call_with_args_from_file "${jukebox_dir}"/packages-autohotspot_dhcpcd.txt echo) + local packages_autohotspot_NetworkManager=$(call_with_args_from_file "${jukebox_dir}"/packages-autohotspot_NetworkManager.txt echo) printf "\nTESTING installed packages...\n\n" @@ -249,7 +323,12 @@ verify_apt_packages() { fi if [[ "$AUTOHOTSPOTconfig" == "YES" ]]; then - packages="${packages} ${packages_autohotspot}" + if [[ $(is_dhcpcd_enabled) == true ]]; then + packages="${packages} ${packages_autohotspot_dhcpcd}" + fi + if [[ $(is_NetworkManager_enabled) == true ]]; then + packages="${packages} ${packages_autohotspot_NetworkManager}" + fi fi # check for raspberry pi packages only on raspberry pi's but not on test docker containers running on x86_64 machines @@ -343,27 +422,29 @@ verify_systemd_services() { } verify_spotify_config() { - local etc_mopidy_conf="/etc/mopidy/mopidy.conf" - local mopidy_conf="${HOME_DIR}/.config/mopidy/mopidy.conf" - - printf "\nTESTING spotify config...\n\n" - - check_file_contains_string "username = ${SPOTIuser}" "${etc_mopidy_conf}" - check_file_contains_string "password = ${SPOTIpass}" "${etc_mopidy_conf}" - check_file_contains_string "client_id = ${SPOTIclientid}" "${etc_mopidy_conf}" - check_file_contains_string "client_secret = ${SPOTIclientsecret}" "${etc_mopidy_conf}" - check_file_contains_string "media_dir = ${DIRaudioFolders}" "${etc_mopidy_conf}" - - check_file_contains_string "username = ${SPOTIuser}" "${mopidy_conf}" - check_file_contains_string "password = ${SPOTIpass}" "${mopidy_conf}" - check_file_contains_string "client_id = ${SPOTIclientid}" "${mopidy_conf}" - check_file_contains_string "client_secret = ${SPOTIclientsecret}" "${mopidy_conf}" - check_file_contains_string "media_dir = ${DIRaudioFolders}" "${mopidy_conf}" - - # check that mopidy service is enabled - check_service_enablement mopidy enabled - # check that mpd service is disabled - check_service_enablement mpd disabled + if [[ "${SPOTinstall}" == "YES" ]]; then + local etc_mopidy_conf="/etc/mopidy/mopidy.conf" + local mopidy_conf="${HOME_DIR}/.config/mopidy/mopidy.conf" + + printf "\nTESTING spotify config...\n\n" + + check_file_contains_string "username = ${SPOTIuser}" "${etc_mopidy_conf}" + check_file_contains_string "password = ${SPOTIpass}" "${etc_mopidy_conf}" + check_file_contains_string "client_id = ${SPOTIclientid}" "${etc_mopidy_conf}" + check_file_contains_string "client_secret = ${SPOTIclientsecret}" "${etc_mopidy_conf}" + check_file_contains_string "media_dir = ${DIRaudioFolders}" "${etc_mopidy_conf}" + + check_file_contains_string "username = ${SPOTIuser}" "${mopidy_conf}" + check_file_contains_string "password = ${SPOTIpass}" "${mopidy_conf}" + check_file_contains_string "client_id = ${SPOTIclientid}" "${mopidy_conf}" + check_file_contains_string "client_secret = ${SPOTIclientsecret}" "${mopidy_conf}" + check_file_contains_string "media_dir = ${DIRaudioFolders}" "${mopidy_conf}" + + # check that mopidy service is enabled + check_service_enablement mopidy enabled + # check that mpd service is disabled + check_service_enablement mpd disabled + fi } verify_mpd_config() { @@ -405,18 +486,14 @@ main() { printf "\nTesting installation:\n" verify_installation_exitcode verify_conf_file - if [[ "$WIFIconfig" == "YES" ]]; then - verify_wifi_settings - fi verify_apt_packages "${JUKEBOX_HOME_DIR}" verify_pip_packages "${JUKEBOX_HOME_DIR}" + verify_wifi_settings verify_samba_config verify_webserver_config verify_systemd_services - if [[ "${SPOTinstall}" == "YES" ]]; then - verify_spotify_config - fi verify_mpd_config + verify_spotify_config verify_autohotspot_settings verify_folder_access "${JUKEBOX_HOME_DIR}" } diff --git a/scripts/playout_controls.sh b/scripts/playout_controls.sh index 908655ff8..c6f6f3c80 100755 --- a/scripts/playout_controls.sh +++ b/scripts/playout_controls.sh @@ -120,7 +120,7 @@ if [ "${DEBUG_playout_controls_sh}" == "TRUE" ]; then echo "VAR VALUE: ${VALUE}" # speed of these commands. shortcutCommands="^(setvolume|volumedown|volumeup|mute)$" -autohotspot_script="/usr/bin/autohotspot" +autohotspot_service='autohotspot.service' # Run the code from this block only, if the current command is not in "shortcutCommands" if [[ ! "$COMMAND" =~ $shortcutCommands ]] @@ -1007,7 +1007,7 @@ case $COMMAND in enablewifi) if [ "${DEBUG_playout_controls_sh}" == "TRUE" ]; then echo " ${COMMAND}" >> ${PATHDATA}/../logs/debug.log; fi rfkill unblock wifi - if [ -f "$autohotspot_script" ]; then sudo "$autohotspot_script"; fi + sudo systemctl start "$autohotspot_service" > /dev/null 2>&1 ;; disablewifi) if [ "${DEBUG_playout_controls_sh}" == "TRUE" ]; then echo " ${COMMAND}" >> ${PATHDATA}/../logs/debug.log; fi @@ -1021,10 +1021,8 @@ case $COMMAND in # Build special for franzformator rfkill list wifi | grep -i "Soft blocked: no" > /dev/null 2>&1 WIFI_SOFTBLOCK_RESULT=$? - wpa_cli -i wlan0 status | grep 'ip_address' > /dev/null 2>&1 - WIFI_IP_RESULT=$? - if [ "${DEBUG_playout_controls_sh}" == "TRUE" ]; then echo " WIFI_IP_RESULT='${WIFI_IP_RESULT}' WIFI_SOFTBLOCK_RESULT='${WIFI_SOFTBLOCK_RESULT}'" >> ${PATHDATA}/../logs/debug.log; fi - if [ $WIFI_SOFTBLOCK_RESULT -eq 0 ] && [ $WIFI_IP_RESULT -eq 0 ] + if [ "${DEBUG_playout_controls_sh}" == "TRUE" ]; then echo " WIFI_SOFTBLOCK_RESULT='${WIFI_SOFTBLOCK_RESULT}'" >> ${PATHDATA}/../logs/debug.log; fi + if [ $WIFI_SOFTBLOCK_RESULT -eq 0 ] then if [ "${DEBUG_playout_controls_sh}" == "TRUE" ]; then echo " Wifi will now be deactivated" >> ${PATHDATA}/../logs/debug.log; fi echo "Wifi will now be deactivated" @@ -1033,7 +1031,7 @@ case $COMMAND in if [ "${DEBUG_playout_controls_sh}" == "TRUE" ]; then echo " Wifi will now be activated" >> ${PATHDATA}/../logs/debug.log; fi echo "Wifi will now be activated" rfkill unblock wifi - if [ -f "$autohotspot_script" ]; then sudo "$autohotspot_script"; fi + sudo systemctl start "$autohotspot_service" > /dev/null 2>&1 fi ;; randomcard) diff --git a/scripts/userscripts/addhotspot.sh b/scripts/userscripts/addhotspot.sh deleted file mode 100644 index 6e8e63a4e..000000000 --- a/scripts/userscripts/addhotspot.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -if [[ -z "$1" || -z "$2" ]]; then - echo "usage: addhotspot.sh " - exit 1 -fi - -# addhotspot.sh newssid newpassword -wpa_passphrase "$1" $2 >> /etc/wpa_supplicant/wpa_supplicant.conf - -# /etc/dhcpcd.conf -if [[ ! $(grep -w "ssid $1" /etc/dhcpcd.conf) ]]; then - echo "ssid $1" >> /etc/dhcpcd.conf -fi diff --git a/scripts/userscripts/placeholder b/scripts/userscripts/placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/settings/version-number b/settings/version-number index 437459cd9..e70b4523a 100644 --- a/settings/version-number +++ b/settings/version-number @@ -1 +1 @@ -2.5.0 +2.6.0