diff --git a/.github/workflows/integration_delivery.yml b/.github/workflows/integration_delivery.yml index 2d606f2b..30c27eb2 100644 --- a/.github/workflows/integration_delivery.yml +++ b/.github/workflows/integration_delivery.yml @@ -16,6 +16,11 @@ jobs: - uses: actions/checkout@v4 name: Checkout + - name: System Dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y portaudio19-dev + - name: Load Cached Poetry id: cached-poetry uses: actions/cache@v4 @@ -131,7 +136,7 @@ jobs: - name: Run Tests run: | mkdir -p $HOME/.kivy/mods - poetry run poe test --make-screenshots --cov-report=xml --cov-report=html -n auto --log-level=DEBUG + POETRY_VIRTUALENVS_OPTIONS_SYSTEM_SITE_PACKAGES=true poetry run poe test --make-screenshots --cov-report=xml --cov-report=html -n auto --log-level=DEBUG - name: Collect Window Screenshots uses: actions/upload-artifact@v4 @@ -265,7 +270,7 @@ jobs: strategy: fail-fast: false matrix: - suffix: ['lite', '', 'full'] + suffix: ['lite', ''] steps: - run: echo Building amd64-${{ matrix.suffix }} image @@ -404,14 +409,6 @@ jobs: name: ubo_app-${{ needs.build.outputs.version }}-bookworm-arm64.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 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4051a037..5efe89f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ - build(packer): mount the first partition of the image in `/boot/firmware` instead of `/boot` to be compatible with the new linux kernel - ci(github): download and cache images as it is the slowest part of the build +- feat(sound): `SoundPlayAudioEvent` action for playing an audio sample with type + of `Sequence[int]` +- feat(voice): add new service voice with `VoiceReadTextAction`, it uses orca service + from picovocie to read text with human voice +- feat(notification): read the extra information of the notification when opened +- feat(ssh): force password change after first login for temporarily created accounts ## Version 0.12.6 diff --git a/poetry.lock b/poetry.lock index e248ef89..84b78d6b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -126,13 +126,13 @@ typing-extensions = ">=4.0,<5.0" [[package]] name = "adafruit-circuitpython-requests" -version = "3.2.4" +version = "3.2.5" description = "A requests-like library for web interfacing" optional = false python-versions = "*" files = [ - {file = "adafruit-circuitpython-requests-3.2.4.tar.gz", hash = "sha256:2125010f5526a2e2240ef937bd0003d8b1482a072df36db38d9dc9c0eb654e36"}, - {file = "adafruit_circuitpython_requests-3.2.4-py3-none-any.whl", hash = "sha256:52043ccd331e3ea109184ce1f56a5c5b34a37f057ebb5a33715a67f91d173ef9"}, + {file = "adafruit_circuitpython_requests-3.2.5-py3-none-any.whl", hash = "sha256:25f789bac9f0568c289585418e52abeee7cd37c06c2fb58780aeb62022faff3e"}, + {file = "adafruit_circuitpython_requests-3.2.5.tar.gz", hash = "sha256:971d7fe96b032c8f7a92ab6aaa1997e3c920171f3b935ae76b2c6b8af9c8929d"}, ] [package.dependencies] @@ -211,87 +211,87 @@ files = [ [[package]] name = "aiohttp" -version = "3.9.4" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89"}, - {file = "aiohttp-3.9.4-cp310-cp310-win32.whl", hash = "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926"}, - {file = "aiohttp-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e"}, - {file = "aiohttp-3.9.4-cp311-cp311-win32.whl", hash = "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f"}, - {file = "aiohttp-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53"}, - {file = "aiohttp-3.9.4-cp312-cp312-win32.whl", hash = "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36"}, - {file = "aiohttp-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c3770365675f6be220032f6609a8fbad994d6dcf3ef7dbcf295c7ee70884c9af"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:305edae1dea368ce09bcb858cf5a63a064f3bff4767dec6fa60a0cc0e805a1d3"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f121900131d116e4a93b55ab0d12ad72573f967b100e49086e496a9b24523ea"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b71e614c1ae35c3d62a293b19eface83d5e4d194e3eb2fabb10059d33e6e8cbf"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419f009fa4cfde4d16a7fc070d64f36d70a8d35a90d71aa27670bba2be4fd039"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b39476ee69cfe64061fd77a73bf692c40021f8547cda617a3466530ef63f947"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b33f34c9c7decdb2ab99c74be6443942b730b56d9c5ee48fb7df2c86492f293c"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c78700130ce2dcebb1a8103202ae795be2fa8c9351d0dd22338fe3dac74847d9"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:268ba22d917655d1259af2d5659072b7dc11b4e1dc2cb9662fdd867d75afc6a4"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:17e7c051f53a0d2ebf33013a9cbf020bb4e098c4bc5bce6f7b0c962108d97eab"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7be99f4abb008cb38e144f85f515598f4c2c8932bf11b65add0ff59c9c876d99"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d58a54d6ff08d2547656356eea8572b224e6f9bbc0cf55fa9966bcaac4ddfb10"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7673a76772bda15d0d10d1aa881b7911d0580c980dbd16e59d7ba1422b2d83cd"}, - {file = "aiohttp-3.9.4-cp38-cp38-win32.whl", hash = "sha256:e4370dda04dc8951012f30e1ce7956a0a226ac0714a7b6c389fb2f43f22a250e"}, - {file = "aiohttp-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:eb30c4510a691bb87081192a394fb661860e75ca3896c01c6d186febe7c88530"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3"}, - {file = "aiohttp-3.9.4-cp39-cp39-win32.whl", hash = "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b"}, - {file = "aiohttp-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d"}, - {file = "aiohttp-3.9.4.tar.gz", hash = "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -1165,6 +1165,40 @@ files = [ {file = "pulsectl-23.5.2.tar.gz", hash = "sha256:e911d398eaf0539cf3c63b4217357b51a3d1b7e4a50607d1591cf2b49f5d2c6a"}, ] +[[package]] +name = "pvorca" +version = "0.1.4" +description = "Orca Text-to-Speech Engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pvorca-0.1.4-py3-none-any.whl", hash = "sha256:20a6fbd4263543845822199ec61c846abc6c6eefe724e6e9e70a6634c97063b8"}, + {file = "pvorca-0.1.4.tar.gz", hash = "sha256:8413bea22ca0087ce62c22859aebe7b4fcc65e83ee845a1d7cbd4ce608464480"}, +] + +[[package]] +name = "pyaudio" +version = "0.2.14" +description = "Cross-platform audio I/O with PortAudio" +optional = false +python-versions = "*" +files = [ + {file = "PyAudio-0.2.14-cp310-cp310-win32.whl", hash = "sha256:126065b5e82a1c03ba16e7c0404d8f54e17368836e7d2d92427358ad44fefe61"}, + {file = "PyAudio-0.2.14-cp310-cp310-win_amd64.whl", hash = "sha256:2a166fc88d435a2779810dd2678354adc33499e9d4d7f937f28b20cc55893e83"}, + {file = "PyAudio-0.2.14-cp311-cp311-win32.whl", hash = "sha256:506b32a595f8693811682ab4b127602d404df7dfc453b499c91a80d0f7bad289"}, + {file = "PyAudio-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:bbeb01d36a2f472ae5ee5e1451cacc42112986abe622f735bb870a5db77cf903"}, + {file = "PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b"}, + {file = "PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3"}, + {file = "PyAudio-0.2.14-cp38-cp38-win32.whl", hash = "sha256:858caf35b05c26d8fc62f1efa2e8f53d5fa1a01164842bd622f70ddc41f55000"}, + {file = "PyAudio-0.2.14-cp38-cp38-win_amd64.whl", hash = "sha256:2dac0d6d675fe7e181ba88f2de88d321059b69abd52e3f4934a8878e03a7a074"}, + {file = "PyAudio-0.2.14-cp39-cp39-win32.whl", hash = "sha256:f745109634a7c19fa4d6b8b7d6967c3123d988c9ade0cd35d4295ee1acdb53e9"}, + {file = "PyAudio-0.2.14-cp39-cp39-win_amd64.whl", hash = "sha256:009f357ee5aa6bc8eb19d69921cd30e98c42cddd34210615d592a71d09c4bd57"}, + {file = "PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87"}, +] + +[package.extras] +test = ["numpy"] + [[package]] name = "pyftdi" version = "0.55.4" @@ -1257,13 +1291,13 @@ files = [ [[package]] name = "pyright" -version = "1.1.358" +version = "1.1.359" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.358-py3-none-any.whl", hash = "sha256:0995b6a95eb11bd26f093cd5dee3d5e7258441b1b94d4a171b5dc5b79a1d4f4e"}, - {file = "pyright-1.1.358.tar.gz", hash = "sha256:185524a8d52f6f14bbd3b290b92ad905f25b964dddc9e7148aad760bd35c9f60"}, + {file = "pyright-1.1.359-py3-none-any.whl", hash = "sha256:5582777be7eab73512277ac7da7b41e15bc0737f488629cb9babd96e0769be61"}, + {file = "pyright-1.1.359.tar.gz", hash = "sha256:f0eab50f3dafce8a7302caeafd6a733f39901a2bf5170bb23d77fd607c8a8dbc"}, ] [package.dependencies] @@ -1376,18 +1410,18 @@ pytest = ">=7.0.0" [[package]] name = "pytest-xdist" -version = "3.5.0" +version = "3.6.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"}, - {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"}, + {file = "pytest_xdist-3.6.0-py3-none-any.whl", hash = "sha256:958e08f38472e1b3a83450d8d3e682e90fdbffee39a97dd0f27185a3bd9074d1"}, + {file = "pytest_xdist-3.6.0.tar.gz", hash = "sha256:2bf346fb1f1481c8d255750f80bc1dfb9fb18b9ad5286ead0b741b6fd56d15b7"}, ] [package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" +execnet = ">=2.1" +pytest = ">=7.0.0" [package.extras] psutil = ["psutil (>=3.0)"] @@ -1556,28 +1590,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.7" +version = "0.4.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, - {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, - {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, - {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, - {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, + {file = "ruff-0.4.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2d9ef6231e3fbdc0b8c72404a1a0c46fd0dcea84efca83beb4681c318ea6a953"}, + {file = "ruff-0.4.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9485f54a7189e6f7433e0058cf8581bee45c31a25cd69009d2a040d1bd4bfaef"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2921ac03ce1383e360e8a95442ffb0d757a6a7ddd9a5be68561a671e0e5807e"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eec8d185fe193ad053eda3a6be23069e0c8ba8c5d20bc5ace6e3b9e37d246d3f"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa27d9d72a94574d250f42b7640b3bd2edc4c58ac8ac2778a8c82374bb27984"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f1ee41580bff1a651339eb3337c20c12f4037f6110a36ae4a2d864c52e5ef954"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0926cefb57fc5fced629603fbd1a23d458b25418681d96823992ba975f050c2b"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6e37f2e3cd74496a74af9a4fa67b547ab3ca137688c484749189bf3a686ceb"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd703a5975ac1998c2cc5e9494e13b28f31e66c616b0a76e206de2562e0843c"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b92f03b4aa9fa23e1799b40f15f8b95cdc418782a567d6c43def65e1bbb7f1cf"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c859f294f8633889e7d77de228b203eb0e9a03071b72b5989d89a0cf98ee262"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b34510141e393519a47f2d7b8216fec747ea1f2c81e85f076e9f2910588d4b64"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6e68d248ed688b9d69fd4d18737edcbb79c98b251bba5a2b031ce2470224bdf9"}, + {file = "ruff-0.4.1-py3-none-win32.whl", hash = "sha256:b90506f3d6d1f41f43f9b7b5ff845aeefabed6d2494307bc7b178360a8805252"}, + {file = "ruff-0.4.1-py3-none-win_amd64.whl", hash = "sha256:c7d391e5936af5c9e252743d767c564670dc3889aff460d35c518ee76e4b26d7"}, + {file = "ruff-0.4.1-py3-none-win_arm64.whl", hash = "sha256:a1eaf03d87e6a7cd5e661d36d8c6e874693cb9bc3049d110bc9a97b350680c43"}, + {file = "ruff-0.4.1.tar.gz", hash = "sha256:d592116cdbb65f8b1b7e2a2b48297eb865f6bdc20641879aa9d7b9c11d86db79"}, ] [[package]] @@ -1743,13 +1777,13 @@ files = [ [[package]] name = "ubo-gui" -version = "0.10.6" +version = "0.10.7" description = "GUI sdk for Ubo Pod" optional = true python-versions = "<4.0,>=3.11" files = [ - {file = "ubo_gui-0.10.6-py3-none-any.whl", hash = "sha256:966f3e74376d0dd403dadfe6245e74f39d91c352b4c0ec02de80162a1179402d"}, - {file = "ubo_gui-0.10.6.tar.gz", hash = "sha256:a00d8b2297759e74dfcef43b2303692cef5105a00f6584201fb9d8ff78777e00"}, + {file = "ubo_gui-0.10.7-py3-none-any.whl", hash = "sha256:1424c46ea53e2575dddbf2e0425909e6f39653af81c20c8625e4fb7a40101dd4"}, + {file = "ubo_gui-0.10.7.tar.gz", hash = "sha256:1316a74f4669e6c0089db388c0e30012c07588dc24e14f9d0ca5970188e2d9e6"}, ] [package.dependencies] @@ -1891,4 +1925,4 @@ dev = ["ubo-gui", "ubo-gui"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "3b0694ec8a29e29adca36209994a05056d796490d6aea1a47cfe067e79c72066" +content-hash = "5d2fff2ac70e67fe711cd33c266a0590f2e47c9f1216fc9dcb03a41087a30b40" diff --git a/pyproject.toml b/pyproject.toml index e4c557b6..707aeac8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,10 +18,10 @@ priority = "primary" python = "^3.11" psutil = "^5.9.8" ubo-gui = [ - { version = "^0.10.6", markers = "extra=='default'", extras = [ + { version = "^0.10.7", markers = "extra=='default'", extras = [ "default", ] }, - { version = "^0.10.6", markers = "extra=='dev'", extras = [ + { version = "^0.10.7", markers = "extra=='dev'", extras = [ "dev", ] }, ] @@ -39,22 +39,24 @@ adafruit-circuitpython-veml7700 = "^1.1.22" docker = "^7.0.0" python-dotenv = "^1.0.1" sentry-sdk = "^1.43.0" +pvorca = "^0.1.4" [tool.poetry.group.dev] optional = true [tool.poetry.group.dev.dependencies] poethepoet = "^0.24.4" -pyright = "^1.1.358" +pyright = "^1.1.359" pytest = "^8.0.0" pytest-asyncio = "^0.23.5.post1" pytest-cov = "^4.1.0" pytest-timeout = "^2.3.1" pytest-xdist = "^3.5.0" -ruff = "^0.3.7" +ruff = "^0.4.1" tenacity = "^8.2.3" toml = "^0.10.2" pytest-mock = "^3.14.0" +pyaudio = { version = "^0.2.14", markers = "platform_machine!='aarch64'" } [tool.poetry.extras] default = ["ubo-gui"] @@ -80,6 +82,7 @@ args = [ { name = "deps", type = "boolean" }, { name = "run", type = "boolean" }, { name = "bootstrap", type = "boolean" }, + { name = "env", type = "boolean" }, ] cmd = "scripts/deploy.sh" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 02c1045c..7c395f6d 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -10,19 +10,22 @@ LATEST_VERSION=$(basename $(ls -rt dist/*.whl | tail -n 1)) deps=${deps:-"False"} bootstrap=${bootstrap:-"False"} run=${run:-"False"} +env=${env:-"False"} function run_on_pod() { if [ $# -lt 1 ] || [ $# -gt 2 ]; then echo "Usage: run_on_pod [is_root]" - exit 1 + return 1 fi if [ $# -eq 1 ]; then ssh ubo-development-pod "sudo XDG_RUNTIME_DIR=/run/user/\$(id -u ubo) -u ubo bash -c 'source \$HOME/.profile && source /etc/profile && source /opt/ubo/env/bin/activate && $1'" - exit 0 + return 0 fi if [ "$2" == "root" ]; then ssh ubo-development-pod "sudo bash -c '$1'" - exit 0 + return 0 + else + return 1 fi } @@ -35,8 +38,10 @@ run_on_pod "pip install --upgrade --force-reinstal --no-deps /tmp/$LATEST_VERSIO test "$bootstrap" == "True" && run_on_pod "/opt/ubo/env/bin/bootstrap; systemctl restart ubo-system.service" "root" +test "$env" == "True" && + scp ubo_app/.dev.env ubo-development-pod:/tmp/ && + run_on_pod "chown ubo:ubo /tmp/.dev.env" "root" && + run_on_pod "mv /tmp/.dev.env /opt/ubo/env/lib/python3.11/site-packages/ubo_app/" + test "$run" == "True" && run_on_pod "systemctl --user restart ubo-app.service" - -test "$env" == "True" && - scp ubo_app/.dev.env /opt/ubo/env/lib/python3.11/site-packages/ubo_app/.env diff --git a/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-006.jsonc b/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-006.jsonc index 5258853a..0a5f1127 100644 --- a/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-006.jsonc +++ b/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-006.jsonc @@ -162,7 +162,7 @@ "Main", "Settings", "WiFi Settings", - "8d723104f77383c13458a748e9bb17bc" + "85776e9add84f39e71545a137a1d5006" ] }, "status_icons": { diff --git a/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-007.jsonc b/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-007.jsonc index c3103faf..60c45251 100644 --- a/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-007.jsonc +++ b/tests/end_to_end/results/test_wireless_flow/wireless_flow/store-007.jsonc @@ -162,7 +162,7 @@ "Main", "Settings", "WiFi Settings", - "8d723104f77383c13458a748e9bb17bc" + "85776e9add84f39e71545a137a1d5006" ] }, "status_icons": { diff --git a/tests/fixtures/load_services.py b/tests/fixtures/load_services.py index e071b8c0..8bc99136 100644 --- a/tests/fixtures/load_services.py +++ b/tests/fixtures/load_services.py @@ -25,6 +25,8 @@ class LoadServices(Protocol): def __call__( self: LoadServices, service_ids: Sequence[str], + *, + timeout: float | None = None, ) -> None: ... @overload @@ -33,6 +35,7 @@ def __call__( service_ids: Sequence[str], *, run_async: Literal[True], + timeout: float | None = None, ) -> Coroutine[None, None, None]: ... @@ -45,12 +48,13 @@ def load_services_and_wait( service_ids: Sequence[str], *, run_async: bool = False, + timeout: float | None = None, ) -> Coroutine[None, None, None] | None: from ubo_app.load_services import load_services load_services(service_ids) - @wait_for(run_async=cast(Literal[True], run_async)) + @wait_for(run_async=cast(Literal[True], run_async), timeout=timeout) def check() -> None: for service_id in service_ids: assert any( diff --git a/tests/integration/results/test_services/all_services_register/store-000.jsonc b/tests/integration/results/test_services/all_services_register/store-000.jsonc index a7e2aeea..e5f9a16d 100644 --- a/tests/integration/results/test_services/all_services_register/store-000.jsonc +++ b/tests/integration/results/test_services/all_services_register/store-000.jsonc @@ -7,7 +7,7 @@ "queue": [] }, "docker": { - "_id": "8d723104f77383c13458a748e9bb17bc", + "_id": "85776e9add84f39e71545a137a1d5006", "home_assistant": { "container_ip": null, "docker_id": null, @@ -251,6 +251,39 @@ "is_short": false, "label": "SSH" }, + { + "background_color": "#68B7FF", + "color": [ + 1, + 1, + 1, + 1 + ], + "icon": null, + "is_short": false, + "label": "Voice", + "sub_menu": { + "heading": "󰔊", + "items": [ + { + "action": "", + "background_color": "#68B7FF", + "color": [ + 1, + 1, + 1, + 1 + ], + "icon": "󰐲", + "is_short": false, + "label": "Access Token" + } + ], + "placeholder": null, + "sub_heading": "Set the access token for picovoice service", + "title": "Voice Settings" + } + }, { "background_color": "#68B7FF", "color": [ @@ -425,6 +458,7 @@ "serial_number": "", "update_status": "up_to_date" }, + "voice": {}, "wifi": { "connections": [], "current_connection": null, diff --git a/tests/integration/test_services.py b/tests/integration/test_services.py index 333b86e1..e26d5cd9 100644 --- a/tests/integration/test_services.py +++ b/tests/integration/test_services.py @@ -23,6 +23,7 @@ 'sensors', 'docker', 'ssh', + 'voice', ] @@ -40,7 +41,7 @@ async def test_all_services_register( app = MenuApp() app_context.set_app(app) - load_services(ALL_SERVICES_IDS) + load_services(ALL_SERVICES_IDS, timeout=10) await stability() store_snapshot.take() window_snapshot.take() diff --git a/tests/monkeypatch.py b/tests/monkeypatch.py index 29525962..82e92cb7 100644 --- a/tests/monkeypatch.py +++ b/tests/monkeypatch.py @@ -221,6 +221,8 @@ async def fake_create_subprocess_exec( @pytest.fixture(autouse=True) def _monkeypatch(monkeypatch: pytest.MonkeyPatch) -> None: """Mock external resources.""" + from ubo_app.utils.fake import Fake + random.seed(0) tracemalloc.start() @@ -229,6 +231,8 @@ def _monkeypatch(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr('importlib.metadata.version', lambda _: '0.0.0') monkeypatch.setattr('ubo_app.constants.STORE_GRACE_TIME', 0.1) + sys.modules['pyaudio'] = Fake() + _monkeypatch_socket(monkeypatch) _monkeypatch_psutil(monkeypatch) _monkeypatch_docker(monkeypatch) diff --git a/ubo_app/constants.py b/ubo_app/constants.py index 1dc7a3f7..170a3414 100644 --- a/ubo_app/constants.py +++ b/ubo_app/constants.py @@ -28,6 +28,8 @@ # each time a UUID is generated. DEBUG_MODE_TEST_UUID = strtobool(os.environ.get('UBO_DEBUG_TEST_UUID', 'False')) == 1 +PICOVOICE_API_KEY = os.environ.get('PICOVOICE_API_KEY', None) + DEBUG_MODE_DOCKER = strtobool(os.environ.get('UBO_DEBUG_DOCKER', 'False')) == 1 DOCKER_PREFIX = os.environ.get('UBO_DOCKER_PREFIX', '') DOCKER_INSTALLATION_LOCK_FILE = Path('/var/run/ubo/docker_installation.lock') diff --git a/ubo_app/menu_app/menu_central.py b/ubo_app/menu_app/menu_central.py index a282d109..dded5bbc 100644 --- a/ubo_app/menu_app/menu_central.py +++ b/ubo_app/menu_app/menu_central.py @@ -22,6 +22,7 @@ NotificationsClearAction, NotificationsDisplayEvent, ) +from ubo_app.store.services.voice import VoiceReadTextAction from .home_page import HomePage @@ -130,7 +131,12 @@ def display_notification( ) application.bind( - on_info=lambda _: self.menu_widget.open_application(info_application), + on_info=lambda _: ( + dispatch( + VoiceReadTextAction(text=notification.extra_information or ''), + ), + self.menu_widget.open_application(info_application), + ), ) self.menu_widget.open_application(application) diff --git a/ubo_app/services/000-sound/audio_manager.py b/ubo_app/services/000-sound/audio_manager.py index d1ae2c64..bc663d33 100644 --- a/ubo_app/services/000-sound/audio_manager.py +++ b/ubo_app/services/000-sound/audio_manager.py @@ -6,16 +6,22 @@ import asyncio import contextlib import math +import struct import time import wave +from typing import TYPE_CHECKING import alsaaudio import pulsectl import pyaudio from ubo_app.logging import logger +from ubo_app.utils import IS_RPI from ubo_app.utils.async_ import create_task +if TYPE_CHECKING: + from collections.abc import Sequence + CHUNK_SIZE = 1024 @@ -78,6 +84,8 @@ async def initialize_audio() -> None: async def restart_pulse_audio(self: AudioManager) -> None: """Restart pulseaudio.""" + if not IS_RPI: + return process = await asyncio.create_subprocess_exec( '/usr/bin/env', 'pulseaudio', @@ -95,7 +103,13 @@ def find_respeaker_index(self: AudioManager) -> int: """Find the index of the ReSpeaker device.""" for index in range(self.pyaudio.get_device_count()): info = self.pyaudio.get_device_info_by_index(index) - if not isinstance(info['name'], int | float) and 'wm8960' in info['name']: + if ( + not isinstance(info['name'], int | float) + and 'wm8960' in info['name'] + or not IS_RPI + and isinstance(info['maxOutputChannels'], int) + and info['maxOutputChannels'] > 0 + ): logger.debug('ReSpeaker found at index', extra={'index': index}) logger.debug('Device Info', extra={'info': info}) return index @@ -118,7 +132,39 @@ def close_stream(self: AudioManager) -> None: self.stream.close() self.stream = None - def play(self: AudioManager, filename: str) -> None: + def initialize_playback( + self: AudioManager, + *, + channels: int, + rate: int, + width: int, + ) -> pyaudio.PyAudio.Stream: + """Initialize the playback stream. + + Parameters + ---------- + channels : int + Number of channels + + rate : int + Frame rate of the audio + + width : int + Sample width of the audio + + """ + self.close_stream() + self.is_playing = True + self.stream = self.pyaudio.open( + format=self.pyaudio.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + output_device_index=self.find_respeaker_index(), + ) + return self.stream + + def play_file(self: AudioManager, filename: str) -> None: """Play a waveform audio file. Parameters @@ -128,23 +174,61 @@ def play(self: AudioManager, filename: str) -> None: """ # open the file for reading. - self.close_stream() - logger.info('Opening audio file for playback', extra={'filename_': filename}) try: with wave.open(filename, 'rb') as wf: - self.is_playing = True - self.stream = self.pyaudio.open( - format=self.pyaudio.get_format_from_width(wf.getsampwidth()), + stream = self.initialize_playback( channels=wf.getnchannels(), rate=wf.getframerate(), - output=True, - output_device_index=self.find_respeaker_index(), + width=wf.getsampwidth(), ) data = wf.readframes(CHUNK_SIZE) - while data and not self.should_stop and self.stream.is_active(): - self.stream.write(data) + while data and not self.should_stop and stream.is_active(): + stream.write(data) data = wf.readframes(CHUNK_SIZE) + stream.stop_stream() + except Exception: + create_task(self.restart_pulse_audio()) + logger.exception('Something went wrong while playing an audio file') + finally: + self.is_playing = False + self.close_stream() + + def play_sequence( + self: AudioManager, + sequence: Sequence[int], + *, + channels: int, + rate: int, + width: int, + ) -> None: + """Play a sequence of audio. + + Parameters + ---------- + sequence : Sequence[int] + Audio as a sequence of 16-bit linearly-encoded integers. + + channels : int + Number of channels + + rate : int + Frame rate of the audio + + width : int + Sample width of the audio + + """ + try: + stream = self.initialize_playback( + channels=channels, + rate=rate, + width=width, + ) + data = b''.join(struct.pack('h', sample) for sample in sequence) + + stream.write(data) + except Exception: create_task(self.restart_pulse_audio()) logger.exception('Something went wrong while playing an audio file') diff --git a/ubo_app/services/000-sound/reducer.py b/ubo_app/services/000-sound/reducer.py index 02d354fd..1fef24d2 100644 --- a/ubo_app/services/000-sound/reducer.py +++ b/ubo_app/services/000-sound/reducer.py @@ -16,6 +16,9 @@ SoundAction, SoundChangeVolumeAction, SoundDevice, + SoundEvent, + SoundPlayAudioAction, + SoundPlayAudioEvent, SoundPlayChimeAction, SoundPlayChimeEvent, SoundSetMuteStatusAction, @@ -31,7 +34,7 @@ def reducer( state: SoundState | None, action: Action, -) -> ReducerResult[SoundState, Action, SoundPlayChimeEvent]: +) -> ReducerResult[SoundState, Action, SoundEvent]: if state is None: if isinstance(action, InitAction): return SoundState( @@ -107,4 +110,16 @@ def reducer( SoundPlayChimeEvent(name=action.name), ], ) + elif isinstance(action, SoundPlayAudioAction): + return CompleteReducerResult( + state=state, + events=[ + SoundPlayAudioEvent( + sample=action.sample, + channels=action.channels, + rate=action.rate, + width=action.width, + ), + ], + ) return state diff --git a/ubo_app/services/000-sound/setup.py b/ubo_app/services/000-sound/setup.py index 0ef52b04..2be363a3 100644 --- a/ubo_app/services/000-sound/setup.py +++ b/ubo_app/services/000-sound/setup.py @@ -7,11 +7,13 @@ from constants import SOUND_MIC_STATE_ICON_ID, SOUND_MIC_STATE_ICON_PRIORITY from ubo_app.store import autorun, dispatch, subscribe_event -from ubo_app.store.services.sound import SoundPlayChimeEvent +from ubo_app.store.services.sound import SoundPlayAudioEvent, SoundPlayChimeEvent from ubo_app.store.status_icons import StatusIconsRegisterAction def init_service() -> None: + audio_manager = AudioManager() + dispatch( StatusIconsRegisterAction( icon='󰍭', @@ -20,8 +22,6 @@ def init_service() -> None: ), ) - audio_manager = AudioManager() - @autorun(lambda state: state.sound.playback_volume) def _(volume: float) -> None: audio_manager.set_playback_volume(volume) @@ -36,7 +36,17 @@ def _(is_mute: bool) -> None: # noqa: FBT001 subscribe_event( SoundPlayChimeEvent, - lambda event: audio_manager.play( + lambda event: audio_manager.play_file( Path(__file__).parent.joinpath(f'sounds/{event.name}.wav').as_posix(), ), ) + + subscribe_event( + SoundPlayAudioEvent, + lambda event: audio_manager.play_sequence( + event.sample, + channels=event.channels, + rate=event.rate, + width=event.width, + ), + ) diff --git a/ubo_app/services/000-sound/ubo_handle.py b/ubo_app/services/000-sound/ubo_handle.py index 9d717b9f..f20fd6aa 100644 --- a/ubo_app/services/000-sound/ubo_handle.py +++ b/ubo_app/services/000-sound/ubo_handle.py @@ -12,7 +12,6 @@ def setup(service: Service) -> None: from setup import init_service service.register_reducer(reducer) - init_service() diff --git a/ubo_app/services/030-ip/ubo_handle.py b/ubo_app/services/030-ip/ubo_handle.py index ee053ea9..5deef0b4 100644 --- a/ubo_app/services/030-ip/ubo_handle.py +++ b/ubo_app/services/030-ip/ubo_handle.py @@ -12,7 +12,6 @@ async def setup(service: Service) -> None: from setup import init_service service.register_reducer(reducer) - await init_service() diff --git a/ubo_app/services/050-ssh/setup.py b/ubo_app/services/050-ssh/setup.py index f98591b5..a6b66957 100644 --- a/ubo_app/services/050-ssh/setup.py +++ b/ubo_app/services/050-ssh/setup.py @@ -95,6 +95,9 @@ async def act() -> None: icon='', display_type=NotificationDisplayType.STICKY, color=DANGER_COLOR, + extra_information='Note that in order to make ssh work, we had \ +to make sure password authentication for ssh server is enabled, you may want to \ +disable it later.', ), ), ) @@ -111,8 +114,7 @@ async def act() -> None: display_type=NotificationDisplayType.STICKY, extra_information='Note that in order to make things work for you, \ we had to make sure password authentication for ssh server is enabled, you may want to \ -disable it later. Clearing all temporary users will disable password authentication too\ -.', +disable it later.', color=SUCCESS_COLOR, ), ), diff --git a/ubo_app/services/090-voice/reducer.py b/ubo_app/services/090-voice/reducer.py new file mode 100644 index 00000000..fbb6dba8 --- /dev/null +++ b/ubo_app/services/090-voice/reducer.py @@ -0,0 +1,35 @@ +# ruff: noqa: D100, D101, D102, D103, D104, D107, N999 +from __future__ import annotations + +from redux import CompleteReducerResult, InitAction, InitializationActionError + +from ubo_app.store.services.voice import ( + VoiceAction, + VoiceEvent, + VoiceReadTextAction, + VoiceState, + VoiceSynthesizeTextEvent, +) + + +def reducer( + state: VoiceState | None, + action: VoiceAction, +) -> VoiceState | CompleteReducerResult[VoiceState, VoiceAction, VoiceEvent]: + if state is None: + if isinstance(action, InitAction): + return VoiceState() + raise InitializationActionError(action) + + if isinstance(action, VoiceReadTextAction): + return CompleteReducerResult( + state=state, + events=[ + VoiceSynthesizeTextEvent( + text=action.text, + speech_rate=action.speech_rate, + ), + ], + ) + + return state diff --git a/ubo_app/services/090-voice/setup.py b/ubo_app/services/090-voice/setup.py new file mode 100644 index 00000000..08756ba4 --- /dev/null +++ b/ubo_app/services/090-voice/setup.py @@ -0,0 +1,82 @@ +"""Implement `init_service` for vocie module.""" + +from __future__ import annotations + +import pvorca +from redux import FinishEvent +from ubo_gui.menu.types import ActionItem, HeadedMenu, SubMenuItem + +from ubo_app.constants import PICOVOICE_API_KEY +from ubo_app.store import dispatch, subscribe_event +from ubo_app.store.main import RegisterSettingAppAction +from ubo_app.store.services.sound import SoundPlayAudioAction +from ubo_app.store.services.voice import VoiceSynthesizeTextEvent +from ubo_app.utils.async_ import create_task +from ubo_app.utils.qrcode import qrcode_input + + +def init_service() -> None: + """Initialize vocie service.""" + orca: pvorca.Orca | None = None + + if PICOVOICE_API_KEY: + orca = pvorca.create(access_key=PICOVOICE_API_KEY) + + def input_access_token() -> None: + async def act() -> None: + nonlocal orca + orca = pvorca.create( + access_key=( + await qrcode_input( + '.*', + prompt='Enter the Picovoice API key', + ) + )[0], + ) + + create_task(act()) + + def synthesize(event: VoiceSynthesizeTextEvent) -> None: + if not orca: + return + audio_sequence = orca.synthesize( + text=event.text, + speech_rate=event.speech_rate, + ) + + dispatch( + SoundPlayAudioAction( + sample=audio_sequence, + channels=1, + rate=orca.sample_rate, + width=2, + ), + ) + + subscribe_event(VoiceSynthesizeTextEvent, synthesize) + + dispatch( + RegisterSettingAppAction( + menu_item=SubMenuItem( + label='Voice', + sub_menu=HeadedMenu( + title='Voice Settings', + heading='󰔊', + sub_heading='Set the access token for picovoice service', + items=[ + ActionItem( + label='Access Token', + icon='󰐲', + action=input_access_token, + ), + ], + ), + ), + ), + ) + + def cleanup() -> None: + if orca: + orca.delete() + + subscribe_event(FinishEvent, cleanup) diff --git a/ubo_app/services/090-voice/ubo_handle.py b/ubo_app/services/090-voice/ubo_handle.py new file mode 100644 index 00000000..f070a645 --- /dev/null +++ b/ubo_app/services/090-voice/ubo_handle.py @@ -0,0 +1,22 @@ +# ruff: noqa: D100, D101, D102, D103, D104, D107, N999 +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ubo_app.services import Service, register + + +def setup(service: Service) -> None: + from reducer import reducer + from setup import init_service + + service.register_reducer(reducer) + init_service() + + +register( + service_id='voice', + label='Voice', + setup=setup, +) diff --git a/ubo_app/setup.py b/ubo_app/setup.py index dcef6031..967a62bb 100644 --- a/ubo_app/setup.py +++ b/ubo_app/setup.py @@ -12,7 +12,6 @@ def setup() -> None: sys.modules['alsaaudio'] = Fake() sys.modules['pulsectl'] = Fake() - sys.modules['pyaudio'] = Fake() sys.modules['sdbus'] = Fake() sys.modules['sdbus_async'] = Fake() sys.modules['sdbus_async.networkmanager'] = Fake() diff --git a/ubo_app/store/__init__.py b/ubo_app/store/__init__.py index 6b4b0ed6..4f0ad336 100644 --- a/ubo_app/store/__init__.py +++ b/ubo_app/store/__init__.py @@ -31,6 +31,7 @@ from ubo_app.store.services.sensors import SensorsAction, SensorsState from ubo_app.store.services.sound import SoundAction, SoundState from ubo_app.store.services.ssh import SSHAction, SSHState +from ubo_app.store.services.voice import VoiceAction, VoiceState from ubo_app.store.services.wifi import WiFiAction, WiFiEvent, WiFiState from ubo_app.store.status_icons import StatusIconsAction, StatusIconsState from ubo_app.store.status_icons.reducer import reducer as status_icons_reducer @@ -69,6 +70,7 @@ class RootState(BaseCombineReducerState): ip: IpState notifications: NotificationsState docker: DockerState + voice: VoiceState class ScreenshotEvent(BaseEvent): ... @@ -89,6 +91,7 @@ class ScreenshotEvent(BaseEvent): ... | NotificationsAction | DockerAction | RgbRingAction + | VoiceAction ) EventType = KeypadEvent | CameraEvent | WiFiEvent | IpEvent | ScreenshotEvent diff --git a/ubo_app/store/services/sound.py b/ubo_app/store/services/sound.py index 7dfe2d6f..e03f59b3 100644 --- a/ubo_app/store/services/sound.py +++ b/ubo_app/store/services/sound.py @@ -2,10 +2,14 @@ from __future__ import annotations from enum import StrEnum +from typing import TYPE_CHECKING from immutable import Immutable from redux import BaseAction, BaseEvent +if TYPE_CHECKING: + from collections.abc import Sequence + class SoundDevice(StrEnum): INPUT = 'Input' @@ -38,6 +42,13 @@ class SoundPlayChimeAction(SoundAction): name: str +class SoundPlayAudioAction(SoundAction): + sample: Sequence[int] + channels: int + rate: int + width: int + + class SoundEvent(BaseEvent): ... @@ -45,6 +56,13 @@ class SoundPlayChimeEvent(SoundEvent): name: str +class SoundPlayAudioEvent(SoundEvent): + sample: Sequence[int] + channels: int + rate: int + width: int + + class SoundState(Immutable): playback_volume: float is_playback_mute: bool diff --git a/ubo_app/store/services/voice.py b/ubo_app/store/services/voice.py new file mode 100644 index 00000000..983ba398 --- /dev/null +++ b/ubo_app/store/services/voice.py @@ -0,0 +1,24 @@ +# ruff: noqa: D100, D101, D102, D103, D104, D107, N999 +from __future__ import annotations + +from immutable import Immutable +from redux import BaseAction, BaseEvent + + +class VoiceAction(BaseAction): ... + + +class VoiceEvent(BaseEvent): ... + + +class VoiceReadTextAction(VoiceAction): + text: str + speech_rate: float | None = None + + +class VoiceSynthesizeTextEvent(VoiceEvent): + text: str + speech_rate: float | None = None + + +class VoiceState(Immutable): ... diff --git a/ubo_app/system/system_manager/create_temporary_ssh_account.sh b/ubo_app/system/system_manager/create_temporary_ssh_account.sh index 828905fa..cf941d0d 100755 --- a/ubo_app/system/system_manager/create_temporary_ssh_account.sh +++ b/ubo_app/system/system_manager/create_temporary_ssh_account.sh @@ -27,6 +27,7 @@ useradd -m -s /bin/bash $USERNAME # Set the password PASSWORD=$(openssl rand -base64 6) echo "${USERNAME}:${PASSWORD}" | chpasswd +passwd --expire $USERNAME > /dev/null printf "${USERNAME}:${PASSWORD}" # Add the user to the sudo group