diff --git a/.github/workflows/integration_delivery.yml b/.github/workflows/integration_delivery.yml index 53402cb1..cee560d7 100644 --- a/.github/workflows/integration_delivery.yml +++ b/.github/workflows/integration_delivery.yml @@ -16,6 +16,15 @@ jobs: - uses: actions/checkout@v4 name: Checkout + - name: Save Cached Poetry + id: cached-poetry + uses: actions/cache@v4 + with: + path: | + ~/.cache + ~/.local + key: poetry-${{ hashFiles('poetry.lock') }} + - uses: actions/setup-python@v5 name: Setup Python with: @@ -30,15 +39,6 @@ jobs: - name: Install dependencies run: poetry install --extras=dev --with dev - - name: Save Cached Poetry - id: cached-poetry - uses: actions/cache/save@v4 - with: - path: | - ~/.cache - ~/.local - key: poetry-${{ hashFiles('pyproject.toml', 'poetry.lock') }} - type-check: name: Type Check needs: @@ -61,7 +61,7 @@ jobs: path: | ~/.cache ~/.local - key: poetry-${{ hashFiles('pyproject.toml', 'poetry.lock') }} + key: poetry-${{ hashFiles('poetry.lock') }} - name: Create stub files run: poetry run pyright --createstub kivy @@ -91,7 +91,7 @@ jobs: path: | ~/.cache ~/.local - key: poetry-${{ hashFiles('pyproject.toml', 'poetry.lock') }} + key: poetry-${{ hashFiles('poetry.lock') }} - name: Lint run: poetry run poe lint @@ -102,7 +102,8 @@ jobs: - dependencies runs-on: ubuntu-latest outputs: - ubo_app_version: ${{ steps.extract_version.outputs.ubo_app_version }} + version: ${{ steps.extract_version.outputs.version }} + name: ${{ steps.extract_version.outputs.name }} steps: - uses: actions/checkout@v4 name: Checkout @@ -122,7 +123,7 @@ jobs: path: | ~/.cache ~/.local - key: poetry-${{ hashFiles('pyproject.toml', 'poetry.lock') }} + key: poetry-${{ hashFiles('poetry.lock') }} - name: Build run: poetry build @@ -130,8 +131,8 @@ jobs: - name: Extract Version id: extract_version run: | - echo "ubo_app_version=$(poetry run python scripts/print_version.py)" >> "$GITHUB_OUTPUT" - echo "ubo_app_version=$(poetry run python scripts/print_version.py)" + echo "version=$(poetry version --short)" >> "$GITHUB_OUTPUT" + echo "name=$(poetry version | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - name: Upload wheel uses: actions/upload-artifact@v4 @@ -157,8 +158,8 @@ jobs: - build runs-on: ubuntu-latest environment: - name: PyPI - url: https://pypi.org/p/ubo-app + name: release + url: https://pypi.org/p/${{ needs.build.outputs.name }} permissions: id-token: write steps: @@ -177,6 +178,7 @@ jobs: with: packages-dir: dist verbose: true + skip-existing: true images: name: Create Images @@ -231,7 +233,7 @@ jobs: - name: Build Artifact env: - PKR_VAR_ubo_app_version: ${{ needs.build.outputs.ubo_app_version }} + PKR_VAR_ubo_app_version: ${{ needs.build.outputs.version }} PKR_VAR_image_url: ${{ steps.generate_image_url.outputs.image_url }} PKR_VAR_image_checksum: ${{ steps.generate_image_url.outputs.image_checksum }} @@ -257,8 +259,8 @@ jobs: uses: actions/upload-artifact@v4 with: name: - ubo_app-bookworm${{ - steps.generate_image_url.outputs.dashed_suffix}}.img.gz + ubo_app-${{ needs.build.outputs.version }}-bookworm${{ + steps.generate_image_url.outputs.dashed_suffix}}-arm64.img.gz path: /ubo_app-bookworm${{ steps.generate_image_url.outputs.dashed_suffix }}.img.gz @@ -273,6 +275,9 @@ jobs: - pypi-publish - images runs-on: ubuntu-latest + environment: + name: release + url: https://pypi.org/p/${{ needs.build.outputs.name }} permissions: contents: write if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') @@ -280,23 +285,24 @@ jobs: - name: Procure Lite Image uses: actions/download-artifact@v4 with: - name: ubo_app-bookworm-lite.img.gz + name: + ubo_app-${{ needs.build.outputs.version + }}-bookworm-lite-arm64.img.gz path: artifacts - name: Procure Default Image uses: actions/download-artifact@v4 with: - name: ubo_app-bookworm.img.gz + name: ubo_app-${{ needs.build.outputs.version }}-bookworm-arm64.img.gz path: artifacts - # TODO - # It is larger than 2GB, so it is not possible to upload it to GitHub - # Looking for a solution to this problem - # - name: Procure Full Image - # uses: actions/download-artifact@v4 - # with: - # name: ubo_app-bookworm-full.img.gz - # path: artifacts + - name: Procure Full Image + uses: actions/download-artifact@v4 + with: + name: + ubo_app-${{ needs.build.outputs.version + }}-bookworm-full-arm64.img.gz + path: artifacts - name: Procure Wheel uses: actions/download-artifact@v4 @@ -310,13 +316,30 @@ jobs: name: binary path: artifacts + - name: + Split Large Files into 2GB chunks in a for loop only if they are + bigger than 2GB + run: | + for file in artifacts/*; do + if [ $(stat -c%s "$file") -gt 2000000000 ]; then + split -b 2000000000 "$file" "$file"_ + rm "$file" + fi + done + - name: Release uses: softprops/action-gh-release@v1 with: files: artifacts/* - tag_name: ${{ needs.build.outputs.ubo_app_version }} + tag_name: ${{ needs.build.outputs.version }} body: | - Release of version ${{ needs.build.outputs.ubo_app_version }} - PyPI package: https://pypi.org/project/ubo-app/${{ needs.build.outputs.ubo_app_version }} + Release of version ${{ needs.build.outputs.version }} + PyPI package: https://pypi.org/project/${{ needs.build.outputs.name }}/${{ needs.build.outputs.version }} + + Note than GitHub doesn't allow assets bigger than 2GB in a release. Due to this, the files bigger than 2GB have been split into 2GB chunks. You can join them using the following command: + + ```bash + cat [[filename]]_* > [[filename]] + ``` prerelease: false draft: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 329228ff..ab001cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Version 0.10.7 + +- chore: split asset files bigger than 2GB in the release into chunks of 2GB. +- refactor: general housekeeping + ## Version 0.10.6 - fix: wireless module now has sufficient privileges diff --git a/poetry.lock b/poetry.lock index c397603d..f9c06417 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1116,13 +1116,13 @@ pywin32 = ">=223" [[package]] name = "pyright" -version = "1.1.350" +version = "1.1.353" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.350-py3-none-any.whl", hash = "sha256:f1dde6bcefd3c90aedbe9dd1c573e4c1ddbca8c74bf4fa664dd3b1a599ac9a66"}, - {file = "pyright-1.1.350.tar.gz", hash = "sha256:a8ba676de3a3737ea4d8590604da548d4498cc5ee9ee00b1a403c6db987916c6"}, + {file = "pyright-1.1.353-py3-none-any.whl", hash = "sha256:8d7e6719d0be4fd9f4a37f010237c6a74d91ec1e7c81de634c2f3f9965f8ab43"}, + {file = "pyright-1.1.353.tar.gz", hash = "sha256:24343bbc2a4f997563f966b6244a2e863473f1d85af6d24abcb366fcbb4abca9"}, ] [package.dependencies] @@ -1161,6 +1161,20 @@ files = [ lock = ">=2018.3.25.2110,<2019.0.0.0" python-immutable = ">=1.0.0,<2.0.0" +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "python-immutable" version = "1.0.2" @@ -1174,17 +1188,17 @@ files = [ [[package]] name = "python-redux" -version = "0.9.24" +version = "0.10.4" description = "Redux implementation for Python" optional = false -python-versions = ">=3.9,<4.0" +python-versions = ">=3.11,<4.0" files = [ - {file = "python_redux-0.9.24-py3-none-any.whl", hash = "sha256:b5ebc975e5e3921bdc84c2b6998fc17c9fa17f9b879b1e07bf3db5395ba1c633"}, - {file = "python_redux-0.9.24.tar.gz", hash = "sha256:7eeb64a6d2b53e33203ce61ec5859c3c44f6bf3cf13464c432385e8f058095ef"}, + {file = "python_redux-0.10.4-py3-none-any.whl", hash = "sha256:7ea4210e8e3a527690d71c650c0de9c5adcabdc874bd657271957c759ff3c424"}, + {file = "python_redux-0.10.4.tar.gz", hash = "sha256:ef6b6f8c3a56ff069e5907331bc2889cef2d80d31c5fb96cbf87a8c54f495325"}, ] [package.dependencies] -python-immutable = ">=1.0.0,<2.0.0" +python-immutable = ">=1.0.2,<2.0.0" typing-extensions = ">=4.9.0,<5.0.0" [[package]] @@ -1269,28 +1283,28 @@ files = [ [[package]] name = "ruff" -version = "0.2.1" +version = "0.2.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, - {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, - {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, - {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, - {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, + {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, + {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, + {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, + {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, ] [[package]] @@ -1543,4 +1557,4 @@ dev = ["ubo-gui", "ubo-gui"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "925a49e068a9b3a98a021ce28f97a2c4a5b3d0a130c467cb9a4254dc29465c9e" +content-hash = "01c10c2a527507e1e08ea47fc77d3e89459f1ab699fb4a0277743803b9f9cad9" diff --git a/pyproject.toml b/pyproject.toml index a2f735be..73da75aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ubo-app" -version = "0.10.6" +version = "0.10.7" description = "Ubo main app, running on device initialization. A platform for running other apps." authors = ["Sassan Haradji "] license = "Apache-2.0" @@ -25,7 +25,7 @@ ubo-gui = [ 'dev', ] }, ] -python-redux = "^0.9.24" +python-redux = "==0.10.4" pyzbar = "^0.1.9" sdbus-networkmanager = { version = "^2.0.0", markers = "platform_machine=='aarch64'" } rpi_ws281x = { version = "^5.0.0", markers = "platform_machine=='aarch64'" } @@ -45,6 +45,7 @@ optional = true pyright = "^1.1.349" ruff = "^0.2.1" toml = "^0.10.2" +python-dotenv = "^1.0.1" [tool.poetry.extras] default = ['ubo-gui', 'headless-kivy-pi'] diff --git a/ubo_app/load_services.py b/ubo_app/load_services.py index 6b02ce4e..5a21d952 100644 --- a/ubo_app/load_services.py +++ b/ubo_app/load_services.py @@ -57,18 +57,18 @@ def get_filename(self: UboServiceModuleLoader, name: str | None = None) -> str: class UboServiceLoopLoader(importlib.abc.Loader): - def __init__(self: UboServiceLoopLoader, thread: UboServiceThread) -> None: - self.thread = thread + def __init__(self: UboServiceLoopLoader, service: UboServiceThread) -> None: + self.service = service def exec_module(self: UboServiceLoopLoader, module: ModuleType) -> None: cast(Any, module)._create_task = ( # noqa: SLF001 - lambda task: self.thread.loop.call_soon_threadsafe( - self.thread.loop.create_task, + lambda task: self.service.loop.call_soon_threadsafe( + self.service.loop.create_task, task, ) ) cast(Any, module)._run_in_executor = ( # noqa: SLF001 - lambda executor, task, *args: self.thread.loop.run_in_executor( + lambda executor, task, *args: self.service.loop.run_in_executor( executor, task, *args, @@ -76,7 +76,7 @@ def exec_module(self: UboServiceLoopLoader, module: ModuleType) -> None: ) def __repr__(self: UboServiceLoopLoader) -> str: - return f'{self.thread.path}' + return f'{self.service.path}' class UboServiceFinder(importlib.abc.MetaPathFinder): @@ -98,13 +98,13 @@ def find_spec( ) if matching_path in REGISTERED_PATHS: - thread = REGISTERED_PATHS[matching_path] - module_name = f'{thread.service_uid}:{fullname}' + service = REGISTERED_PATHS[matching_path] + module_name = f'{service.service_uid}:{fullname}' if fullname == 'ubo_app.utils.loop': return importlib.util.spec_from_loader( module_name, - UboServiceLoopLoader(thread), + UboServiceLoopLoader(service), ) spec = PathFinder.find_spec( @@ -232,11 +232,11 @@ def load_services() -> None: current_path = Path().absolute() os.chdir(service_path.as_posix()) - thread = UboServiceThread( + service = UboServiceThread( service_path, service_uid, ) - REGISTERED_PATHS[service_path] = thread - thread.start() + REGISTERED_PATHS[service_path] = service + service.start() os.chdir(current_path) diff --git a/ubo_app/menu_central.py b/ubo_app/menu_central.py index a23e3567..b607e06b 100644 --- a/ubo_app/menu_central.py +++ b/ubo_app/menu_central.py @@ -50,7 +50,7 @@ def __init__( self.ids.right_column.add_widget(volume_widget) @autorun(lambda state: state.sound.playback_volume) - def sync_output_volume(selector_result: float) -> None: + def _(selector_result: float) -> None: volume_widget.value = selector_result * 100 @cached_property @@ -134,7 +134,7 @@ def handle_title_change(_: MenuWidget, title: str) -> None: subscribe_event( KeypadKeyPressEvent, self.handle_key_press_event, - options=EventSubscriptionOptions(run_async=False), + options=EventSubscriptionOptions(run_async=True), ) subscribe_event( diff --git a/ubo_app/services/000-sound/audio_manager.py b/ubo_app/services/000-sound/audio_manager.py index d3c5b06a..c0e3df20 100644 --- a/ubo_app/services/000-sound/audio_manager.py +++ b/ubo_app/services/000-sound/audio_manager.py @@ -7,9 +7,8 @@ import time import wave -from headless_kivy_pi import IS_RPI - from ubo_app.logging import logger +from ubo_app.utils import IS_RPI if not IS_RPI: import sys diff --git a/ubo_app/services/010-ip/setup.py b/ubo_app/services/010-ip/setup.py index 53632ecf..c63b8853 100644 --- a/ubo_app/services/010-ip/setup.py +++ b/ubo_app/services/010-ip/setup.py @@ -52,7 +52,7 @@ def get_ip_addresses(interfaces: Sequence[IpNetworkInterface]) -> list[SubMenuIt ] -def load_ip_addresses(_: IpUpdateRequestEvent | None = None) -> None: +def load_ip_addresses() -> None: ip_addresses_by_interface = defaultdict(list) for interface_name, ip_addresses in psutil.net_if_addrs().items(): for address in ip_addresses: diff --git a/ubo_app/services/010-wifi/pages/create_wireless_connection.py b/ubo_app/services/010-wifi/pages/create_wireless_connection.py index a737b211..ad03cb83 100644 --- a/ubo_app/services/010-wifi/pages/create_wireless_connection.py +++ b/ubo_app/services/010-wifi/pages/create_wireless_connection.py @@ -4,7 +4,7 @@ import pathlib from typing import Sequence -from kivy.clock import Clock +from kivy.clock import mainthread from kivy.lang.builder import Builder from kivy.properties import BooleanProperty from ubo_gui.constants import SUCCESS_COLOR @@ -50,7 +50,7 @@ def __init__( self.create_wireless_connection, ) dispatch(SoundPlayChimeAction(name='scan')) - self.bind(on_close=lambda *_: self.unsubscribe) + self.bind(on_close=lambda *_: self.unsubscribe()) def create_wireless_connection( self: CreateWirelessConnectionPage, @@ -95,7 +95,7 @@ async def act() -> None: ), ), ) - Clock.schedule_once(lambda _: self.dispatch('on_close'), 0) + mainthread(self.dispatch)('on_close') self.creating = True create_task(act()) diff --git a/ubo_app/services/010-wifi/setup.py b/ubo_app/services/010-wifi/setup.py index 088b4c94..cf8987b9 100644 --- a/ubo_app/services/010-wifi/setup.py +++ b/ubo_app/services/010-wifi/setup.py @@ -59,4 +59,4 @@ def init_service() -> None: dispatch(RegisterSettingAppAction(menu_item=main.WiFiMainMenu)) - subscribe_event(WiFiUpdateRequestEvent, lambda _: create_task(request_scan())) + subscribe_event(WiFiUpdateRequestEvent, lambda: create_task(request_scan())) diff --git a/ubo_app/services/010-wifi/wifi_manager.py b/ubo_app/services/010-wifi/wifi_manager.py index 17faf8c8..950099bb 100644 --- a/ubo_app/services/010-wifi/wifi_manager.py +++ b/ubo_app/services/010-wifi/wifi_manager.py @@ -380,7 +380,7 @@ async def forget_wireless_connection(ssid: str) -> None: async def get_connections() -> list[WiFiConnection]: # It is need as this action is not atomic and the active_connection may not be # available when active_connection.state is queried - for i in range(RETRIES): + for _ in range(RETRIES): with contextlib.suppress(Exception): active_connection = await get_active_connection() active_connection_ssid = await get_active_connection_ssid() diff --git a/ubo_app/services/020-keyboard/setup.py b/ubo_app/services/020-keyboard/setup.py index 0bc6de6a..095c791d 100644 --- a/ubo_app/services/020-keyboard/setup.py +++ b/ubo_app/services/020-keyboard/setup.py @@ -5,27 +5,22 @@ from kivy.core.window import Keyboard, Window, WindowBase -from ubo_app.store.services.keypad import ( - Key, - KeypadKeyPressAction, -) -from ubo_app.store.services.sound import ( - SoundDevice, - SoundToggleMuteStatusAction, -) +from ubo_app.store.services.keypad import Key, KeypadKeyPressAction +from ubo_app.store.services.sound import SoundDevice, SoundToggleMuteStatusAction if TYPE_CHECKING: Modifier = Literal['ctrl', 'alt', 'meta', 'shift'] def on_keyboard( - _: WindowBase, + window: WindowBase, key: int, - _scancode: int, - _codepoint: str, + scancode: int, + codepoint: str, modifier: list[Modifier], ) -> None: """Handle keyboard events.""" + _ = window, scancode, codepoint from ubo_app.store import dispatch if modifier == []: diff --git a/ubo_app/services/020-keypad/setup.py b/ubo_app/services/020-keypad/setup.py index 3eb7efb4..f0f7223e 100644 --- a/ubo_app/services/020-keypad/setup.py +++ b/ubo_app/services/020-keypad/setup.py @@ -1,4 +1,4 @@ -# pyright: reportMissingImports=false +# pyright: reportMissingImports=false, reportMissingModuleSource=false # ruff: noqa: D100, D101, D102, D103, D104, D107 from __future__ import annotations @@ -10,7 +10,6 @@ import board from immutable import Immutable -from RPi import GPIO # pyright: ignore [reportMissingModuleSource] from ubo_app.store.services.keypad import Key, KeypadKeyPressAction from ubo_app.store.services.sound import SoundDevice, SoundSetMuteStatusAction @@ -166,7 +165,11 @@ def disable_interrupt_for_higher_bits( i2c.write_then_readinto(buffer, buffer, out_end=1, in_start=1) def init_i2c(self: Keypad) -> None: + if not IS_RPI: + return # connect to the I2C bus + from RPi import GPIO + GPIO.setmode(GPIO.BCM) i2c = board.I2C() # Set this to the GPIO of the interrupt: diff --git a/ubo_app/side_effects.py b/ubo_app/side_effects.py index ffe4d109..0e49a05d 100644 --- a/ubo_app/side_effects.py +++ b/ubo_app/side_effects.py @@ -19,8 +19,8 @@ ) from ubo_app.store.update_manager.reducer import ABOUT_MENU_PATH from ubo_app.store.update_manager.utils import check_version, update -from ubo_app.utils import initialize_board, turn_off_screen, turn_on_screen from ubo_app.utils.async_ import create_task +from ubo_app.utils.hardware import initialize_board, turn_off_screen, turn_on_screen if TYPE_CHECKING: from ubo_app.menu import MenuApp diff --git a/ubo_app/store/__init__.py b/ubo_app/store/__init__.py index 6a2a773c..8011659c 100644 --- a/ubo_app/store/__init__.py +++ b/ubo_app/store/__init__.py @@ -8,8 +8,9 @@ BaseCombineReducerState, CombineReducerAction, CreateStoreOptions, + InitAction, + Store, combine_reducers, - create_store, ) from ubo_app.constants import DEBUG_MODE @@ -71,10 +72,10 @@ def scheduler(callback: Callable[[], None], *, interval: bool) -> None: Clock.create_trigger(lambda _: callback(), 0, interval=interval)() -store = create_store( +store = Store( root_reducer, CreateStoreOptions( - auto_init=True, + auto_init=False, scheduler=scheduler, action_middleware=lambda action: logger.debug( 'Action dispatched', @@ -92,7 +93,9 @@ def scheduler(callback: Callable[[], None], *, interval: bool) -> None: subscribe = store.subscribe subscribe_event = store.subscribe_event +dispatch(InitAction()) + if DEBUG_MODE: subscribe(lambda state: logger.verbose('State updated', extra={'state': state})) -__ALL__ = (autorun, dispatch, subscribe, subscribe_event) +__all__ = ('autorun', 'dispatch', 'subscribe', 'subscribe_event') diff --git a/ubo_app/store/main/__init__.py b/ubo_app/store/main/__init__.py index c9d02d90..5be8755f 100644 --- a/ubo_app/store/main/__init__.py +++ b/ubo_app/store/main/__init__.py @@ -4,7 +4,8 @@ from dataclasses import field from typing import TYPE_CHECKING, Sequence -from redux import BaseAction, BaseEvent, FinishAction, Immutable, InitAction +from immutable import Immutable +from redux import BaseAction, BaseEvent, FinishAction, InitAction from ubo_app.store.services.keypad import KeypadAction from ubo_app.store.status_icons import StatusIconsAction diff --git a/ubo_app/store/main/_menus.py b/ubo_app/store/main/_menus.py index 4d8b8ce1..acfe5955 100644 --- a/ubo_app/store/main/_menus.py +++ b/ubo_app/store/main/_menus.py @@ -3,7 +3,7 @@ from datetime import datetime, timezone from typing import TYPE_CHECKING, Sequence -from redux import AutorunOptions, FinishAction +from redux import AutorunOptions from ubo_gui.menu.types import ( ActionItem, ApplicationItem, @@ -15,12 +15,7 @@ from ubo_app.store import autorun, dispatch from ubo_app.store.main import PowerOffAction -from ubo_app.store.services.notifications import ( - Chime, - Notification, - NotificationsClearAction, -) -from ubo_app.store.services.sound import SoundPlayChimeAction +from ubo_app.store.services.notifications import Notification, NotificationsClearAction from ubo_app.store.update_manager.utils import CURRENT_VERSION, about_menu_items if TYPE_CHECKING: @@ -140,11 +135,7 @@ def on_dismiss(self: NotificationWrapper) -> None: ), ActionItem( label='Turn off', - action=lambda: dispatch( - SoundPlayChimeAction(name=Chime.FAILURE), - PowerOffAction(), - FinishAction(), - ), + action=lambda: dispatch(PowerOffAction()), icon='power_settings_new', is_short=True, ), diff --git a/ubo_app/store/services/camera.py b/ubo_app/store/services/camera.py index 7712d220..b083a24a 100644 --- a/ubo_app/store/services/camera.py +++ b/ubo_app/store/services/camera.py @@ -1,7 +1,8 @@ # ruff: noqa: D100, D101, D102, D103, D104, D107, N999 from __future__ import annotations -from redux import BaseAction, BaseEvent, Immutable +from immutable import Immutable +from redux import BaseAction, BaseEvent class CameraAction(BaseAction): diff --git a/ubo_app/store/services/docker.py b/ubo_app/store/services/docker.py index fbaa33fa..a214a06c 100644 --- a/ubo_app/store/services/docker.py +++ b/ubo_app/store/services/docker.py @@ -4,7 +4,8 @@ from dataclasses import field from enum import Enum, auto -from redux import BaseAction, BaseCombineReducerState, BaseEvent, Immutable +from immutable import Immutable +from redux import BaseAction, BaseCombineReducerState, BaseEvent class DockerStatus(Enum): diff --git a/ubo_app/store/services/ip.py b/ubo_app/store/services/ip.py index 0796acab..7028fe27 100644 --- a/ubo_app/store/services/ip.py +++ b/ubo_app/store/services/ip.py @@ -3,7 +3,8 @@ from typing import Sequence -from redux import BaseAction, BaseEvent, Immutable +from immutable import Immutable +from redux import BaseAction, BaseEvent class IpAction(BaseAction): diff --git a/ubo_app/store/services/notifications.py b/ubo_app/store/services/notifications.py index fec3b463..1216eab5 100644 --- a/ubo_app/store/services/notifications.py +++ b/ubo_app/store/services/notifications.py @@ -8,7 +8,8 @@ from typing import Sequence from uuid import uuid4 -from redux import BaseAction, BaseEvent, Immutable +from immutable import Immutable +from redux import BaseAction, BaseEvent class Importance(StrEnum): diff --git a/ubo_app/store/services/rgb_ring.py b/ubo_app/store/services/rgb_ring.py index 03f96f55..108509bb 100644 --- a/ubo_app/store/services/rgb_ring.py +++ b/ubo_app/store/services/rgb_ring.py @@ -3,7 +3,8 @@ from typing import TypeAlias -from redux import BaseAction, BaseEvent, Immutable +from immutable import Immutable +from redux import BaseAction, BaseEvent Color: TypeAlias = ( tuple[float | int, float | int, float | int] diff --git a/ubo_app/store/services/sensors.py b/ubo_app/store/services/sensors.py index 966ee5da..435cd7e6 100644 --- a/ubo_app/store/services/sensors.py +++ b/ubo_app/store/services/sensors.py @@ -4,7 +4,8 @@ from enum import Enum, auto from typing import TYPE_CHECKING, Generic, Sequence, TypeVar -from redux import BaseAction, Immutable +from immutable import Immutable +from redux import BaseAction if TYPE_CHECKING: from datetime import datetime diff --git a/ubo_app/store/services/sound.py b/ubo_app/store/services/sound.py index 8be62987..0fb8e2f4 100644 --- a/ubo_app/store/services/sound.py +++ b/ubo_app/store/services/sound.py @@ -3,7 +3,8 @@ from enum import StrEnum -from redux import BaseAction, BaseEvent, Immutable +from immutable import Immutable +from redux import BaseAction, BaseEvent class SoundDevice(StrEnum): diff --git a/ubo_app/store/services/wifi.py b/ubo_app/store/services/wifi.py index 0c35b299..85073feb 100644 --- a/ubo_app/store/services/wifi.py +++ b/ubo_app/store/services/wifi.py @@ -4,7 +4,8 @@ from enum import StrEnum from typing import Sequence -from redux import BaseAction, BaseEvent, Immutable +from immutable import Immutable +from redux import BaseAction, BaseEvent class WiFiType(StrEnum): diff --git a/ubo_app/store/status_icons/__init__.py b/ubo_app/store/status_icons/__init__.py index 0c6adbef..ea1f511b 100644 --- a/ubo_app/store/status_icons/__init__.py +++ b/ubo_app/store/status_icons/__init__.py @@ -3,7 +3,8 @@ from typing import Sequence -from redux import BaseAction, Immutable +from immutable import Immutable +from redux import BaseAction class IconState(Immutable): diff --git a/ubo_app/store/update_manager/reducer.py b/ubo_app/store/update_manager/reducer.py index 8b541655..83de6fc3 100644 --- a/ubo_app/store/update_manager/reducer.py +++ b/ubo_app/store/update_manager/reducer.py @@ -54,14 +54,17 @@ def reducer( latest_version=action.latest_version, ) version_comparison = 1 - with contextlib.suppress( - ValueError, - ): # ValueError happens for alpha/beta versions - version_comparison = semver.compare( + with contextlib.suppress(ValueError): + latest_version = semver.Version.parse( action.latest_version, + optional_minor_and_patch=True, + ) + current_version = semver.Version.parse( action.current_version, + optional_minor_and_patch=True, ) - if version_comparison == 1: + version_comparison = latest_version.compare(current_version) + if version_comparison > 0: return CompleteReducerResult( state=state, actions=[ diff --git a/ubo_app/system/system_manager/__main__.py b/ubo_app/system/system_manager/__main__.py index 09d41d8e..c3fdbd68 100644 --- a/ubo_app/system/system_manager/__main__.py +++ b/ubo_app/system/system_manager/__main__.py @@ -17,7 +17,7 @@ from ubo_app.logging import add_file_handler, add_stdout_handler, get_logger from ubo_app.system.system_manager.led import LEDManager -SOCKET_PATH = Path(os.getenv('RUNTIME_DIRECTORY', '/run/ubo')).joinpath( +SOCKET_PATH = Path(os.environ.get('RUNTIME_DIRECTORY', '/run/ubo')).joinpath( 'system_manager.sock', ) # The order of the pixel colors - RGB or GRB. diff --git a/ubo_app/utils/__init__.py b/ubo_app/utils/__init__.py index 7c5b022e..a9fadd0b 100644 --- a/ubo_app/utils/__init__.py +++ b/ubo_app/utils/__init__.py @@ -1,45 +1,4 @@ # ruff: noqa: D100, D101, D102, D103, D104, D107 from pathlib import Path -from headless_kivy_pi.constants import BYTES_PER_PIXEL - -from ubo_app.utils.fake import Fake - IS_RPI = Path('/etc/rpi-issue').exists() -if not IS_RPI: - import sys - - sys.modules['RPi'] = Fake() - -from RPi import GPIO # pyright: ignore [reportMissingModuleSource] # noqa: E402 - - -def initialize_board() -> None: - if not IS_RPI: - return - - GPIO.setmode(GPIO.BCM) - GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) - - -def turn_off_screen() -> None: - if not IS_RPI: - return - - GPIO.setup(26, GPIO.OUT) - GPIO.output(26, GPIO.LOW) - - from headless_kivy_pi import HeadlessWidget - - display = HeadlessWidget._display # noqa: SLF001 - data = [0] * HeadlessWidget.width * HeadlessWidget.height * BYTES_PER_PIXEL - display._block(0, 0, HeadlessWidget.width - 1, HeadlessWidget.height - 1, data) # noqa: SLF001 - - -def turn_on_screen() -> None: - if not IS_RPI: - return - - GPIO.setmode(GPIO.BCM) - GPIO.setup(26, GPIO.OUT) - GPIO.output(26, GPIO.HIGH) diff --git a/ubo_app/utils/fake.py b/ubo_app/utils/fake.py index 4ef47862..5d39c77f 100644 --- a/ubo_app/utils/fake.py +++ b/ubo_app/utils/fake.py @@ -22,10 +22,10 @@ def __getattr__(self: Fake, attr: str) -> Fake | str: extra={'attr': attr}, ) if attr == '__file__': - return '' + return 'fake' return self - def __getitem__(self: Fake, key: str) -> Fake: + def __getitem__(self: Fake, key: object) -> Fake: logger.verbose( 'Accessing fake item of a `Fake` instance', extra={'key': key}, @@ -74,6 +74,9 @@ def __mro_entries__(self: Fake, bases: tuple[type[Fake]]) -> tuple[type[Fake]]: ) return (cast(type, self),) + def __len__(self: Fake) -> int: + return 1 + def __index__(self: Fake) -> int: return 1 @@ -83,5 +86,11 @@ def __contains__(self: Fake, _: object) -> bool: def __eq__(self: Fake, _: object) -> bool: return True + def __ne__(self: Fake, _: object) -> bool: + return False + + def __str__(self: Fake) -> str: + return 'Fake' + def __repr__(self: Fake) -> str: return 'Fake' diff --git a/ubo_app/utils/hardware.py b/ubo_app/utils/hardware.py new file mode 100644 index 00000000..7b5ad005 --- /dev/null +++ b/ubo_app/utils/hardware.py @@ -0,0 +1,47 @@ +# pyright: reportMissingModuleSource=false +# ruff: noqa: D100, D101, D102, D103, D104, D107 +from ubo_app.utils import IS_RPI + + +def initialize_board() -> None: + if not IS_RPI: + return + + from RPi import GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + + +def turn_off_screen() -> None: + if not IS_RPI: + return + from headless_kivy_pi import HeadlessWidget + from headless_kivy_pi.constants import BYTES_PER_PIXEL + from RPi import GPIO + + GPIO.setup(26, GPIO.OUT) + GPIO.output(26, GPIO.LOW) + + display = HeadlessWidget._display # noqa: SLF001 + if not display: + return + data = [0] * HeadlessWidget.width * HeadlessWidget.height * BYTES_PER_PIXEL + display._block( # noqa: SLF001 + 0, + 0, + HeadlessWidget.width - 1, + HeadlessWidget.height - 1, + data, + ) + + +def turn_on_screen() -> None: + if not IS_RPI: + return + + from RPi import GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setup(26, GPIO.OUT) + GPIO.output(26, GPIO.HIGH)