diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index bd74dc04..00000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Deploy Website -on: - push: - branches: # triggers the workflow on push events to the main branch - - main - workflow_dispatch: # allows you to run the workflow manually -permissions: - contents: read - pages: write - id-token: write -concurrency: - group: "pages" - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Fetch all history for all branches and tags. This is required for the git revision date plugin and git authors plugin. - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install MkDocs and plugins - run: pip install mkdocs mkdocs-mermaid2-plugin mkdocs-git-revision-date-localized-plugin mkdocs-git-authors-plugin - - name: Build with MkDocs - run: mkdocs build - # - name: Check for hard links and symlinks - # run: | - # find site -type l -exec echo "Symlink found: {}" \; -o -type f -links +1 -exec echo "Hard link found: {}" \; - - name: Upload build artifacts - uses: actions/upload-pages-artifact@v3 - with: - name: github-pages - path: site - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - diff --git a/.gitignore b/.gitignore index b9cfee33..992a162d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,224 @@ -site +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + + + +# Custom rules *ignored* -*__pycache__* -.vscode/ *.zip Captured_Frames Debug diff --git a/docs/Asservissement.md b/docs/Asservissement.md deleted file mode 100644 index f9061f2d..00000000 --- a/docs/Asservissement.md +++ /dev/null @@ -1 +0,0 @@ -Page asservissement \ No newline at end of file diff --git a/docs/Camera.md b/docs/Camera.md deleted file mode 100644 index 03a1518e..00000000 --- a/docs/Camera.md +++ /dev/null @@ -1,4 +0,0 @@ -les mode de camera peuve etre obtenu avec - -picam2 = Picamera2() -picam2.sensor_modes \ No newline at end of file diff --git a/docs/Deroulement_course.md b/docs/Deroulement_course.md deleted file mode 100644 index d13a490c..00000000 --- a/docs/Deroulement_course.md +++ /dev/null @@ -1,98 +0,0 @@ -# Déroulement de la course - -Cette page sert à détailler les différentes étapes auxquelles vous pouvez/devez participer au cours de l'année. - -## Tests sur pistes - -Respectivement un et deux mois avant la course, ainsi que les 2 jours qui la précèdent, l'ENS vous ouvre ses portes pour des essais sur piste. Ils auront installé une piste en utilisant les mêmes composants que le jour de la course sur laquelle vous pouvez essayer de faire tourner votre voiture. - -!!! warning -Ces essais sont très importants ! Faites au maximum pour y aller ! En effet, votre voiture peut très bien tourner sur le simulateur ou sur une piste artisanale si vous en avez une mais se comporter de manière très différente dans les conditions de la course. C'est une occasion de ne pas travailler dans le vide et de vous assurer concrètement de la validité de l'avancement de votre projet. - -### Où aller - -Ces essais ont lieu au même endroit qu'aura lieu la course. Rendez-vous donc à l'ENS (4 Avenue des Sciences, 91190 Gif-sur-Yvette). - -- Passez les 2 portes tournantes, vous arrivez dans la cour interne. -- Suivez l'allée jusqu'au bout. -- Passez la porte et prenez l'escalier à gauche. -- Montez 2 étages. -- Dans le corps principal du batiment (longue allée avec plusieurs étages de plateforme), allez tout au fond à droite. - -### A ne pas oublier - -Pensez à embarquer tout votre matériel. Voiture, batteries, testeur/chargeur de batterie, câbles de remplacement, mini-routeur diffusant du Intech_Second, vos PCs, etc. C'est un peu loin pour faire l'aller-retour si vous avez oublié des choses. - -## Homologation - -Il s'agit de la première partie de la course. Vous devez pouvoir montrer que votre voiture respecte les critères définis par le règlement. - -!!! Note -Vous pouvez faire votre homologation la veille vers la fin de journée si vous êtes encore là pour vos essais sur pistes (ce qui est fortement recommandé !!). Si vous le pouvez, faites le, cela sera un énorme gain de temps (et diminution de stress) pour vous le jour J. - -L'homologation se déroule de la façon suivante : - -- - -## Le contre-la-montre - -Vous voici le jour J, homologué, prêts à en découdre. Félicitations pour cette première étape ! -Vous passez donc maintenant à la première phase des qualifications : le contre-la-montre ! - -### Aparte : pour la com - -Avant de vous parler des courses, petit conseil pour la com : c'est le moment de faire des rushs (je rappelle que vous devez faire un clip à la fin du GATE). Si vous voulez avoir une bonne vue des courses, mettez vous sur les plateformes en hauteur pour filmer (un étage suffit). Et hésitez pas à filmer d'autres moments de la journée, vous savez pas de quoi vous aurez besoin pour le clip (et puis ça fait des moments sympas entre vous 😉). - -### Revenons à nos moutons - -Le principe est simple : vous devez effectuer 2 tours de piste seul et avec obstacles le plus rapidement possible. Vous avez pour cela 2 essais (séparés par un peu de temps pour vous permettre de corriger des choses si le premier se déroule mal). Ne sera retenu évidemment que le meilleur des 2 chronos. Si jamais vous ne parvenez pas à terminer les 2 tours, votre temps sera mis au maximum (le maximum sera normalement défini un peu au-dessus du temps de la voiture ayant fini les 2 tours avec le plus haut temps). - -Un classement sera alors dressé qui pemerttra de définir les poules pour la seconde phase des qualifications. - -C'est maintenant l'heure de la pause du midi, bon appétit ! - -## Courses à plusieurs - -Passons à la deuxième phase des qualifications. Vous allez affronter d'autres voitures sur pistes cette fois (4 à 8 voitures selon le nombre de voitures présentes à la course), et ce, sur 3 tours. - -A chaque course la voiture accumule des points pour le classement général des qualifications : 10 points pour le premier, 6 points pour le 2nd, 4 pour le 3ème, et 2 points pour les voitures ayant terminé la course. Une voiture ne bouclant pas les 3 tours n'obtient aucun point. - -!!! note - -Pour information, pour notre année, le fait de ne pas terminer les 3 tours faisait que l'on avait 0 point, même si on les avait quasiment fait (par exemple, se crasher au dernier virage, on parle d'expérience 😭). M. Taillandier-Loize a indiqué qu'il demanderait à ce que cette règle soit rediscutée pour plutôt offrir des points par tour. On espère que ça sera accepté. - -!!! warning - -Attention, voici des règles importantes : - -- Les équipes ont 3 min pour installer leur véhicule sur la piste. -- L’ensemble des véhicules de la poule est positionné sur la grille de départ selon les résultats des qualifications. -- Une fois que toutes les équipes ont annoncé être prêtes, il est interdit de toucher les véhicules. Le signal de départ est donné oralement par l’arbitre. -- L’ordre d’arrivée est relevé après un nombre de tours définis à l’avance (3 par défaut pour la phase de qualification et 5 pour les phases finales). -- Une voiture ne terminant pas le nombre de tours définis n'est pas classée. -- Un véhicule ayant un comportement notoirement agressif envers les véhicules adverses est disqualifié retiré de la piste, de même qu'une voiture empêchant volontairement une autre de la doubler. -- Un véhicule immobilisé sur la piste plus de 10 secondes en l'absence d'une voiture le bloquant est retiré de la piste. -- Un véhicule ayant parcouru plus de 2 m à contre-sens est retiré de la piste. - -## Finales - -On espère que tout s'est bien passé pour vous jusqu'ici ! C'est le moment, la dernière ligne droite, l'heure de briller ! - -Bon pour cette partie je vais reprendre la docu de la course : - -A l'issue des qualifications, chaque voiture obtient 2 scores : - -- le total coursesQualif (la somme des points obtenus lors des 2 courses de qualification) -- le total CLM+courseQualif(le score précédent plus les points obtenus en qualification contre la montre). - -2 manches sont composées de chacune de 3 courses. - -Les 2 premiers de chaque poule (avec le score courseQualif, le score CLM+courseQualif servant à départager les ex-aequo) font les courses 1 de chaque manche, les 3ème et 4ème font les courses 2 de chaque manche et les autres font la course 3 de chaque manche. Le classement des qualifications (avec le score courseQualif, le score CLM+courseQualif servant à départager les ex-aequo) détermine la grille de départ, la voiture ayant le plus de points s’élançant en tête sur la grille de départ. - -Les arbitres relèvent l’ordre d’arrivée après un nombre de tours définis à l’avance (5 par défaut) et attribuent les points de la manière suivante : 25 pts pour le 1er, 18 pts pour le 2e, 15 pts pour le 3e, 12 pts pour le 4e ; 10 pts pour le 5e, 8 pts pour le 6e ; 6 pts pour le 7e, 4 pts pour le 8e ; 2 pts pour le 9e et 1 pt pour le 10e. Une voiture ne terminant pas le nombre de tours défini obtient 0 point. Une fois les voitures de la première course classées, on continue l'attribution des points aux voitures de la 2nde course. - -Le classement final se fait avec les points des 2 courses finales uniquement. En cas d'égalité, le score CLM+courseQualif est pris en compte pour le classement final. Si il y a de nouveau égalité, le score coursesQualif départage les équipes. - -Les prix récompensent les 1er, 2ème, 3ème du classement général. S'y ajoute un prix de l'innovation et un prix pour les 1ers parmi les licences. - -Et voilà, CoVAPSy, c'est finiii... On espère que vous avez tout cassé (sauf la voiture svp) ! La partie technique du GATE est globalement finie, il vous reste donc les actions de promotions à faire, bon courage 😉 diff --git a/docs/elec/Hat/Hat.kicad_pcb b/docs/Hardware/Hat/Hat.kicad_pcb similarity index 100% rename from docs/elec/Hat/Hat.kicad_pcb rename to docs/Hardware/Hat/Hat.kicad_pcb diff --git a/docs/elec/Hat/Hat.kicad_prl b/docs/Hardware/Hat/Hat.kicad_prl similarity index 100% rename from docs/elec/Hat/Hat.kicad_prl rename to docs/Hardware/Hat/Hat.kicad_prl diff --git a/docs/elec/Hat/Hat.kicad_pro b/docs/Hardware/Hat/Hat.kicad_pro similarity index 100% rename from docs/elec/Hat/Hat.kicad_pro rename to docs/Hardware/Hat/Hat.kicad_pro diff --git a/docs/elec/Hat/Hat.kicad_sch b/docs/Hardware/Hat/Hat.kicad_sch similarity index 100% rename from docs/elec/Hat/Hat.kicad_sch rename to docs/Hardware/Hat/Hat.kicad_sch diff --git a/docs/elec/Hat/Hat.kicad_sch-bak b/docs/Hardware/Hat/Hat.kicad_sch-bak similarity index 100% rename from docs/elec/Hat/Hat.kicad_sch-bak rename to docs/Hardware/Hat/Hat.kicad_sch-bak diff --git a/docs/elec/Hat/PCA9517D_112/PCA9517D_112.kicad_sym b/docs/Hardware/Hat/PCA9517D_112/PCA9517D_112.kicad_sym similarity index 100% rename from docs/elec/Hat/PCA9517D_112/PCA9517D_112.kicad_sym rename to docs/Hardware/Hat/PCA9517D_112/PCA9517D_112.kicad_sym diff --git a/docs/elec/Hat/PCA9517D_112/PCA9517D_112.step b/docs/Hardware/Hat/PCA9517D_112/PCA9517D_112.step similarity index 100% rename from docs/elec/Hat/PCA9517D_112/PCA9517D_112.step rename to docs/Hardware/Hat/PCA9517D_112/PCA9517D_112.step diff --git a/docs/elec/Hat/PCA9517D_112/SOIC127P600X175-8N.kicad_mod b/docs/Hardware/Hat/PCA9517D_112/SOIC127P600X175-8N.kicad_mod similarity index 100% rename from docs/elec/Hat/PCA9517D_112/SOIC127P600X175-8N.kicad_mod rename to docs/Hardware/Hat/PCA9517D_112/SOIC127P600X175-8N.kicad_mod diff --git a/docs/elec/Hat/PCA9517D_112/how-to-import.htm b/docs/Hardware/Hat/PCA9517D_112/how-to-import.htm similarity index 100% rename from docs/elec/Hat/PCA9517D_112/how-to-import.htm rename to docs/Hardware/Hat/PCA9517D_112/how-to-import.htm diff --git a/docs/elec/Hat/fabrication-toolkit-options.json b/docs/Hardware/Hat/fabrication-toolkit-options.json similarity index 100% rename from docs/elec/Hat/fabrication-toolkit-options.json rename to docs/Hardware/Hat/fabrication-toolkit-options.json diff --git a/docs/elec/Hat/fp-info-cache b/docs/Hardware/Hat/fp-info-cache similarity index 100% rename from docs/elec/Hat/fp-info-cache rename to docs/Hardware/Hat/fp-info-cache diff --git a/docs/elec/Hat/sym-lib-table b/docs/Hardware/Hat/sym-lib-table similarity index 100% rename from docs/elec/Hat/sym-lib-table rename to docs/Hardware/Hat/sym-lib-table diff --git a/docs/elec/Hat_CoVASPSy_v1re2_Schema-1.pdf b/docs/Hardware/Hat_CoVASPSy_v1re2_Schema-1.pdf similarity index 100% rename from docs/elec/Hat_CoVASPSy_v1re2_Schema-1.pdf rename to docs/Hardware/Hat_CoVASPSy_v1re2_Schema-1.pdf diff --git a/docs/elec/Interface/Interface.kicad_pcb b/docs/Hardware/Interface/Interface.kicad_pcb similarity index 100% rename from docs/elec/Interface/Interface.kicad_pcb rename to docs/Hardware/Interface/Interface.kicad_pcb diff --git a/docs/elec/Interface/Interface.kicad_prl b/docs/Hardware/Interface/Interface.kicad_prl similarity index 100% rename from docs/elec/Interface/Interface.kicad_prl rename to docs/Hardware/Interface/Interface.kicad_prl diff --git a/docs/elec/Interface/Interface.kicad_pro b/docs/Hardware/Interface/Interface.kicad_pro similarity index 100% rename from docs/elec/Interface/Interface.kicad_pro rename to docs/Hardware/Interface/Interface.kicad_pro diff --git a/docs/elec/Interface/Interface.kicad_sch b/docs/Hardware/Interface/Interface.kicad_sch similarity index 100% rename from docs/elec/Interface/Interface.kicad_sch rename to docs/Hardware/Interface/Interface.kicad_sch diff --git a/docs/elec/Interface/fp-info-cache b/docs/Hardware/Interface/fp-info-cache similarity index 100% rename from docs/elec/Interface/fp-info-cache rename to docs/Hardware/Interface/fp-info-cache diff --git a/docs/elec/Interface_CoVASPSy_v1re2_Schema.pdf b/docs/Hardware/Interface_CoVASPSy_v1re2_Schema.pdf similarity index 100% rename from docs/elec/Interface_CoVASPSy_v1re2_Schema.pdf rename to docs/Hardware/Interface_CoVASPSy_v1re2_Schema.pdf diff --git a/docs/elec/Mezzanine_CoVASPSy_v1re2_Schema.pdf b/docs/Hardware/Mezzanine_CoVASPSy_v1re2_Schema.pdf similarity index 100% rename from docs/elec/Mezzanine_CoVASPSy_v1re2_Schema.pdf rename to docs/Hardware/Mezzanine_CoVASPSy_v1re2_Schema.pdf diff --git a/docs/elec/mezzanine/mezzanine.kicad_pcb b/docs/Hardware/mezzanine/mezzanine.kicad_pcb similarity index 100% rename from docs/elec/mezzanine/mezzanine.kicad_pcb rename to docs/Hardware/mezzanine/mezzanine.kicad_pcb diff --git a/docs/elec/mezzanine/mezzanine.kicad_prl b/docs/Hardware/mezzanine/mezzanine.kicad_prl similarity index 100% rename from docs/elec/mezzanine/mezzanine.kicad_prl rename to docs/Hardware/mezzanine/mezzanine.kicad_prl diff --git a/docs/elec/mezzanine/mezzanine.kicad_pro b/docs/Hardware/mezzanine/mezzanine.kicad_pro similarity index 100% rename from docs/elec/mezzanine/mezzanine.kicad_pro rename to docs/Hardware/mezzanine/mezzanine.kicad_pro diff --git a/docs/elec/mezzanine/mezzanine.kicad_sch b/docs/Hardware/mezzanine/mezzanine.kicad_sch similarity index 100% rename from docs/elec/mezzanine/mezzanine.kicad_sch rename to docs/Hardware/mezzanine/mezzanine.kicad_sch diff --git a/docs/IA.md b/docs/IA.md deleted file mode 100644 index dc07f627..00000000 --- a/docs/IA.md +++ /dev/null @@ -1,5 +0,0 @@ - -Raceline is a Trakmania IA its journey is desribed here: [https://www.youtube.com/watch?v=cUojVsCJ51I](https://www.youtube.com/watch?v=cUojVsCJ51I) -[https://linesight-rl.github.io/linesight/build/html/index.html](https://linesight-rl.github.io/linesight/build/html/index.html) -[https://arxiv.org/abs/1806.06923](https://arxiv.org/abs/1806.06923) - diff --git a/docs/Instruction_manual_UST-10LX_MRS0020D_en_1513910662-1.pdf b/docs/Instruction_manual_UST-10LX_MRS0020D_en_1513910662-1.pdf deleted file mode 100644 index 511f6217..00000000 Binary files a/docs/Instruction_manual_UST-10LX_MRS0020D_en_1513910662-1.pdf and /dev/null differ diff --git a/docs/Lidar.md b/docs/Lidar.md deleted file mode 100644 index 2ee9fbc9..00000000 --- a/docs/Lidar.md +++ /dev/null @@ -1,106 +0,0 @@ -# Lidar - -## UST-10lx basics - -an image showing a UST-10lx with an orange top. We can read Hokuyo Smart Urg underneath - - -Les Lidar utiliser par INTech sont des [UST-10LX](https://www.hokuyo-aut.jp/search/single.php?serial=167) concu et vendu par Hokuyo. Une documentation non officel (mais plutot complete [existe ici])(https://sourceforge.net/p/urgnetwork/wiki/Home/). Le manuel -du l'UST-10lx est [télécharchable ici](./Instruction_manual_UST-10LX_MRS0020D_en_1513910662-1.pdf) - -## UST-10lx connectivity - -Il communique par Ethernet en utilisant le protocole [Secure Communications Interoperability Protocol (SCIP)](https://en.wikipedia.org/wiki/Secure_Communications_Interoperability_Protocol). Ce protocol peut faire peur a premiere vue mais nous n'utilison que les commande decrit [ici](https://sourceforge.net/p/urgnetwork/wiki/scip_en/). Pour communiquer par ethernet, les lidar possede une adresse IP: - -``` -192.168.0.10:10940/24 -``` - -Il faut donc choisir une adresse ip dans le bon sous-resaux. Nous avons arbitrairement choisi: - -``` -192.168.0.20 -``` - -## UST-10lx specs - -an image showing a UST-10lx with an orange top. We can read Hokuyo Smart Urg underneath - - -| Spec | Value | Unit | -|---------------------|----------------------|------| -| Min distance | 20 | mm | -| Max distance | 30000 | mm | -| Accuracy | ±40 | mm | -| Scan angle | 270 | ° | -| Angular resolution | 0.25 | pt/° | -| Angular resolution | 1440 | pt/tr| -| Scan rate | 40 | Hz | -| Poids | 130 | g | -| Voltage | 10-30 | VDC | -| Current @24VDC | 150 (450 at startup) | mA | - - -## Using HokuyoReader class - -This class is from [cassc/tcp_hokuyo.py](https://gist.github.com/cassc/26ac479624cb028b2567491a68c34fb8) -This class is simpler than [hokuyolx](#using-hokuyolx-class) - -Create the class instance with -``` python -sensor = HokuyoReader(IP, PORT) -``` - -use `HokuyoReader.stop()` to get rid of any leftover data or problems from an improper shutdown and start mesuring with `HokuyoReader.startContinuous(0, 1080)` with 0 and 1080 the steps that are mesured - -```pyhton -sensor.stop() -sensor.startContinuous(0, 1080) -``` - -Distances can be retrived as a numpy array with the `HokuyoReader.rDistance()` (r standing for radial) - -```python -distance_array=sensor.rDistance() -``` - -Use `HokuyoReader.stop()` to gracefully shutdown the lidar - -```pyhton -sensor.stop() - -``` - -!!! note - This class has been renamed to Lidar to be more descriptive in code. - -!!! note - After contacting the author, he has agreed to licence his script as MIT (none before), allowing us to do the same on this repository. - -## Using Hokuyolx class - -This class comes from [SkoltechRobotics/hokuyolx](https://github.com/SkoltechRobotics/hokuyolx). -This class has considerably more options than [HokuyoReader](#using-hokuyoreader-class) but is more complicated to understand. This class is documented at [http://hokuyolx.rtfd.org/](http://hokuyolx.rtfd.org/). As of 12/12/24 it doesn't work out off the box and we use [HokuyoReader](#using-hokuyoreader-class) - -## Les galères - -On a eu pas mal de galères avec le lidar qui ce déconnecter de manière intempestive. En temps normal, soit le lidar est débranché, l’interface eth0 est Down et elle n’a pas d’IP, soit le lidar est branché et l’interface est UP avec un IP static (192.168.1.20). Malheureusement, des fois, pour des raisons un peu mystère, l’interface rester UP, mais perd son IP fixe. - -Débrancher et rebrancher le lidar a systématiquement marché, mais n’adresse pas la cause du problème. 2 théories : -- Le câble est baisé et fait un faux contact -- Le Network manager de la RPI fout la merde - -Comme la languette de rétention, Ethernet était un peu mort donc on a reserti un nouveau connecteur (Merci MiNET ❤️). Cela a résolu une grande partie du problème et nous n’avons observer que deux récidives : l’une directement après l’autre le 03/04/25 lors des essais sur piste. - -###Quelques commandes utiles - -```bash -ip a -``` -Permets de voir les différentes interfaces réseau, leurs statuts et les IP associer. - -```bash -watch -n 0.1 "ip a" -``` -Permet d’exécuter toutes les 0,1 s (le max) une commande et d’affiche le résultat, ici`ip a`. - diff --git a/docs/img/Logo/Autotech_original.jpeg b/docs/Logo/Autotech_original.jpeg similarity index 100% rename from docs/img/Logo/Autotech_original.jpeg rename to docs/Logo/Autotech_original.jpeg diff --git "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (carr\303\251)_promo Ecole.svg" "b/docs/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (carr\303\251)_promo Ecole.svg" similarity index 100% rename from "docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (carr\303\251)_promo Ecole.svg" rename to "docs/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (carr\303\251)_promo Ecole.svg" diff --git "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole.svg" "b/docs/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole.svg" similarity index 100% rename from "docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole.svg" rename to "docs/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole.svg" diff --git a/docs/Simulateur.md b/docs/Simulateur.md deleted file mode 100644 index e568c8d5..00000000 --- a/docs/Simulateur.md +++ /dev/null @@ -1,132 +0,0 @@ -# Simulateur - -Cette page de documentation détaille le fonctionnement des scripts utilisés dans le cadre de l'utilisation de [Webots](https://cyberbotics.com) pour entrainer une IA sur une simulation de piste. - ---- -## Objectifs - -On cherche à simuler le déplacement d'une voiture dans un environnement semblable à la piste de la course. A chaque étape de la simulation on donne en entrée les données des différents capteurs à un model qui nous renvoie une consigne d'angle de roues à appliquer au véhicule pour la prochaine étape de la simulation. - - ---- -## Organisation basique à une voiture - -Une première approche pourrairt être d'organiser notre monde avec une [Piste](#piste) et une [Voiture](#voiture). Dans cette approche toute la logique serait géré au niveau du [Driver](https://cyberbotics.com/doc/automobile/driver-library) de la [Voiture](#voiture): -- obtenir la data des capteurs -- exécuter le model pour obtenir une consigne -- update l'état de la voiture en accord avec cette consigne -- puis finalement calculer les rewards données à la fin de chaque episode pour entrainer l'[IA](./IA.md) - -!!! note - Le problème de cette architecture est qu'elle n'est pas scalable. Elle nous force à n'utiliser qu'une seule voiture en même temps car en augmentant le nombre de voitures, elles gèreraient toutes leur propre model en local et on entraînerait donc _n_ models au lieu d'un 1 ce qui est inutile. - -
-```mermaid -flowchart TD - A("`__fa:fa-car TT02_0__ - Hokuyo - (_AI model_)`") -``` -
- ---- -## Organisation complexe à _n_ voitures - -Pour s'affranchir de ces limites on propose de déplacer l'éxécution du model dans un controller séparé des _n_ voitures. On choisira donc de crééer un [Robot](https://cyberbotics.com/doc/reference/robot) de type [Supervisor](https://cyberbotics.com/doc/reference/supervisor) qu'on nommera `WorldSupervisor` en ajoutant le field `supervisor TRUE` dans la string de définition du node correspondant à notre supervisor. Notre objectif est de créer _n_ voitures à l'initialisation du monde et de faire communiquer les voitures et le WorldSupervisor via des [Emitters](https://www.cyberbotics.com/doc/reference/emitter)/[Receivers](https://www.cyberbotics.com/doc/reference/receiver). Il faut donc que le WorldSupervisor ait _n_ couples d'[Emitters](https://www.cyberbotics.com/doc/reference/emitter)/[Receivers](https://www.cyberbotics.com/doc/reference/receiver) pour communiquer à chaque voiture sur un channel différent. - -!!! warning - _n_ n'est pas connu avant l'éxecution du controller du WorldSupervisor. Or, l'état d'un [Robot](https://cyberbotics.com/doc/reference/robot) pour les fonctions de controller dans Webots ne change pas dynamiquement i.e. si le WorldSupervisor s'ajoute lui-même des devices via son controller au cours de l'éxecution, ces derniers ne seront pas acessibles via la méthode `getDevice(name)`. - -C'est pour cela qu'en plus d'un WorldSupervisor gérant la logique de l'[IA](./IA.md), nous avons besoin d'un autre [Supervisor](https://cyberbotics.com/doc/reference/supervisor) dont le rôle est de créer le WorldSupervisor avec ses _n_ couples d'[Emitters](https://www.cyberbotics.com/doc/reference/emitter)/[Receivers](https://www.cyberbotics.com/doc/reference/receiver) et les _n_ voitures au début de l'initialisation du monde. On nommera ce nouveau [Supervisor](https://cyberbotics.com/doc/reference/supervisor) `WorldInit`. - -!!! warning - L'API [Emitters](https://www.cyberbotics.com/doc/reference/emitter)/[Receivers](https://www.cyberbotics.com/doc/reference/receiver) ne garantit pas un planning spécifique pour la transmission. Il arrive parfois que plusieurs paquets soient regroupés et reçus ensemble ou qu'aucun packet ne soit reçu. Ces cas doivent donc être traités et on perd le déterminisme de l'entraînement. - ---- -### Pendant l'initialisation du monde - -
-```mermaid -flowchart TD - Init("WorldInit") --> |crée| A("`__WorldSupervisor__ - (_AI model_) - supervisor_emitter_0 - supervisor_receiver_0 - supervisor_emitter_1 - supervisor_receiver_1 - ... - supervisor_emitter_n-1 - supervisor_receiver_n-1`") - - Init --> |crée| B("`__fa:fa-car TT02_0__ - - Hokuyo - TT02_emitter - TT02_receiver`") - - Init --> |crée| C("`__fa:fa-car TT02_1__ - Hokuyo - TT02_emitter - TT02_receiver`") - - Init --> D[...] - style D fill:none,stroke:none - linkStyle 3 stroke-width:0,marker-end:none - Init --> |crée| E("`__fa:fa-car TT02_n-1__ - Hokuyo - TT02_emitter - TT02_receiver`") -``` -
- ---- -### Pendant l'exécution des controllers -
-```mermaid -flowchart TD - Super("`__WorldSupervisor__ - supervisor_emitter_0 - supervisor_receiver_0 - supervisor_emitter_1 - supervisor_receiver_1 - ... - supervisor_emitter_n-1 - supervisor_receiver_n-1`") --> |commande| B("`__fa:fa-car TT02_0__ - Hokuyo - TT02_emitter - TT02_receiver`") - - Super --> |commande| C("`__fa:fa-car TT02_1__ - Hokuyo - TT02_emitter - TT02_receiver`") - - Super --> D[...] - style D fill:none,stroke:none - linkStyle 2 stroke-width:0,marker-end:none - - Super --> |commande| E("`__fa:fa-car TT02_n-1__ - Hokuyo - TT02_emitter - TT02_receiver`") -``` -
- ---- -## Environnement Webots utilisé - -### Piste - -Le monde utilisé est `Simulateur/worlds/piste2.wbt`. Ce monde vient de la version 2023 du [github officiel CoVAPsy](https://github.com/ajuton-ens/CourseVoituresAutonomesSaclay). - -### Voiture - -La voiture utilisé est TT02(`Simulateur/protos/TT02_2023b.proto`). Ce proto vient de la version 2023 du [github officiel CoVAPsy](https://github.com/ajuton-ens/CourseVoituresAutonomesSaclay). - -### Capteurs - -Les capteurs utilisés sont : - -- Un [Lidar](./Lidar.md) aux specifications de notre vrai lidar. - -- Un [TouchSensor](https://cyberbotics.com/doc/reference/touchsensor) uniquement sur le simulateur pour détecter les collisions. diff --git a/docs/UV_USAGE.md b/docs/UV_USAGE.md deleted file mode 100644 index b7cc2bdb..00000000 --- a/docs/UV_USAGE.md +++ /dev/null @@ -1,127 +0,0 @@ -# UV Dependency Management Guide - -This project now uses [UV](https://github.com/astral-sh/uv) for fast, reliable Python package management. - -## Quick Start - -### Install UV -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -source $HOME/.cargo/env -``` - -### Install Project Dependencies -```bash -# Install all runtime dependencies -uv sync - -# Install with documentation dependencies -uv sync --extra docs - -# Install with development dependencies -uv sync --extra dev - -# Install all optional dependencies -uv sync --all-extras -``` - -### Running Python Scripts and Commands -```bash -# Run a script using the project's virtual environment -uv run python src/HL/main.py - -# Run a script directly (if defined in pyproject.toml) -uv run covapsy-main - -# Run any command with the project's dependencies -uv run mkdocs serve - -# Note: Always use 'uv run' prefix to ensure you're using the UV-managed environment -``` - -### Managing Dependencies - -#### Add a new dependency -```bash -# Add runtime dependency -uv add numpy - -# Add development dependency -uv add --dev pytest - -# Add documentation dependency -uv add --optional docs mkdocs -``` - -#### Remove a dependency -```bash -uv remove package-name -``` - -#### Update dependencies -```bash -# Update all packages -uv lock --upgrade - -# Update specific package -uv add package-name@latest -``` - -### Virtual Environment Management - -UV automatically manages virtual environments. You can also: - -```bash -# Activate the virtual environment manually -source .venv/bin/activate - -# Or run commands directly -uv run python your_script.py -``` - -### Lock File - -The `uv.lock` file pins exact versions for reproducible builds. Commit this file to version control. - - -### Old way (pip): -```bash -pip install -r requirements.txt -``` - -### New way (UV): -```bash -uv sync -``` - -## Benefits of UV - -- **Fast**: 10-100x faster than pip -- **Reliable**: Deterministic resolution with lock files -- **Cross-platform**: Works on Linux, macOS, Windows -- **Drop-in replacement**: Compatible with existing Python tooling -- **Modern**: Built in Rust with modern dependency resolution - -## Troubleshooting - -### Common Issues - -#### "Plugin not installed" or "Module not found" errors -**Problem**: Running commands directly (e.g., `mkdocs serve`) instead of through UV. -**Solution**: Always prefix commands with `uv run`: -```bash -# ❌ Wrong -mkdocs serve - -# ✅ Correct -uv run mkdocs serve -``` - -#### UV installs unwanted dependencies -**Problem**: UV may incorrectly resolve optional dependencies due to dependency resolution bugs. -**Solution**: Remove problematic lock files and clear cache: -```bash -rm uv.lock -uv cache clean -uv sync --extra docs -``` diff --git a/docs/building_docs.md b/docs/building_docs.md deleted file mode 100644 index 51352797..00000000 --- a/docs/building_docs.md +++ /dev/null @@ -1,175 +0,0 @@ -# How to use Markdown and MkDocs - -This documentation is built using Markdown and MkDocs. This page is a guide to get you started contributing. - -## What is Markdown - -> Markdown is a lightweight markup language used to format text in a simple and readable way. It uses plain-text syntax to define elements like headings, lists, links, and emphasis (e.g., **bold**, *italic*). Markdown is commonly used in documentation, blogging platforms, and version control systems like GitHub because it is easy to write and converts seamlessly into HTML for web display. -ChatGPT - -Markdown files are often recognizable by their `.md` and rarely `.markdown` file extensions. - -## Basic Syntax - -### Bold and Italics - -To make bold text, surround your text with double asterisks: - - - - - -
**This text will be bolded**This text will be bolded
- -For italics, use a single asterisk: - - - - - -
*This text will be italicized*This text will be italicized
- -These can be combined: - - - - - -
***This text will be bold and italic***This text will be bold and italic
- -### Headings - -Headings are denoted using `#` at the beginning of a line. The number of `#` determines the depth of the header: - -| Input | Render | -|------------------|------------------ | -| # Heading 1 |

Heading 1

| -| ## Heading 2 |

Heading 2

| -| ### Heading 3 |

Heading 3

| -| #### Heading 4 |

Heading 4

| -| ##### Heading 5 |
Heading 5
| - -!!! warning - A space is required for a heading to work. - - - \#Heading 1 ❌ - - \## Heading 1 ✔️ - -### Links - -To add links, use the following syntax: - -\[The text that will be displayed](The link to follow) - -\[A link to example.org](https://example.org/) - -[A link to example.org](https://example.org/) - -!!! Note - These can be used for internal links as well. For example, we can link this section with \[Links](#Links) or \[Links](/building_docs/#Links): [Links](#links). - - The second method can be generalized to reach any page or header on the website. - -### Images - -The syntax to add images is almost the same as links. Alt text is the text used by accessibility readers. It should therefore be descriptive rather than a title. - -\!\[Alt text](path/to/image.png) - -\!\[A square version of the autotech logo. It is used as a favicon for the site](img/favicon.png) - -![A square version of the autotech logo. It is used as a favicon for the site](img/favicon.png) - -!!! Note - See [Images in HTML](#images-in-html) for more fine-grain control of images. - -### Code Blocks - -Code blocks in Markdown can be created using triple backticks (\`\`\`). You can specify the language for syntax highlighting by adding the language name after the opening backticks. Here is an example of a code block in Python: - -\`\`\`python - -def hello_world(): - - print("Hello, world!") - -\`\`\` - -```python -def hello_world(): - print("Hello, world!") -``` - -You can also create inline code by wrapping text in single backticks (\`). For example, \`print("Hello, world!")\` renders as `print("Hello, world!")`. - -## Advanced Syntax - -Markdown's strength is in the simplicity of writing, but that sometimes comes back to bite us when trying to do something a little more complicated. Luckily, most Markdown renderers accept some HTML in Markdown files. This is the case with MkDocs and GitHub. **HTML should only be used as necessary.** - -### Comments - -In Markdown, there is no built-in syntax for comments. However, you can use HTML comments to add notes or comments that will not be rendered in the final output. Here is an example: - -``` - -``` - -### Images in HTML - -Images are impossible to scale or render next to text. Using the HTML `` tag, it is possible to customize your image's behavior. Here is an example of an image that is scaled and displayed to the right. Keep in mind that images **can't** be rendered above the position of the `` tag: - -``` -A square version of the autotech logo. It is used as a favicon for the site -``` - -A square version of the autotech logo. It is used as a favicon for the site - -!!! warning - Due to differences in how the HTML and Markdown are compiled, there are different paths when adding images in HTML and Markdown. - -
- -### Admonitions - -Admonitions are a way to highlight important information in your documentation. MkDocs supports several types of admonitions, such as notes, warnings/caution, danger, and tips/hint. Here is an example of how to use them: - -``` -!!! note - This is a note admonition. -``` -!!! note - This is a note admonition. - -``` -!!! warning - This is a warning admonition. -``` -!!! warning - This is a warning admonition. - -## Using MkDocs - -For full documentation, visit [mkdocs.org](https://www.mkdocs.org). - -### Commands - -* `mkdocs serve` - Start the live-reloading docs server. It should now be accessible at [127.0.0.1:8000](http://127.0.0.1:8000/) on ***your machine**** `mkdocs gh-deploy` - Build the documentation site on the gh-pages branch of your repository. - -!!! Note - On the [Club-INTech/CoVAPSy](https://github.com/Club-INTech/CoVAPSy) repository, `mkdocs gh-deploy` is automatically called upon push to the main branch. It can still be manually called when on another branch, but [gh-pages](https://github.com/Club-INTech/CoVAPSy/tree/gh-pages) will be overwritten by any push on main. - -### Project Layout - - mkdocs.yml ## The configuration file. - docs/ - index.md ## The documentation homepage. - ... ## Other markdown pages, images, and other files. - img/ - favicon.png ## The icon that appears on the browser tab. - ... other images - -### Using GitHub Pages - -Navigate to the repository, then to settings/pages. Using the drop-down menus, select "Deploy from branch" for source and "gh-pages" and "/root" for branch. - -!!! Note - You may have to run `mkdocs gh-deploy` for the gh-pages branch to show. See [Commands](#commands). diff --git "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (carr\303\251)_promo Ecole_transparent.png" "b/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (carr\303\251)_promo Ecole_transparent.png" deleted file mode 100755 index b3c13413..00000000 Binary files "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (carr\303\251)_promo Ecole_transparent.png" and /dev/null differ diff --git "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_blanc.png" "b/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_blanc.png" deleted file mode 100644 index b7c48769..00000000 Binary files "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_blanc.png" and /dev/null differ diff --git "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_blanc_120px.png" "b/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_blanc_120px.png" deleted file mode 100644 index 55f269f9..00000000 Binary files "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_blanc_120px.png" and /dev/null differ diff --git "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_noir.png" "b/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_noir.png" deleted file mode 100644 index a67ea072..00000000 Binary files "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_noir.png" and /dev/null differ diff --git a/docs/img/UST-10LX_Scan_pattern.png b/docs/img/UST-10LX_Scan_pattern.png deleted file mode 100644 index da6f082a..00000000 Binary files a/docs/img/UST-10LX_Scan_pattern.png and /dev/null differ diff --git a/docs/img/UST-10lx.jpg b/docs/img/UST-10lx.jpg deleted file mode 100644 index 7de34604..00000000 Binary files a/docs/img/UST-10lx.jpg and /dev/null differ diff --git a/docs/img/favicon.ico b/docs/img/favicon.ico deleted file mode 100644 index 48ff8b1f..00000000 Binary files a/docs/img/favicon.ico and /dev/null differ diff --git a/docs/img/favicon.png b/docs/img/favicon.png deleted file mode 100755 index b3c13413..00000000 Binary files a/docs/img/favicon.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md deleted file mode 100755 index 134fbf30..00000000 --- a/docs/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Home - -Bonjour à toi, cher INTech-ien intéressé par AutoTech. Ces pages seront ta bible pour commencer à contribuer à AutoTech. - -Cette documentation a pour objectif d’être autosuffisante, mais comme aucune documentation n’est parfaite, n’hésite pas à te rapprocher de tes 2A. - -Le conseil le plus précieux que je puisse te donner est de documenter au fur et à mesure, et d’utiliser exclusivement Git. - -Utilise Git, car tu seras amené à faire des modifications par-ci par-là, et notamment pendant le rush de la course, tu pourrais être tenté de faire du scp (ssh copy). Résiste. Faire ça pose problème, car tu risques de perdre des versions de code qui fonctionnaient bien. Dans la même veine, utilise et abuse de la fonctionnalité Release de GitHub. Elle te sera très utile lorsque tu seras dans le rush et que plus rien ne fonctionnera. - -Enfin, documente au fur et à mesure. Tu pourrais être tenté de dire : « C’est le rush, je documenterai plus tard. » Résiste là aussi. Premièrement, tu pourrais mieux comprendre certains éléments en les documentant proprement. Deuxièmement, tu risques d’oublier des choses. Et troisièmement, tu pourrais te retrouver à rédiger la documentation le 17 septembre, alors que ta première réunion avec les 1A est le 18 septembre et que tu n’as pas touché au projet depuis la mi-avril. \ No newline at end of file diff --git a/docs/requirments.txt b/docs/requirments.txt deleted file mode 100644 index 99f97a46..00000000 --- a/docs/requirments.txt +++ /dev/null @@ -1,4 +0,0 @@ -mkdocs -mkdocs-mermaid2-plugin -mkdocs-git-revision-date-localized-plugin -mkdocs-git-authors-plugin \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100755 index e298a5c4..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,67 +0,0 @@ -site_name: AutoTech's Docs - -nav: - - Home: index.md - - Lidar.md - - Simulateur.md - - IA.md - - Asservissement.md - - How to create documentation: building_docs.md - - Déroulement de la course: Deroulement_course.md - - Regle de la course: https://ajuton-ens.github.io/CourseVoituresAutonomesSaclay/ - -theme: - title: AutoTech's Docs - name: readthedocs - logo: img/Logo/Logo_équipe N°20_AutoTech 2025 (long)_promo Ecole_transparent.png - favicon: img/favicon.ico - theme: - # icon: does not work idk why -Matthias - # admonition: - # note: octicons/tag-16 - # abstract: octicons/checklist-16 - # info: octicons/info-16 - # tip: octicons/squirrel-16 - # success: octicons/check-16 - # question: octicons/question-16 - # warning: octicons/alert-16 - # failure: octicons/x-circle-16 - # danger: octicons/zap-16 - # bug: octicons/bug-16 - # example: octicons/beaker-16 - # quote: octicons/quote-16 - - # locale: fr # does not work idk why -Arnaud autotech 2025 - -repo_url: https://github.com/Association-INTech/CoVAPSy - -site_description: "AutoTech est un projet de l'association de robotique, INTech, de Telecom SudParis et IMT Business School. Il a pour but de concevoir un véhicule autonome pour la compétition CoVAPSy." - -# site_author: "AutoTech" - -# exclude_docs: | -# api-config.json # A file with this name anywhere. -# /requirements.txt # Top-level "docs/requirements.txt". -# *.py # Any file with this extension anywhere. -# !/foo/example.py # But keep this particular file. - -markdown_extensions: - - admonition # This is a plugin for adding colored boxes for notes, warnings, etc. - -plugins: - - search - - mermaid2 - # TODO: Overide theme to add authors and dates - # - git-authors #https://github.com/timvink/mkdocs-git-authors-plugin - # - git-revision-date-localized: #https://timvink.github.io/mkdocs-git-revision-date-localized-plugin/options/ - # type: timeago - # custom_format: "%d. %B %Y" - # timezone: Europe/Amsterdam - # locale: en - # fallback_to_build_date: false - # enable_creation_date: true - # exclude: - # - index.md - # enable_git_follow: true - # enabled: true - # strict: true diff --git a/pyproject.toml b/pyproject.toml index 848ed581..7e029326 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,73 +1,26 @@ [project] -name = "Autotech" +name = "autotech" version = "0.1.0" -description = "Participation de INTech à CoVAPSy" +description = "INTech participation to CoVAPSy" readme = "README.md" requires-python = ">=3.12,<3.14" authors = [ + # 2025 team { name = "Arnaud Costermans", email = "arnaud.costermans@intech-robotics.fr" }, { name = "Matthias Bienvenu", email = "matthias.bienvenu@intech-robotics.fr" }, { name = "Alexandre Naizondard", email = "alexandre.naizondard@telecom-sudparis.eu" }, { name = "Antonin Fruchet", email = "antonin.fruchet@telecom-sudparis.eu" }, + { name = "Mark Kirov", email = "mark.kirov@telecom-sudparis.eu" }, + # 2026 team { name = "Cyril Bampeta", email = "cyril.bampeta@intech-robotics.fr" }, + { name = "Robinson Cerceau", email = "robinson.cerceau@intech-robotics.fr" }, + { name = "Nikopol Markgraf", email = "nikopol.markgra@intech-robotics.fr" }, + { name = "Nachid Raslane", email = "nachid.raslane@intech-robotics.fr" }, + { name = "Arthur Ripoll", email = "arthur.ripoll@intech-robotics.fr" }, + { name = "Mohamed Benabadji", email = "mohamed.benabadji@intech-robotics.fr" }, ] +dependencies = [] -dependencies = [ - -] - -[project.optional-dependencies] -docs = [ - "mkdocs>=1.4.0", - "mkdocs-mermaid2-plugin>=0.6.0", - "mkdocs-git-revision-date-localized-plugin>=1.1.0", - "mkdocs-git-authors-plugin>=0.6.0", -] -simu = [ - # Core scientific computing and data processing - "numpy>=1.21.0", - "matplotlib>=3.4.0", - - # Machine learning and AI - "onnx>=1.8.0", - "onnxruntime>=1.8.0", - "torch>=1.9.0", - "stable-baselines3>=1.6.0", - - # Used in the controller - "psutil[simu]>=7.1.3", -] -rpi = [ - # Note: picamera2 causes UV to auto-include rpi extras even when not requested - # This is likely a UV bug. Add back when needed: - # "picamera2>=0.3.0", - # AI Inference - "onnxruntime>=1.8.0", - # Raspberry Pi specific hardware control - "rpi-hardware-pwm>=0.1.0", - "RPi.GPIO>=0.7.1", - "gpiozero>=1.6.0", - # I2C and SPI communication (Linux specific) - "smbus>=1.1.post2", - "spidev>=3.5", - # OLED display support - "luma.oled>=3.8.0", - "luma.core>=2.3.0", - "Adafruit-SSD1306>=1.6.0", - # Time-of-Flight sensor support - "adafruit-circuitpython-vl53l0x>=3.6.0", - "adafruit-blinka>=8.0.0", - # PS4 controller support - "pyPS4Controller>=1.2.0", - "zmq>=0.0.0", - "netifaces>=0.11.0", - "matplotlib>=3.10.5", - "opencv-python>=4.12.0.88", - "scipy>=1.15.3", - "websockets>=16.0", - "fastapi>=0.128.0", - "uvicorn>=0.40.0", - "websocket>=0.2.1", - "websockets>=16.0", -] +[tool.uv.workspace] +members = ["src/high_level", "src/simulation"] diff --git a/scripts/Simple.py b/scripts/Simple.py deleted file mode 100644 index b7e26308..00000000 --- a/scripts/Simple.py +++ /dev/null @@ -1,104 +0,0 @@ -from HL.Lidar import Lidar - -import time -from rpi_hardware_pwm import HardwarePWM - -IP = '192.168.0.10' -PORT = 10940 - -#paramètres de la fonction vitesse_m_s -direction_prop = 1 # -1 pour les variateurs inversés ou un petit rapport correspond à une marche avant -pwm_stop_prop = 7.53 -point_mort_prop = 0.5 -delta_pwm_max_prop = 1.1 #pwm à laquelle on atteint la vitesse maximale - -vitesse_max_m_s_hard = 8 #vitesse que peut atteindre la voiture -vitesse_max_m_s_soft = 2 #vitesse maximale que l'on souhaite atteindre - - -#paramètres de la fonction set_direction_degre -direction = 1 #1 pour angle_pwm_min a gauche, -1 pour angle_pwm_min à droite -angle_pwm_min = 6.91 #min -angle_pwm_max = 10.7 #max -angle_pwm_centre= 8.805 - -angle_degre_max = +18 #vers la gauche -angle_degre=0 - -pwm_prop = HardwarePWM(pwm_channel=0, hz=50, chip=2) #use chip 2 on pi 5 in accordance with the documentation -pwm_prop.start(pwm_stop_prop) - -def set_vitesse_m_s(vitesse_m_s): - if vitesse_m_s > vitesse_max_m_s_soft : - vitesse_m_s = vitesse_max_m_s_soft - elif vitesse_m_s < -vitesse_max_m_s_hard : - vitesse_m_s = -vitesse_max_m_s_hard - if vitesse_m_s == 0 : - pwm_prop.change_duty_cycle(pwm_stop_prop) - elif vitesse_m_s > 0 : - vitesse = vitesse_m_s * (delta_pwm_max_prop)/vitesse_max_m_s_hard - pwm_prop.change_duty_cycle(pwm_stop_prop + direction_prop*(point_mort_prop + vitesse )) - elif vitesse_m_s < 0 : - vitesse = vitesse_m_s * (delta_pwm_max_prop)/vitesse_max_m_s_hard - pwm_prop.change_duty_cycle(pwm_stop_prop - direction_prop*(point_mort_prop - vitesse )) - -def recule(): - set_vitesse_m_s(-vitesse_max_m_s_hard) - time.sleep(0.2) - set_vitesse_m_s(0) - time.sleep(0.2) - set_vitesse_m_s(-1) - -pwm_dir = HardwarePWM(pwm_channel=1,hz=50,chip=2) #use chip 2 on pi 5 in accordance with the documentation -pwm_dir.start(angle_pwm_centre) - -def set_direction_degre(angle_degre) : - global angle_pwm_min - global angle_pwm_max - global angle_pwm_centre - angle_pwm = angle_pwm_centre + direction * (angle_pwm_max - angle_pwm_min) * angle_degre /(2 * angle_degre_max ) - if angle_pwm > angle_pwm_max : - angle_pwm = angle_pwm_max - if angle_pwm < angle_pwm_min : - angle_pwm = angle_pwm_min - pwm_dir.change_duty_cycle(angle_pwm) - -#connexion et démarrage du lidar -lidar = Lidar(IP, PORT) -lidar.stop() -lidar.startContinuous(0, 1080) - - -tableau_lidar_mm = [0]*360 #création d'un tableau de 360 zéros - -time.sleep(1) #temps de démarrage du lidar - -try : - while True : - for angle in range(len(tableau_lidar_mm)) : - # translation of the angle from the lidar to the angle of the table - if angle > 135 and angle < 225: - tableau_lidar_mm[angle] = float('nan') - else: - tableau_lidar_mm[angle] = lidar.rDistance[540 + (-angle * 4)] - #l'angle de la direction est la différence entre les mesures - #des rayons du lidar à -60 et +60° - - angle_degre = 0.02*(tableau_lidar_mm[60]-tableau_lidar_mm[-60]) - print(tableau_lidar_mm[60], tableau_lidar_mm[-60], angle_degre) - set_direction_degre(angle_degre) - vitesse_m_s = 0.05 - set_vitesse_m_s(vitesse_m_s) - time.sleep(0.1) - ############################################## -except KeyboardInterrupt: #récupération du CTRL+C - vitesse_m_s = 0 - set_vitesse_m_s(vitesse_m_s) - print("fin des acquisitions") - -#arrêt et déconnexion du lidar et des moteurs -lidar.stop() -pwm_dir.stop() -pwm_prop.start(pwm_stop_prop) - - diff --git a/scripts/commande_PS4.py b/scripts/commande_PS4.py deleted file mode 100644 index a6a0428a..00000000 --- a/scripts/commande_PS4.py +++ /dev/null @@ -1,158 +0,0 @@ -from pyPS4Controller.controller import Controller -import time -import os -from threading import Thread -from src.HL.programme.programme import Program -from src.HL.Autotech_constant import MAX_ANGLE -import logging -################################################### -#Intialisation du protocole zmq -################################################## - -def envoie_donnee(Voiture): #si utilisation de la voiture directement - print("lancement de l'i2c") - import smbus - import struct - from src.HL.Autotech_constant import SLAVE_ADDRESS - - bus = smbus.SMBus(1) - while True: - try : - data = struct.pack(' 0): - self.vitesse_mms = 0 - else: - self.vitesse_mms = vit - - def on_L2_release(self): #arrete la voiture lorsque L2 est arrété d'étre préssé. - self.vitesse_mms = 0 - - def on_L3_up(self,value): - pass - def on_L3_down(self,value): - pass - def on_L3_y_at_rest(self): - pass - -if __name__ == "__main__": - controller = MyController(interface="/dev/input/js0", connecting_using_ds4drv=False) - try: - Thread(target = envoie_donnee,args=(controller,), daemon=True).start() - controller.listen(timeout=60) - - except KeyboardInterrupt: - print("Arrêt du programme") - controller.stop() - exit(0) diff --git a/scripts/displayvoltage.py b/scripts/displayvoltage.py deleted file mode 100644 index 1040c650..00000000 --- a/scripts/displayvoltage.py +++ /dev/null @@ -1,57 +0,0 @@ -import time -from luma.core.interface.serial import i2c -from luma.core.render import canvas -from luma.oled.device import ssd1306 -from PIL import Image, ImageDraw, ImageFont -import struct -import smbus #type: ignore #ignore the module could not be resolved error because it is a linux only module - -bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1 - -# I2C address of the slave -SLAVE_ADDRESS = 0x08 -# I2C configuration -serial = i2c(port=1, address=0x3C) -oled_display = ssd1306(serial) -font = ImageFont.load_default() - -def write_data(data): - # Convert string to list of ASCII values - data_list = [ord(char) for char in data] - bus.write_i2c_block_data(SLAVE_ADDRESS, 0, data_list) - -def read_data(num_floats=3): - - # Each float is 4 bytes - length = num_floats * 4 - # Read a block of data from the slave - data = bus.read_i2c_block_data(SLAVE_ADDRESS, 0, length) - # Convert the byte data to floats - if len(data) >= length: - float_values = struct.unpack('f' * num_floats, bytes(data[:length])) - return list(float_values) - else: - raise ValueError("Not enough data received from I2C bus") - -def displayvoltage(): - received = read_data(2) # Adjust length as needed - received= [ round(elem, 2) for elem in received ] - for i in range(len(received)): - if received[i] < 6: - received[i] = 0.0 - # print(f"Received from slave: {received}") - with canvas(oled_display) as draw: - display_height = oled_display.height - text = f"LiP:{received[0]:.2f}V|NiH:{received[1]:.2f}V" - # _, text_height = draw.textsize(text, font=font) # Get the width and height of the text - # text_height = draw.textlength(text, font=font, direction="ttb") - text_height = 11 - print(f"Text height: {text_height}") - text_y_position = display_height - text_height # Position text at the bottom - draw.text((0, text_y_position), text, fill="white", font=font) - - -if __name__ == "__main__": - while True: - displayvoltage() - \ No newline at end of file diff --git a/scripts/launch_train_multiprocessing.py b/scripts/launch_train_multiprocessing.py deleted file mode 100644 index 6ec5ba8a..00000000 --- a/scripts/launch_train_multiprocessing.py +++ /dev/null @@ -1,133 +0,0 @@ -import logging -import os -import sys - -from typing import * - -import torch.nn as nn - -from stable_baselines3 import PPO -from stable_baselines3.common.vec_env import SubprocVecEnv - -simu_path = __file__.rsplit('/', 2)[0] + '/src/Simulateur' -if simu_path not in sys.path: - sys.path.insert(0, simu_path) - -from config import * -from TemporalResNetExtractor import TemporalResNetExtractor -from CNN1DResNetExtractor import CNN1DResNetExtractor -from onnx_utils import * - -from WebotsSimulationGymEnvironment import WebotsSimulationGymEnvironment -if LOG_LEVEL == logging.DEBUG: from DynamicActionPlotCallback import DynamicActionPlotDistributionCallback - - -if __name__ == "__main__": - - if not os.path.exists("/tmp/autotech/"): - os.mkdir("/tmp/autotech/") - - os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') - - - def make_env(simulation_rank: int, vehicle_rank: int): - if LOG_LEVEL == logging.DEBUG: - print("CAREFUL !!! created an SERVER env with {simulation_rank}_{vehicle_rank}") - return WebotsSimulationGymEnvironment(simulation_rank, vehicle_rank) - - envs = SubprocVecEnv([lambda simulation_rank=simulation_rank, vehicle_rank=vehicle_rank : make_env(simulation_rank, vehicle_rank) for vehicle_rank in range(n_vehicles) for simulation_rank in range(n_simulations)]) - - ExtractorClass = CNN1DResNetExtractor - - policy_kwargs = dict( - features_extractor_class=ExtractorClass, - features_extractor_kwargs=dict( - context_size=context_size, - lidar_horizontal_resolution=lidar_horizontal_resolution, - camera_horizontal_resolution=camera_horizontal_resolution, - device=device - ), - activation_fn=nn.ReLU, - net_arch=[512, 512, 512], - ) - - - ppo_args = dict( - n_steps=4096, - n_epochs=10, - batch_size=256, - learning_rate=3e-4, - gamma=0.99, - verbose=1, - normalize_advantage=True, - device=device - ) - - - save_path = __file__.rsplit("/", 1)[0] + "/checkpoints/" + ExtractorClass.__name__ + "/" - os.makedirs(save_path, exist_ok=True) - - - print(save_path) - print(os.listdir(save_path)) - - valid_files = [x for x in os.listdir(save_path) if x.rstrip(".zip").isnumeric()] - - if valid_files: - model_name = max( - valid_files, - key=lambda x : int(x.rstrip(".zip")) - ) - print(f"Loading model {save_path + model_name}") - model = PPO.load( - save_path + model_name, - envs, - **ppo_args, - policy_kwargs=policy_kwargs - ) - i = int(model_name.rstrip(".zip")) + 1 - print(f"----- Model found, loading {model_name} -----") - - else: - model = PPO( - "MlpPolicy", - envs, - **ppo_args, - policy_kwargs=policy_kwargs - ) - - i = 0 - print("----- Model not found, creating a new one -----") - - print("MODEL HAS HYPER PARAMETERS:") - print(f"{model.learning_rate=}") - print(f"{model.gamma=}") - print(f"{model.verbose=}") - print(f"{model.n_steps=}") - print(f"{model.n_epochs=}") - print(f"{model.batch_size=}") - print(f"{model.device=}") - - print("SERVER : finished executing") - - # obs = envs.reset() - # while True: - # action, _states = model.predict(obs, deterministic=True) # Use deterministic=True for evaluation - # obs, reward, done, info = envs.step(action) - # envs.render() # Optional: visualize the environment - - - while True: - export_onnx(model) - test_onnx(model) - - if LOG_LEVEL <= logging.DEBUG: - model.learn(total_timesteps=500_000, callback=DynamicActionPlotDistributionCallback()) - else: - model.learn(total_timesteps=500_000) - - print("iteration over") - - model.save(save_path + str(i)) - - i += 1 diff --git a/scripts/remote_control_controller.py b/scripts/remote_control_controller.py deleted file mode 100644 index 9dd2b2e5..00000000 --- a/scripts/remote_control_controller.py +++ /dev/null @@ -1,103 +0,0 @@ -import pygame -import zmq -import time -from threading import Thread -import socket -import struct -################################################### -# Init ZMQ -################################################### -sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - -def envoie_donnee(): - global vitesse_m, direction - while True: - packet = struct.pack("ff", vitesse_m, direction) - sock.sendto(packet, ("192.168.1.10", 5556)) - time.sleep(0.05) - -################################################### -# Paramètres véhicule -################################################### -direction = 0 -vitesse_m = 0 - -vitesse_max_m_s_soft = 2 -vitesse_min_m_s_soft = -2 -angle_degre_max = 18 - -def map_range(x, in_min, in_max, out_min, out_max): - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min - -def set_direction_degre(angle_degre): - global direction - direction = angle_degre - print(direction, vitesse_m) - -def set_vitesse_m_ms(vit): - global vitesse_m - vitesse_m = vit - - -if __name__ == "__main__": - - - ################################################### - # Init pygame + manette - ################################################### - pygame.init() - pygame.joystick.init() - - if pygame.joystick.get_count() == 0: - print("Aucune manette détectée") - exit(1) - - joy = pygame.joystick.Joystick(0) - joy.init() - print("Manette détectée:", joy.get_name()) - - ################################################### - # Boucle principale - ################################################### - Thread(target=envoie_donnee, daemon=True).start() - - try: - while True: - pygame.event.pump() - - # Axes : - # Pour Xbox/PS4 USB : - # L2 = axis 2 (souvent 0..1) - # R2 = axis 5 (souvent 0..1) - # Stick gauche horizontal = axis 0 (-1..1) - - axis_lx = joy.get_axis(0) # Gauche droite - axis_l2 = joy.get_axis(2) # Accélération inverse - axis_r2 = joy.get_axis(5) # Accélération - - # Direction - direction = map_range(axis_lx, -1, 1, -angle_degre_max, angle_degre_max) - set_direction_degre(round(direction)) - - # Accélération - accel = (axis_r2 + 1)/2 - brake = (axis_l2 + 1)/2 - - # Certaines manettes vont de -1..1, d'autres 0..1 - - # Avant - if accel > 0.05: - vit = accel * vitesse_max_m_s_soft * 1000 - set_vitesse_m_ms(round(vit)) - - # Arrière - elif brake > 0.05: - vit = brake * vitesse_min_m_s_soft * 1000 - set_vitesse_m_ms(round(vit)) - else : - set_vitesse_m_ms(0) - time.sleep(0.01) - - except KeyboardInterrupt: - print("Fin du programme.") - pygame.quit() diff --git a/scripts/tune_pwm_direction.py b/scripts/tune_pwm_direction.py deleted file mode 100644 index db7b9f71..00000000 --- a/scripts/tune_pwm_direction.py +++ /dev/null @@ -1,85 +0,0 @@ -from rpi_hardware_pwm import HardwarePWM -import time - -#paramètres de départ, avec des butées très proche du centre -direction = -1 #1 pour angle_pwm_min a gauche, -1 pour angle_pwm_min à droite -angle_pwm_min = 6.91 #min -angle_pwm_max = 10.7 #max -angle_pwm_centre= 8.805 - -angle_degre_max = +18 #vers la gauche -angle_degre=0 - -pwm_dir = HardwarePWM(pwm_channel=1,hz=50,chip=2) #use chip 2 on pi 5 in accordance with the documentation -pwm_dir.start(angle_pwm_centre) - -def set_direction_degre(angle_degre) : - global angle_pwm_min - global angle_pwm_max - global angle_pwm_centre - angle_pwm = angle_pwm_centre + direction * (angle_pwm_max - angle_pwm_min) * angle_degre /(2 * angle_degre_max ) - if angle_pwm > angle_pwm_max : - angle_pwm = angle_pwm_max - if angle_pwm < angle_pwm_min : - angle_pwm = angle_pwm_min - pwm_dir.change_duty_cycle(angle_pwm) - -print("réglage des butées, Q pour quitter") -print("valeur numérique pour tester un angle de direction") -print("I pour inverser droite et gauche") -print("g pour diminuer la butée gauche et G pour l'augmenter") -print("d pour diminuer la butée droite et D pour l'augmenter") - -while True : - a = input("angle, I, g, G, d, D ?") - try : - angle_degre=int(a) - set_direction_degre(angle_degre) - except : - if a == "I" : - direction = -direction - print("nouvelle direction : " + str(direction)) - elif a == "g" : - if direction == 1 : - angle_pwm_max -=0.1 - print("nouvelle butée gauche : " + str(angle_pwm_max)) - else : - angle_pwm_min +=0.1 - print("nouvelle butée gauche : " + str(angle_pwm_min)) - angle_pwm_centre = (angle_pwm_max+angle_pwm_min)/2 - set_direction_degre(18) - elif a == "G" : - if direction == 1 : - angle_pwm_max +=0.1 - print("nouvelle butée gauche : " + str(angle_pwm_max)) - else : - angle_pwm_min -=0.1 - print("nouvelle butée gauche : " + str(angle_pwm_min)) - angle_pwm_centre = (angle_pwm_max+angle_pwm_min)/2 - set_direction_degre(18) - elif a == "d" : - if direction == -1 : - angle_pwm_max -=0.1 - print("nouvelle butée droite : " + str(angle_pwm_max)) - else : - angle_pwm_min +=0.1 - print("nouvelle butée droite : " + str(angle_pwm_min)) - angle_pwm_centre = (angle_pwm_max+angle_pwm_min)/2 - set_direction_degre(-18) - elif a == "D" : - if direction == -1 : - angle_pwm_max +=0.1 - print("nouvelle butée droite : " + str(angle_pwm_max)) - else : - angle_pwm_min -=0.1 - print("nouvelle butée droite : " + str(angle_pwm_min)) - angle_pwm_centre = (angle_pwm_max+angle_pwm_min)/2 - set_direction_degre(-18) - else : - break - -print("nouvelles valeurs") -print("direction : " + str(direction)) -print("angle_pwm_min : " + str(angle_pwm_min)) -print("angle_pwm_max : " + str(angle_pwm_max)) -print("angle_pwm_centre : " + str(angle_pwm_centre)) \ No newline at end of file diff --git a/scripts/tune_pwm_propulsion.py b/scripts/tune_pwm_propulsion.py deleted file mode 100644 index 5bb65a4c..00000000 --- a/scripts/tune_pwm_propulsion.py +++ /dev/null @@ -1,96 +0,0 @@ -from rpi_hardware_pwm import HardwarePWM -import time -import sys -import os -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from src.HL.Autotech_constant import PWM_PROP -# ...existing code... - -#paramètres de la fonction vitesse_m_s, à étalonner -direction_prop = PWM_PROP["direction_prop"] # 1 pour les variateurs à droite ou un petit rapport correspond à une marche avant -pwm_stop_prop = PWM_PROP["pwm_stop_prop"] -point_mort_prop = PWM_PROP["point_mort_prop"] -delta_pwm_max_prop = PWM_PROP["delta_pwm_max_prop"] #pwm à laquelle on atteint la vitesse maximale - -vitesse_max_m_s_hard = 8 #vitesse que peut atteindre la voiture -vitesse_max_m_s_soft = 2 #vitesse maximale que l'on souhaite atteindre - -pwm_prop = HardwarePWM(pwm_channel=0, hz=50, chip=2) #use chip 2 on pi 5 in accordance with the documentation -pwm_prop.start(pwm_stop_prop) - -def set_vitesse_m_s(vitesse_m_s): - if vitesse_m_s > vitesse_max_m_s_soft : - vitesse_m_s = vitesse_max_m_s_soft - elif vitesse_m_s < -vitesse_max_m_s_hard : - vitesse_m_s = -vitesse_max_m_s_hard - if vitesse_m_s == 0 : - pwm_prop.change_duty_cycle(pwm_stop_prop) - elif vitesse_m_s > 0 : - vitesse = vitesse_m_s * (delta_pwm_max_prop)/vitesse_max_m_s_hard - pwm_prop.change_duty_cycle(pwm_stop_prop + direction_prop*(point_mort_prop + vitesse )) - elif vitesse_m_s < 0 : - vitesse = vitesse_m_s * (delta_pwm_max_prop)/vitesse_max_m_s_hard - pwm_prop.change_duty_cycle(pwm_stop_prop - direction_prop*(point_mort_prop - vitesse )) - -def recule(): - set_vitesse_m_s(-vitesse_max_m_s_hard) - time.sleep(0.2) - set_vitesse_m_s(0) - time.sleep(0.2) - set_vitesse_m_s(-1) - -print("réglage des butées, Q pour quitter") -print("valeur numérique pour tester une vitesse en mm/s") -print("R pour reculer") -print("I pour inverser droite et gauche") -print("p pour diminuer delta_pwm_max_prop et P pour l'augmenter") -print("z pour diminuer le point zéro 1,5 ms et Z pour l'augmenter") -print("m pour diminuer le point mort et M pour l'augmenter") - - -while True : - a = input("vitesse en mm/s, R, I, p, P, z, Z, m, M") - try : - vitesse_mm_s=int(a) - set_vitesse_m_s(vitesse_mm_s/1000.0) - except : - if a == "I" or a == "i" : - direction_prop = -direction_prop - print("nouvelle direction : " + str(direction_prop)) - elif a == "R" : - recule() - print("recule") - elif a == "p" : - delta_pwm_max_prop -=0.1 - print("nouveau delta_pwm_max_prop : " + str(delta_pwm_max_prop)) - pwm_prop.change_duty_cycle(pwm_stop_prop + direction_prop*(point_mort_prop+delta_pwm_max_prop)) - elif a == "P" : - delta_pwm_max_prop +=0.1 - print("nouveau delta_pwm_max_prop : " + str(delta_pwm_max_prop)) - pwm_prop.change_duty_cycle(pwm_stop_prop + direction_prop*(point_mort_prop+delta_pwm_max_prop)) - elif a == "z" : - pwm_stop_prop -=0.01 - print("nouveau pwm_stop_prop : " + str(pwm_stop_prop)) - pwm_prop.change_duty_cycle(pwm_stop_prop) - elif a == "Z" : - pwm_stop_prop +=0.01 - print("nouveau pwm_stop_prop : " + str(pwm_stop_prop)) - pwm_prop.change_duty_cycle(pwm_stop_prop) - elif a == "m" : - point_mort_prop -=0.01 - print("nouveau point_mort_prop : " + str(point_mort_prop)) - pwm_prop.change_duty_cycle(pwm_stop_prop + direction_prop*(point_mort_prop)) - elif a == "M" : - point_mort_prop +=0.01 - print("nouveau point_mort_prop : " + str(point_mort_prop)) - pwm_prop.change_duty_cycle(pwm_stop_prop + direction_prop*(point_mort_prop)) - else : - break - -pwm_prop.change_duty_cycle(pwm_stop_prop) -print("nouvelles valeurs") -print("direction : " + str(direction_prop)) -print("delta_pwm_max_prop : " + str(delta_pwm_max_prop)) -print("point zero 1,5 ms : "+ str(pwm_stop_prop)) -print("point mort : " + str(point_mort_prop)) \ No newline at end of file diff --git a/src/HL/README.md b/src/HL/README.md deleted file mode 100644 index 42c2e728..00000000 --- a/src/HL/README.md +++ /dev/null @@ -1 +0,0 @@ -[HokuyoReader.py](HokuyoReader.py) now Lidar.py is included with permission from the author [cassc](https://gist.github.com/cassc/26ac479624cb028b2567491a68c34fb8) who graciously released his code under MIT. \ No newline at end of file diff --git a/src/HL/Serveur_mq.py b/src/HL/Serveur_mq.py deleted file mode 100644 index 94496612..00000000 --- a/src/HL/Serveur_mq.py +++ /dev/null @@ -1,282 +0,0 @@ -import zmq -context = zmq.Context() -import cv2 -import time -import threading -import smbus -import logging -import struct -import os, signal - - -from luma.core.interface.serial import i2c -from luma.core.render import canvas -from luma.oled.device import ssd1306 -from PIL import Image, ImageDraw, ImageFont -from gpiozero import LED, Button, Buzzer -import textwrap -import socket - -from src.HL.programme.scripts.get_ip import check_ssh_connections -import subprocess -from src.HL.actionneur_capteur.Lidar import Lidar -from src.HL.actionneur_capteur.Camera import Camera -from src.HL.actionneur_capteur.ToF import ToF -from src.HL.actionneur_capteur.masterI2C import I2c_arduino -from Autotech_constant import SOCKET_ADRESS, SLAVE_ADDRESS - -#différent programme -from scripts.commande_PS4 import PS4ControllerProgram -from src.HL.programme.SshProgramme import SshProgramme -from src.HL.programme.RemoteControl import RemoteControl -from src.HL.programme.Poweroff import Poweroff -from src.HL.actionneur_capteur.Camera import ProgramStreamCamera -from src.HL.programme.module_initialisation import Initialisation -from src.HL.programme.Car import Ai_Programme -from backend import BackendAPI - -from Autotech_constant import I2C_NUMBER_DATA_RECEIVED, I2C_SLEEP_RECEIVED, I2C_SLEEP_ERROR_LOOP, TEXT_HEIGHT, TEXT_LEFT_OFFSET - - -# on utilise tcp pour les infos des différents informations -telemetry = context.socket(zmq.REP) -telemetry.bind("tcp://0.0.0.0:5557") - - -class Serveur(): - - def __init__(self): - self.log = logging.getLogger(__name__) - #initialisation des différents module qui tourne tout le temps - self.log.info("Initialisation du serveur") - - # initialisation des boutons et de l'i2c - self.bp_next = Button("GPIO5", bounce_time=0.1) - self.bp_entre = Button("GPIO6", bounce_time=0.1) - - self.led1 = LED("GPIO17") - self.led2 = LED("GPIO27") - self.buzzer = Buzzer("GPIO26") - self.log.info("GPIO: boutons, LEDs, buzzer initialisés") - - self.serial = i2c(port=1, address=0x3C) - self.device = ssd1306(self.serial) - self.bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1 - self.log.info("I2C: bus ouvert sur /dev/i2c-1") - - self.length_i2c_received = I2C_NUMBER_DATA_RECEIVED #nombre de donnée à récupéré de l'arduino (voltage lipo, voltage nimh) - - # initialisation des commande de temps - self.initial_time = time.time() - self.last_cmd_time = time.time() - - #donnée des process - self.process_output = "" - self.last_programme_control = 0 - self.process = None - self.temp = None - - self.initialisation_module = Initialisation(self,Camera,Lidar,ToF, I2c_arduino) - - self.programme = [SshProgramme(), - self.initialisation_module, - Ai_Programme(self), - PS4ControllerProgram(), - RemoteControl(), - ProgramStreamCamera(self), - BackendAPI(self, host="0.0.0.0", port=8001, site_dir="/home/intech/CoVAPSy/src/HL/site_controle"), - Poweroff()] - self.log.debug("Programmes chargés: %s", [type(p).__name__ for p in self.programme]) - - # donnée de l'écran - self.Screen = 0 - self.State = 0 - self.scroll_offset = 3 - - - @property - def camera(self): - return self.initialisation_module.camera - - @property - def lidar(self): - return self.initialisation_module.lidar - - @property - def tof(self): - return self.initialisation_module.tof - - @property - def arduino_I2C(self): - return self.initialisation_module.arduino_I2C - - @property - def target_speed(self): - return self.programme[self.last_programme_control].target_speed - - @property - def direction(self): - return self.programme[self.last_programme_control].direction - - #----------------------------------------------------------------------------------------------------- - # affichage de l'écrans - #----------------------------------------------------------------------------------------------------- - def affichage_oled(self,selected): #test non utilisé - im = Image.new("1", (128, 64), "black") - draw = ImageDraw.Draw(im) - font = ImageFont.load_default() - - for num, i in enumerate(range(max(selected - self.scroll_offset, 0), min(len(self.programme), selected + self.scroll_offset))): - y = num * TEXT_HEIGHT - - if i == selected: - draw.rectangle((0, y, 127, y + TEXT_HEIGHT), fill="white") - draw.text((3, y), self.programme[i]["name"], fill="black", font=font) - else: - draw.text((3, y), self.programme[i]["name"], fill="white", font=font) - - with canvas(self.device) as display: - display.bitmap((0, 0), im, fill="white") - - def make_voltage_im(self): - """crée l'image de la derniére ligne qui affiche le voltage des deux batterie de la pi en temps réel""" - if self.arduino_I2C is not None: - received = [self.arduino_I2C.voltage_lipo , self.arduino_I2C.voltage_nimh] - else: - received = [0.0, 0.0] - - # filter out values below 6V and round to 2 decimal places - received = [round(elem, 2) if elem > 6 else 0.0 for elem in received] - text = f"LiP:{received[0]:.2f}V|NiH:{received[1]:.2f}V" - im = Image.new("1", (128, TEXT_HEIGHT), "black") - draw = ImageDraw.Draw(im) - font = ImageFont.load_default() - draw.text((3, 0), text, fill="white", font=font) - return im - - def display_combined_im(self,text): - """ fonction qui écris sur l'écran le texte qu'on lui fourni (et remet par dessus toujours le voltage des batteries)""" - im = Image.new("1", (128, 64), "black") - draw = ImageDraw.Draw(im) - font = ImageFont.load_default() - - # Wrap the text to fit within the width of the display - wrapped_text = textwrap.fill(text, width=20) # Adjust width as needed - draw.text((3, 0), wrapped_text, fill="white", font=font) - - voltage_im = self.make_voltage_im() - im.paste(voltage_im, (0, 64 - TEXT_HEIGHT)) - - with canvas(self.device) as draw: - draw.bitmap((0, 0), im, fill="white") - - - def Idle(self): - """ - gére l'affichage de l'écrans en fonction des fonction en cour ou choisie. - le changement d'écran est géré par les fonction des boutons juste en dessous - """ - if check_ssh_connections(): - self.led1.on() - - if not check_ssh_connections(): - self.led1.off() - - if (self.Screen < len(self.programme)): - text = self.programme[self.Screen].display() - self.display_combined_im(text) - - def bouton_next(self): - """ passe à l'écrans suivant (juste visuelle)""" - self.Screen+=1 - if self.Screen>=len(self.programme): - self.Screen=0 - - def bouton_entre(self,num=None): - """séléctionne le programme afficher à l'acrans et le lance""" - if num!=None: - self.Screen = num - self.State=self.Screen - self.start_process(self.Screen) - - - #--------------------------------------------------------------------------------------------------- - # Processus - #--------------------------------------------------------------------------------------------------- - - - def start_process(self,num_programme): - """lance le porgramme référencé avec son numéro: - si il sagit d'un programme qui controle la voiture il kill lancient programme qui controlé, - sinon le programme est lancé ou tué celon si il était déjà lancé ou tué avant""" - self.log.info("Action utilisateur: programme %d (%s)", - num_programme, - type(self.programme[num_programme]).__name__) - if self.programme[num_programme].running: - self.programme[num_programme].kill() - if self.programme[num_programme].controls_car: - self.last_programme_control = 0 - self.log.warning( - "Changement de contrôle voiture: %s -> %s", - type(self.programme[num_programme]).__name__, - type(self.programme[self.last_programme_control]).__name__ - ) - - - self.log.info("Arrêt du programme %s", - type(self.programme[num_programme]).__name__) - - elif self.programme[num_programme].controls_car: - self.programme[self.last_programme_control].kill() - self.programme[num_programme].start() - self.log.warning( - "Changement de contrôle voiture: %s -> %s", - type(self.programme[self.last_programme_control]).__name__, - type(self.programme[num_programme]).__name__ - ) - self.last_programme_control = num_programme - - - else: - self.programme[num_programme].start() - - - #--------------------------------------------------------------------------------------------------- - # car function - #--------------------------------------------------------------------------------------------------- - - def main(self): - self.bp_next.when_pressed = self.bouton_next - self.bp_entre.when_pressed = self.bouton_entre - - self.log.info("Serveur démarré, entrée dans la boucle principale") - - while True: - self.Idle() - -#--------------------------------------------------------------------------------------------------- -# main -#--------------------------------------------------------------------------------------------------- - -if __name__ == "__main__": - - logging.basicConfig( - level=logging.INFO, - format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", - handlers=[ - logging.FileHandler("/home/intech/CoVAPSy/covapsy.log"), - logging.StreamHandler() - ] - - ) - log_serveur = logging.getLogger("__main__") - log_serveur.setLevel(level=logging.DEBUG) - - log_serveur = logging.getLogger("src.HL") - log_serveur.setLevel(level=logging.DEBUG) - - log_lidar = logging.getLogger("src.HL.actionneur_capteur.Lidar") - log_lidar.setLevel(level=logging.INFO) - - boot = Serveur() - boot.main() diff --git a/src/HL/__init__.py b/src/HL/__init__.py deleted file mode 100644 index 93d8406c..00000000 --- a/src/HL/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#Fuck la gestion de module de python -# On secod thought, pas sure que __init__.py soit necessaire \ No newline at end of file diff --git a/src/HL/actionneur_capteur/Camera.py b/src/HL/actionneur_capteur/Camera.py deleted file mode 100644 index c561f547..00000000 --- a/src/HL/actionneur_capteur/Camera.py +++ /dev/null @@ -1,338 +0,0 @@ -import cv2 -from picamera2 import Picamera2 # type: ignore -from PIL import Image -import numpy as np -import os -import logging as log -import threading -import shutil -import scipy as sp -import time -import logging - -from picamera2 import Picamera2 -from picamera2.encoders import JpegEncoder -from picamera2.outputs import FileOutput -N_IMAGES = 100 # Number of images to capture -SAVE_DIR = "Captured_Frames" # Directory to save frames -DEBUG_DIR = "Debug" # Directory for debug images -DEBUG_DIR_wayfinding = "Debug_Wayfinding" # Directory for wayfinding debug images -COLOUR_KEY = { - "green": 1, - "red": -1, - "none": 0 -} -COLOR_THRESHOLD = 20 # Threshold for color intensity difference -Y_OFFSET = -80 # Offset for the y-axis in the image - -from picamera2.outputs import Output - -class JpegCallback(Output): - def __init__(self, parent_cam): - super().__init__() - self.parent = parent_cam - - def outputframe(self, frame, keyframe=True): - # frame = bytes JPEG - self.parent._on_new_frame(frame) - - - - -from src.HL.programme.Camera_serv import StreamServer, StreamHandler, StreamOutput, frame_buffer -from src.HL.programme.programme import Program -from src.HL.Autotech_constant import PORT_STREAMING_CAMERA, SIZE_CAMERA_X, SIZE_CAMERA_Y, FRAME_RATE, CAMERA_QUALITY, STREAM_PATH, CAMERA_STREAM_ON_START - -class ProgramStreamCamera(Program): - def __init__(self,serveur): - super().__init__() - self.log = logging.getLogger(__name__) - self.serveur = serveur - self.running = False - self.controls_car = False - - if CAMERA_STREAM_ON_START: - self.start() - - - @property - def camera(self): - # accès dynamique - return self.serveur.camera - - - def start(self): - cam = self.camera - if cam is None: - self.log.error("Camera not initialized yet") - return - - self.running = True - self.camera.start_stream() - - def kill(self): - self.running = False - self.camera.stop_stream() - - - - -class Camera: - def __init__(self, size=(SIZE_CAMERA_X, SIZE_CAMERA_Y), port=PORT_STREAMING_CAMERA): - self.size = size - self.port = port - - self.streaming = False - self.stream_thread = None - self.picam2 = None - - self.last_frame = None - self.debug_counter = 0 - self.image_no = 0 - - - # Démarrage en mode "acquisition locale sans stream" - self._start_local_capture() - - - # ---------------------------------------------------------- - # Capture locale (sans MJPEG server) - # ---------------------------------------------------------- - def _start_local_capture(self): - self.picam2 = Picamera2() - config = self.picam2.create_video_configuration( - main={"size": self.size}, # plus large, moins zoomé - controls={"FrameRate": FRAME_RATE} # FPS stable - ) - - self.picam2.configure(config) - self.output = StreamOutput() - - # Qualité JPEG custom - self.picam2.start_recording(JpegEncoder(q=CAMERA_QUALITY), FileOutput(self.output)) - - # thread lecture last_frame - self.capture_thread = threading.Thread( - target=self._update_last_frame_loop, - daemon=True - ) - self.capture_thread.start() - - - def _update_last_frame_loop(self): - """Récupère en continu la dernière frame JPEG.""" - while True: - jpeg = frame_buffer.get() - if jpeg: - np_frame = cv2.imdecode(np.frombuffer(jpeg, np.uint8), cv2.IMREAD_COLOR) - if np_frame is not None: - self.last_frame = cv2.cvtColor(np_frame, cv2.COLOR_BGR2RGB) - time.sleep(0.01) - - - # ---------------------------------------------------------- - # Contrôle streaming MJPEG - # ---------------------------------------------------------- - def start_stream(self): - if self.streaming: - return - import src.HL.programme.Camera_serv - src.HL.programme.Camera_serv.streaming_enabled = True - - self.httpd = StreamServer(("", self.port), StreamHandler) - - def run_server(): - print(f"[INFO] MJPEG stream on http://:{self.port}/{STREAM_PATH}.mjpg") - try: - self.httpd.serve_forever() - except Exception as e: - print("Serveur MJPEG arrêté:", e) - - self.stream_thread = threading.Thread(target=run_server, daemon=True) - self.stream_thread.start() - self.streaming = True - - - - def stop_stream(self): - if not self.streaming: - return - - import src.HL.programme.Camera_serv - src.HL.programme.Camera_serv.streaming_enabled = False - - print("[INFO] Shutting down MJPEG server...") - - self.httpd.shutdown() - self.httpd.server_close() - self.stream_thread.join() - - self.streaming = False - print("[INFO] Stream stopped.") - - - - - def toggle_stream(self): - if self.streaming: - print("[INFO] Stopping stream") - self.stop_stream() - else: - print("[INFO] Starting stream") - self.start_stream() - - - # ---------------------------------------------------------- - # Interface publique - # ---------------------------------------------------------- - def get_last_image(self): - return self.last_frame - - - def camera_matrix(self, vector_size=128, image=None): - """ - Create a matrix of -1, 0, and 1 for a line in the image. The matrix size is 128. - """ - if image is None: - image = self.get_last_image() - height, width, _ = image.shape - if vector_size > width: - raise ValueError("Vector size cannot be greater than image width") - - # Slice the middle 5% of the image height - sliced_image = image[height // 2 - height // 40 + Y_OFFSET: height // 2 + height // 40 + Y_OFFSET, :, :] - - # Ensure the width of the sliced image is divisible by vector_size - adjusted_width = (width // vector_size) * vector_size - sliced_image = sliced_image[:, :adjusted_width, :] - - # Initialize the output matrix - output_matrix = np.zeros(vector_size, dtype=int) - bucket_size = adjusted_width // vector_size - - # Calculate red and green intensities for all segments at once - reshaped_red = sliced_image[:, :, 0].reshape(sliced_image.shape[0], vector_size, bucket_size) - reshaped_green = sliced_image[:, :, 1].reshape(sliced_image.shape[0], vector_size, bucket_size) - red_intensities = np.mean(reshaped_red, axis=(0, 2)) - green_intensities = np.mean(reshaped_green, axis=(0, 2)) - - # Determine the color for each segment - output_matrix[red_intensities > green_intensities + COLOR_THRESHOLD] = COLOUR_KEY["red"] - output_matrix[green_intensities > red_intensities + COLOR_THRESHOLD] = COLOUR_KEY["green"] - output_matrix[np.abs(red_intensities - green_intensities) <= COLOR_THRESHOLD] = COLOUR_KEY["none"] - - # Recreate the image from the matrix - if log.getLogger().isEnabledFor(log.DEBUG): - path= os.path.join(DEBUG_DIR, f"debug_combined_image{self.debug_counter}.jpg") - self.recreate_image_from_matrix(sliced_image, output_matrix, adjusted_width, vector_size).save(path) - - - return output_matrix - - def recreate_image_from_matrix(self, image, matrix, adjusted_width, vector_size=128): - """ - Recreate an image from the matrix of -1, 0, and 1 and append it to the bottom of the sliced image. - """ - - # Create a blank image (20 pixels high) - recreated_image = np.zeros((20, vector_size, 3), dtype=np.uint8) - recreated_image[:, matrix == COLOUR_KEY["red"], :] = [255, 0, 0] # Red - recreated_image[:, matrix == COLOUR_KEY["green"], :] = [0, 255, 0] # Green - recreated_image[:, matrix == COLOUR_KEY["none"], :] = [128, 128, 128] # Gray - - # Resize the recreated image to match the width of the sliced image - scale_factor = adjusted_width // vector_size - recreated_image_resized = np.repeat(recreated_image, scale_factor, axis=1) - - # Adjust the width of the recreated image to match the sliced image - if recreated_image_resized.shape[1] > adjusted_width: - recreated_image_resized = recreated_image_resized[:, :adjusted_width, :] - elif recreated_image_resized.shape[1] < adjusted_width: - padding = adjusted_width - recreated_image_resized.shape[1] - recreated_image_resized = np.pad( - recreated_image_resized, - ((0, 0), (0, padding), (0, 0)), - mode="constant", - constant_values=0, - ) - recreated_image_resized[:, -padding:, 2] = 255 # Blue channel for padding - - # Append the recreated image to the bottom of the sliced image - combined_image = np.vstack((image, recreated_image_resized)) - self.debug_counter += 1 - return Image.fromarray(combined_image).convert("RGB") - - def is_green_or_red(self,lidar): - """ - Check if the car is facing a green or red wall by analyzing the bottom half of the image. - """ - image = self.get_last_image() - height, _, _ = image.shape - bottom_half = image[height // 2:, :, :] # Slice the bottom half of the image - lidar= np.max(sp.ndimage.zoom(lidar[595:855], image.shape[1]/len(lidar[595:855]),mode="nearest")[None,:],0) # Resize lidar data to match the image size - print((lidar < 0.5).sum()) - print(f"min lidar: {lidar.min()}, max lidar: {lidar.max()}") - red_intensity = np.mean(bottom_half[:, :, 0]*(lidar < 0.5)) # Red channel in RGB - green_intensity = np.mean(bottom_half[:, :, 1]*(lidar < 0.5)) # Green channel in RGB - - if green_intensity > red_intensity + COLOR_THRESHOLD: - return COLOUR_KEY["green"] - elif red_intensity > green_intensity + COLOR_THRESHOLD: - return COLOUR_KEY["red"] - return COLOUR_KEY["none"] - - def is_running_in_reversed(self, image = None, LEFT_IS_GREEN=True): - """ - Check if the car is running in reverse. - If the car is in reverse, green will be on the right side of the image and red on the left. - """ - if image is None: - image = self.get_last_image() - matrix = self.camera_matrix(image=image) - if COLOUR_KEY["green"] not in matrix or COLOUR_KEY["red"] not in matrix: - # If there are no green or no red pixels, return False - return False - green_indices = (matrix == COLOUR_KEY["green"]) * np.arange(1, len(matrix) + 1) - average_green_index = np.mean(green_indices[green_indices > 0]) # Average index of green - - red_indices = (matrix == COLOUR_KEY["red"]) * np.arange(1, len(matrix) + 1) - average_red_index = np.mean(red_indices[red_indices > 0]) # Average index of redcolor is red - - if LEFT_IS_GREEN and average_red_index > average_green_index: - if log.getLogger().isEnabledFor(log.DEBUG): - log.debug(f"green: {average_green_index}, red: {average_red_index}") - vector_size = 128 - self.debug_counter += 1 - height, width, _ = image.shape - sliced_image = image[height // 2 - height // 40 + Y_OFFSET: height // 2 + height // 40 + Y_OFFSET, :, :] - - # Ensure the width of the sliced image is divisible by vector_size - adjusted_width = (width // vector_size) * vector_size - sliced_image = sliced_image[:, :adjusted_width, :] - debug_slice_image=self.recreate_image_from_matrix(sliced_image, matrix, adjusted_width, vector_size) - - debug_slice_image.save(os.path.join(DEBUG_DIR_wayfinding, f"wrong_direction_{self.debug_counter}_slice.jpg")) - Image.fromarray(image).convert("RGB").save(os.path.join(DEBUG_DIR_wayfinding, f"wrong_direction{self.debug_counter}.jpg")) - return True - elif not LEFT_IS_GREEN and average_green_index > average_red_index: - return True - -if __name__ == "__main__": - log.basicConfig(level=log.DEBUG) - - camera = Camera() - - print("Attente frame...") - while camera.get_last_image() is None: - time.sleep(0.05) - - frame = camera.get_last_image() - matrix = camera.camera_matrix() - print("camera_matrix OK") - - input("Appuyer pour lancer le stream...") - camera.toggle_stream() - - while True: - if input("Toggle ? ") == "o": - camera.toggle_stream() diff --git a/src/HL/actionneur_capteur/ToF.py b/src/HL/actionneur_capteur/ToF.py deleted file mode 100644 index 2fc75b5e..00000000 --- a/src/HL/actionneur_capteur/ToF.py +++ /dev/null @@ -1,27 +0,0 @@ -import time -import board -import busio -from adafruit_vl53l0x import VL53L0X -import logging - -class ToF: - """ - Class representing a Time of Flight (ToF) sensor. - """ - - def __init__(self): - self.log = logging.getLogger(__name__) - i2c = busio.I2C(board.SCL, board.SDA) - self.vl53 = VL53L0X(i2c) - - def get_distance(self): - """ - Get the distance from the rear ToF sensor. - """ - try: - distance = self.vl53.range - return distance - except Exception as e: - self.log.error(f"Error reading rear ToF sensor: {e}") - return None - diff --git a/src/HL/actionneur_capteur/masterI2C.py b/src/HL/actionneur_capteur/masterI2C.py deleted file mode 100644 index ae9f9171..00000000 --- a/src/HL/actionneur_capteur/masterI2C.py +++ /dev/null @@ -1,60 +0,0 @@ -import smbus #type: ignore #ignore the module could not be resolved error because it is a linux only module -import time -import struct -from Autotech_constant import I2C_SLEEP_RECEIVED, I2C_NUMBER_DATA_RECEIVED, I2C_SLEEP_ERROR_LOOP, SLAVE_ADDRESS -import logging -import threading - -class I2c_arduino: - def __init__(self,serveur): - self.log = logging.getLogger(__name__) - self.serveur = serveur - self.current_speed = 0 - self.send_running = True - self.receive_running = True - - #voltage des lipos - self.voltage_lipo = 0 - self.voltage_nimh = 0 - - #initialisation du bus i2c - self.bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1 - self.log.info("I2C: bus ouvert sur /dev/i2c-1") - - - - #initialization of i2c send and received - threading.Thread(target=self.start_send, daemon=True).start() - threading.Thread(target=self.start_received, daemon=True).start() - - def start_send(self): - """Envoie vitesse/direction régulièrement au microcontroleur. (toute les frames actuellement)""" - time.sleep(1) # Give some time for the target_speed and direction to be set - self.log.info("Thread I2C loop démarré") - while self.send_running: - try : - data = struct.pack('= length: - float_values = struct.unpack('f' * I2C_NUMBER_DATA_RECEIVED, bytes(data[:length])) - list_valeur = list(float_values) - - # on enregistre les valeur - self.voltage_lipo = list_valeur[0] - self.voltage_nimh = list_valeur[1] - self.current_speed = list_valeur[2] - else: - self.log.warning("I2C: taille inattendue (%d au lieu de %d)", len(data), length) - time.sleep(I2C_SLEEP_RECEIVED) \ No newline at end of file diff --git a/src/HL/programme/Car.py b/src/HL/programme/Car.py deleted file mode 100644 index afce745d..00000000 --- a/src/HL/programme/Car.py +++ /dev/null @@ -1,213 +0,0 @@ -import time -import onnxruntime as ort -from scipy.special import softmax -import numpy as np -from gpiozero import LED, Button -import logging -import smbus # type: ignore #ignore the module could not be resolved error because it is a linux only module -import struct -from threading import Thread -from src.HL.programme.programme import Program - -# Import constants from HL.Autotech_constant to share them between files and ease of use -from ..Autotech_constant import MAX_ANGLE, CRASH_DIST, MODEL_PATH, SOCKET_ADRESS, REAR_BACKUP_DIST, LIDAR_DATA_SIGMA, LIDAR_DATA_AMPLITUDE, LIDAR_DATA_OFFSET -from src.HL.programme.scripts.Driver import Driver - - -class Car: - def __init__(self,driving_strategy, serveur): - self.log = logging.getLogger(__name__) - - """Initialize the car's components.""" - self.target_speed = 0 # Speed in millimeters per second - self.direction = 0 # Steering angle in degrees - self.serveur = serveur - self.reverse_count = 0 - def _initialize_ai(): - """Initialize the AI session.""" - try: - self.ai_session = ort.InferenceSession(MODEL_PATH) - self.log.info("AI session initialized successfully") - except Exception as e: - self.log.error(f"Error initializing AI session: {e}") - raise - # Initialize AI session - _initialize_ai() - - self.driving = driving_strategy - - self.log.info("Car initialization complete") - # accès dynamique aux capteurs - @property - def camera(self): - return self.serveur.camera - - @property - def lidar(self): - return self.serveur.lidar - - @property - def tof(self): - return self.serveur.tof - - - def stop(self): - self.target_speed = 0 - self.direction = 0 - self.log.info("Arrêt du moteur") - - - def has_Crashed(self): - - small_distances = [d for d in self.lidar.rDistance[200:880] if 0 < d < CRASH_DIST] # 360 to 720 is the front of the car. 1/3 of the fov of the lidar - self.log.debug(f"Distances: {small_distances}") - if len(small_distances) > 2: - # min_index = self.lidar.rDistance.index(min(small_distances)) - while self.tof.get_distance() < REAR_BACKUP_DIST: - self.log.info(f"Obstacle arriere détecté {self.tof.get_distance()}") - self.target_speed = 0 - time.sleep(0.1) - return True - return False - - def turn_around(self): - """Turn the car around.""" - self.log.info("Turning around") - - self.target_speed = 0 - self.direction = MAX_ANGLE - self.target_speed = -2 #blocing call - time.sleep(1.8) # Wait for the car to turn around - if self.camera.is_running_in_reversed(): - self.turn_around() - - - - def main(self): - # récupération des données du lidar. On ne prend que les 1080 premières valeurs et on ignore la dernière par facilit" pour l'ia - if self.camera is None or self.lidar is None or self.tof is None: - self.log.debug("Capteurs pas encore prêts") - print("Capteurs pas encore prêts") - return - lidar_data = (self.lidar.rDistance[:1080]/1000) - lidar_data_ai= (lidar_data-0.5)*( - LIDAR_DATA_OFFSET + LIDAR_DATA_AMPLITUDE * np.exp(-1/2*((np.arange(1080) - 135) / LIDAR_DATA_SIGMA**2)) - ) #convertir en mètre et ajouter un bruit gaussien #On traffique les données fournit a l'IA - self.direction, self.target_speed = self.driving(lidar_data_ai) #l'ai prend des distance en mètre et non en mm - self.log.debug(f"Min Lidar: {min(lidar_data)}, Max Lidar: {max(lidar_data)}") - - if self.camera.is_running_in_reversed(): - self.reverse_count += 1 - else: - self.reverse_count = 0 - if self.reverse_count > 2: - self.turn_around() - self.reverse_count = 0 - if self.has_Crashed(): - print("Obstacle détecté") - color= self.camera.is_green_or_red(lidar_data) - if color == 0: - small_distances = [d for d in self.lidar.rDistance if 0 < d < CRASH_DIST] - if len(small_distances) == 0: - self.log.info("Aucun obstacle détecté") - return - min_index = np.argmin(small_distances) - direction = MAX_ANGLE if min_index < 540 else -MAX_ANGLE #540 is the middle of the lidar - color = direction/direction - self.log.info("Obstacle détecté, Lidar Fallback") - if color == -1: - self.log.info("Obstacle rouge détecté") - if color == 1: - self.log.info("Obstacle vert détecté") - angle= -color*MAX_ANGLE - self.target_speed = -2 - self.direction = angle - - - - -class Ai_Programme(Program): - def __init__(self, serveur): - super().__init__() - self.log = logging.getLogger(__name__) - self.serveur = serveur - self.driver = None - self.GR86 = None - self.running = False - self.controls_car = True - - @property - def target_speed(self): - if self.GR86 == None: - return 0 - return self.GR86.target_speed - - @property - def direction(self): - if self.GR86 == None: - return 0 - return self.GR86.direction - - def run(self): - while self.running: - try: - self.GR86.main() - except Exception as e: - self.log.error(f"Erreur IA: {e}") - self.running = False - - def start(self): - if self.running: - return - - if self.serveur.camera is None or self.serveur.lidar is None or self.serveur.tof is None: - print("Capteurs non initialisés") - return - - try: - self.driver = Driver(128, 128) - self.driver.load_model() - self.GR86 = Car(self.driver, self.serveur) - except Exception as e: - self.log.error(f"Impossible de démarrer l'IA: {e}") - self.driver = None - self.GR86 = None - return - - self.running = True - Thread(target=self.run, daemon=True).start() - - - def stop(self): - self.running = False - self.GR86.stop() - -""" -if __name__ == '__main__': # non fonctionnelle - Format= '%(asctime)s:%(name)s:%(levelname)s:%(message)s' - if input("Appuyez sur D pour démarrer en debug ou sur n'importe quelle autre touche pour démarrer en mode normal") in ("D", "d"): - logging.basicConfig(level=logging.DEBUG, format=Format) - else: - logging.basicConfig(level=logging.INFO, format=Format) - bp2 = Button("GPIO6") - try: - Schumacher = Driver(128, 128) - GR86 = Car(Schumacher,None,None) - GR86._initialize_camera() - GR86._initialize_lidar() - logging.info("Initialisation terminée") - if input("Appuyez sur D pour démarrer ou tout autre touche pour quitter") in ("D", "d") or bp2.is_pressed: - logging.info("Depart") - while True: - GR86.main() - else: - raise Exception("Le programme a été arrêté par l'utilisateur") - except KeyboardInterrupt: - GR86.stop() - logging.info("Le programme a été arrêté par l'utilisateur") - - except Exception as e: # catch all exceptions to stop the car - GR86.stop() - logging.error("Erreur inconnue") - raise e # re-raise the exception to see the error message - """ \ No newline at end of file diff --git a/src/HL/programme/Poweroff.py b/src/HL/programme/Poweroff.py deleted file mode 100644 index 19abd3b9..00000000 --- a/src/HL/programme/Poweroff.py +++ /dev/null @@ -1,25 +0,0 @@ -import subprocess -import os -from src.HL.programme.programme import Program -import logging - -class Poweroff(Program): - def __init__(self): - super().__init__() - self.log = logging.getLogger(__name__) - self.controls_car = False - self.running = False - - def kill(self): - pass - - def start(self): - self.log.info("Power off started") - subprocess.Popen( - "sudo poweroff", - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - preexec_fn=os.setsid - ) - \ No newline at end of file diff --git a/src/HL/programme/RemoteControl.py b/src/HL/programme/RemoteControl.py deleted file mode 100644 index f0ebdee2..00000000 --- a/src/HL/programme/RemoteControl.py +++ /dev/null @@ -1,42 +0,0 @@ -from src.HL.programme.programme import Program -import struct -import socket -import threading -import time -import logging -from src.HL.Autotech_constant import PORT_REMOTE_CONTROL -class RemoteControl(Program): - """ ce programme permet de prendre le control de la voiture à distance en utilsant des packet udp - on peut prendre le controle avec le script remote_controle_controller.py""" - def __init__(self): - super().__init__() - self.log = logging.getLogger(__name__) - self.controls_car = True - self.running = False - self.target_speed = 0 - self.direction = 0 - - #initialisation - self.public = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.public.bind(("0.0.0.0", PORT_REMOTE_CONTROL)) - - self.log.info("Initialisation de Remote control fini") - - def car_controle(self,sock): - """lance le controle depuis le pc.""" - sock.settimeout(0.1) - - while self.running: - try: - data, ip = sock.recvfrom(1024) - self.target_speed, self.direction = struct.unpack("ff", data) - except socket.timeout: - continue - - def start(self): - self.running = True - threading.Thread(target=self.car_controle, args=(self.public,), daemon=True).start() - - def kill(self): - """fait sortir le thread de sa boucle""" - self.running = False \ No newline at end of file diff --git a/src/HL/programme/module_initialisation.py b/src/HL/programme/module_initialisation.py deleted file mode 100644 index 80ee032b..00000000 --- a/src/HL/programme/module_initialisation.py +++ /dev/null @@ -1,98 +0,0 @@ - -from src.HL.programme.programme import Program -import threading -from ..Autotech_constant import SOCKET_ADRESS -import logging - -class Initialisation(Program): - def __init__(self,server, camera, lidar, tof, I2C): - super().__init__() - self.log = logging.getLogger(__name__) - self.arduino_I2C = None - self.camera = None - self.lidar = None - self.tof = None - self.arduino_I2C_init = 0 - self.camera_init = 0 - self.lidar_init = 0 - self.tof_init = 0 - - threading.Thread(target=self.init_camera, args=(camera,), daemon=True).start() - threading.Thread(target=self.init_lidar, args=(lidar,), daemon=True).start() - threading.Thread(target=self.init_tof, args=(tof,), daemon=True).start() - threading.Thread(target=self.init_I2C_arduino, args=(I2C,server,), daemon=True).start() - - def init_I2C_arduino(self,I2C,server): - try: - self.arduino_I2C = I2C(server) - self.arduino_I2C_init = 1 - self.log.info("I2C Arduino initialized successfully") - except Exception as e: - self.arduino_I2C_init = 2 - self.log.error("I2C Arduino init error : " + str(e)) - - - def init_camera(self,camera): - try: - self.camera = camera() - self.camera_init = 1 - self.log.info("Camera initialized successfully") - except Exception as e: - self.camera_init = 2 - self.log.error("Camera init error : " + str(e)) - - def init_lidar(self,lidar): - try: - self.lidar = lidar(SOCKET_ADRESS["IP"], SOCKET_ADRESS["PORT"]) - self.lidar.stop() - self.lidar.startContinuous(0, 1080) - self.log.info("Lidar initialized successfully") - self.lidar_init = 1 - except Exception as e: - self.lidar_init = 2 - self.log.error("Lidar init error : " + str(e)) - - def init_tof(self,tof): - try: - self.tof = tof() - self.tof_init = 1 - self.log.info("Camera initialized successfully") - except Exception as e: - self.tof_init = 2 - self.log.error("Tof init error : " + str(e)) - - def display(self): - - text = "\ncamera: " - if self.camera_init == 0: - text += "(en cour)" - elif self.camera_init == 1: - text += "ready." - elif self.camera_init == 2: - text += "error" - - text+= "\n lidar: " - if self.lidar_init == 0: - text += "(en cour)" - elif self.lidar_init == 1: - text += "ready." - elif self.lidar_init == 2: - text += "error" - - text+= "\n tof:" - if self.tof_init == 0: - text += "(en cour)" - elif self.tof_init == 1: - text += "ready." - elif self.tof_init == 2: - text += "error" - - text+= "\n Arduino:" - if self.arduino_I2C_init == 0: - text += "(en cour)" - elif self.arduino_I2C_init == 1: - text += "ready." - elif self.arduino_I2C_init == 2: - text += "error" - - return text \ No newline at end of file diff --git a/src/HL/programme/programme.py b/src/HL/programme/programme.py deleted file mode 100644 index 9530372f..00000000 --- a/src/HL/programme/programme.py +++ /dev/null @@ -1,28 +0,0 @@ -from ..Autotech_constant import LOGGING_LEVEL -import logging -from typing import Optional - -""" classe type pour tout les programme """ -class Program: - controls_car:bool # correspond si le programme cotronle la voiture ou non pour savoir si il faut arreter l'ancien programme qui controle la voiture - running:bool # état de base du programme (au lancement de la voiture) - target_speed:Optional[float] # target speed - direction:Optional[float] # target direction - - def __init__(self): - self.logger = logging.getLogger(__name__) - self.logger.setLevel(LOGGING_LEVEL) - - def kill(self): - pass - - def start(self): - pass - - def display(self): - name = self.__class__.__name__ - if (self.running): - return f"{name} \n (running)" - else: - return name - \ No newline at end of file diff --git a/src/HL/programme/scripts/get_ip.py b/src/HL/programme/scripts/get_ip.py deleted file mode 100644 index 1ca3662c..00000000 --- a/src/HL/programme/scripts/get_ip.py +++ /dev/null @@ -1,18 +0,0 @@ -import netifaces as ni #netifaces is abondened, so I use netifaces2 -import subprocess - -def get_ip(device='wlan0'): - ip = ni.ifaddresses(device)[ni.AF_INET][0]['addr'] #note this only gets the first IP address of the interface. An Interface can have sevral IP addresses - return ip - - -def check_ssh_connections(): - result = subprocess.run(['who'], stdout=subprocess.PIPE) - output = result.stdout.decode('utf-8') - ssh_connections = [line for line in output.split('\n') if 'pts/' in line] - return len(ssh_connections) > 0 #bool -if __name__ == "__main__": - if check_ssh_connections(): - print("There are active SSH connections.") - else: - print("No active SSH connections.") \ No newline at end of file diff --git a/src/HL/site_controle/app.js b/src/HL/site_controle/app.js deleted file mode 100644 index 575b3332..00000000 --- a/src/HL/site_controle/app.js +++ /dev/null @@ -1,387 +0,0 @@ -const API = ""; // même origine (FastAPI) -const speedHistory = { - real: [], - demand: [], - maxPoints: 100 -}; - -function drawSpeedChart() { - const canvas = document.getElementById("speedChart"); - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - const w = canvas.width; - const h = canvas.height; - - ctx.clearRect(0, 0, w, h); - - // axes - ctx.strokeStyle = "#444"; - ctx.beginPath(); - ctx.moveTo(40, 10); - ctx.lineTo(40, h - 20); - ctx.lineTo(w - 10, h - 20); - ctx.stroke(); - - - - const maxAbs = Math.max( - ...speedHistory.real.map(Math.abs), - ...speedHistory.demand.map(Math.abs), - 1 - ); - const yZero = h / 2; - const scaleY = (h - 40) / (2 * maxAbs); - // ligne y=0 - ctx.strokeStyle = "#666"; - ctx.setLineDash([5, 5]); - ctx.beginPath(); - ctx.moveTo(40, yZero); - ctx.lineTo(w - 10, yZero); - ctx.stroke(); - ctx.setLineDash([]); - - - function drawCurve(data, color) { - ctx.strokeStyle = color; - ctx.beginPath(); - - data.forEach((v, i) => { - const x = 40 + (i / (data.length - 1)) * (w - 60); - const y = yZero - v * scaleY; - if (i === 0) ctx.moveTo(x, y); - else ctx.lineTo(x, y); - }); - - ctx.stroke(); - } - - drawCurve(speedHistory.demand, "#ffaa00"); // consigne - drawCurve(speedHistory.real, "#00ff88"); // réel - - // légende - - ctx.fillStyle = "#ffaa00"; - ctx.fillText("Consigne", w - 100, 20); - ctx.fillStyle = "#00ff88"; - ctx.fillText("Réelle", w - 100, 35); - ctx.font = "10px monospace"; - -} - -function drawSteering(directionDeg) { - const canvas = document.getElementById("steeringViz"); - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - const w = canvas.width; - const h = canvas.height; - - ctx.clearRect(0, 0, w, h); - - const centerX = w / 2; - const centerY = h * 0.85; - const radius = h * 0.65; - - /* -------- Rapporteur (-18° à +18°) -------- */ - ctx.strokeStyle = "#444"; - ctx.lineWidth = 1; - - const maxDeg = 18; - - ctx.beginPath(); - ctx.arc( - centerX, - centerY, - radius, - (-90 - maxDeg) * Math.PI / 180, - (-90 + maxDeg) * Math.PI / 180 - ); - ctx.stroke(); - - /* -------- Graduations -------- */ - for (let d = -18; d <= 18; d += 6) { - const a = (d - 90) * Math.PI / 180; - - ctx.beginPath(); - ctx.moveTo( - centerX + Math.cos(a) * (radius - 10), - centerY + Math.sin(a) * (radius - 10) - ); - ctx.lineTo( - centerX + Math.cos(a) * radius, - centerY + Math.sin(a) * radius - ); - ctx.stroke(); - - ctx.fillStyle = "#777"; - ctx.font = "10px monospace"; - ctx.fillText( - `${d}°`, - centerX + Math.cos(a) * (radius + 10) - 8, - centerY + Math.sin(a) * (radius + 10) + 3 - ); - } - - /* -------- Tige (direction demandée) -------- */ - const angleRad = (directionDeg - 90) * Math.PI / 180; - - ctx.strokeStyle = "#00ff88"; - ctx.lineWidth = 3; - ctx.beginPath(); - ctx.moveTo(centerX, centerY); - ctx.lineTo( - centerX + Math.cos(angleRad) * (radius - 20), - centerY + Math.sin(angleRad) * (radius - 20) - ); - ctx.stroke(); - - /* -------- Centre -------- */ - ctx.fillStyle = "#00ff88"; - ctx.beginPath(); - ctx.arc(centerX, centerY, 5, 0, Math.PI * 2); - ctx.fill(); -} - - -async function fetchStatus() { - const res = await fetch(`${API}/api/status`); - if (!res.ok) { - throw new Error(`API status failed: ${res.status}`); - } - return await res.json(); -} - -async function fetchCameraUrl() { - const res = await fetch(`${API}/api/stream/camera`); - if (!res.ok) { - throw new Error(`Camera url API failed: ${res.status}`); - } - const data = await res.json(); - return data.url; -} - - -async function startProgram(id) { - await fetch(`${API}/api/programs/${id}/start`, { method: "POST" }); - refreshPrograms(); -} - -async function killProgram(id) { - await fetch(`${API}/api/programs/${id}/kill`, { method: "POST" }); - refreshPrograms(); -} - -function updateTelemetry(t) { - document.getElementById("lipo").textContent = t.battery.lipo.toFixed(2); - document.getElementById("nimh").textContent = t.battery.nimh.toFixed(2); - document.getElementById("vitesse").textContent = - t.car.vitesse_reelle.toFixed(2); - - document.getElementById("target_speed").textContent = - t.car.vitesse_demandee.toFixed(2); - - document.getElementById("direction").textContent = - t.car.direction_demandee.toFixed(2); - document.getElementById("active_program").textContent = - t.car.programme_controle ?? "Aucun"; - speedHistory.real.push(t.car.vitesse_reelle); - speedHistory.demand.push(t.car.vitesse_demandee); - - if (speedHistory.real.length > speedHistory.maxPoints) { - speedHistory.real.shift(); - speedHistory.demand.shift(); - } - - drawSpeedChart(); - drawSteering(t.car.direction_demandee); - -} - -function updatePrograms(programs) { - const tbody = document.getElementById("program_table"); - tbody.innerHTML = ""; - - for (const p of programs) { - const tr = document.createElement("tr"); - - tr.innerHTML = ` - ${p.id} - ${p.name} - ${p.running ? "🟢" : "🔴"} - ${p.controls_car ? "🚗" : "-"} - - - - - `; - - tbody.appendChild(tr); - } -} -async function refreshPrograms() { - try { - const res = await fetch("/api/programs"); - if (!res.ok) { - throw new Error(`Programs API failed: ${res.status}`); - } - const programs = await res.json(); - updatePrograms(programs); - } catch (e) { - console.error("Failed to refresh programs", e); - } -} -function initLidar(retryDelay = 1000) { - const canvas = document.getElementById("lidar"); - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - const scale = 0.15; // 10 cm = 15 px - - const proto = location.protocol === "https:" ? "wss" : "ws"; - const ws = new WebSocket(proto + "://" + location.host + "/api/lidar/ws"); - try{ - ws.onmessage = (e) => { - const data = JSON.parse(e.data); - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - ctx.save(); - ctx.translate(canvas.width / 2, canvas.height / 2); - - /* ---------- Grille ---------- */ - const circleStepMM = 100; // 10 cm - const circleCount = 15; - - ctx.strokeStyle = "#333"; - ctx.lineWidth = 1; - - for (let i = 1; i <= circleCount; i++) { - const r = i * circleStepMM * scale; - ctx.beginPath(); - ctx.arc(0, 0, r, 0, Math.PI * 2); - ctx.stroke(); - - if (i % (circleCount / 3) === 0){ - // label distance - ctx.fillStyle = "#777"; - ctx.font = "10px monospace"; - ctx.fillText(`${i * 10} cm`, r + 2, 0); - } - } - // FOV du lidar (270°) - ctx.strokeStyle = "#444"; - ctx.lineWidth = 1; - - const fovMin = -135 * Math.PI / 180; - const fovMax = 135 * Math.PI / 180; - const fovRadius = 1500 * scale; - - ctx.beginPath(); - // ligne gauche - ctx.moveTo(0, 0); - ctx.lineTo( - Math.sin(fovMin) * fovRadius, - -Math.cos(fovMin) * fovRadius - ); - ctx.stroke(); - - // ligne droite - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.lineTo( - Math.sin(fovMax) * fovRadius, - -Math.cos(fovMax) * fovRadius - ); - ctx.stroke(); - /* ---------- Axes ---------- */ - ctx.strokeStyle = "#555"; - ctx.beginPath(); - ctx.moveTo(-canvas.width / 2, 0); - ctx.lineTo(canvas.width / 2, 0); - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(0, -canvas.height / 2); - ctx.lineTo(0, canvas.height / 2); - ctx.stroke(); - - /* ---------- Points LIDAR ---------- */ - ctx.fillStyle = "#00ff88"; - for (let i = 0; i < data.x.length; i++) { - ctx.fillRect( - data.x[i] * scale, - -data.y[i] * scale, - 2, 2 - ); - } - - ctx.restore(); - }; - }catch(e){ - console.error("Erreur dans LIDAR WS onmessage:", e); - } - - ws.onclose = () => { - console.warn("LIDAR WS disconnected"); - - setTimeout(() => { - initLidar(Math.min(retryDelay * 2, 8000)); - }, retryDelay); -} -} -function initTelemetryWS() { - const proto = location.protocol === "https:" ? "wss" : "ws"; - const ws = new WebSocket(proto + "://" + location.host + "/api/telemetry/ws"); - try{ - ws.onmessage = (e) => { - const data = JSON.parse(e.data); - updateTelemetry(data); - }; - }catch(e){ - console.error("Erreur dans Telemetry WS onmessage:", e); -} - ws.onclose = () => { - console.warn("Telemetry WS disconnected, retrying..."); - setTimeout(initTelemetryWS, 1000); - }; -} - -async function loadProgramsOnce() { - try { - const res = await fetch("/api/programs"); - if (!res.ok) { - throw new Error(`Programs API failed: ${res.status}`); - } - const programs = await res.json(); - updatePrograms(programs); - } catch (e) { - console.error("Failed to load programs", e); - } -} - - -async function init() { - try { - const camUrl = await fetchCameraUrl(); - const camEl = document.getElementById("camera"); - const camLink = document.getElementById("camera-link"); - - if (camEl && camLink) { - camEl.src = camUrl; - camLink.href = camUrl; - - } else { - console.warn("Element #camera introuvable au moment d'init"); - } - - initLidar(); - initTelemetryWS(); - loadProgramsOnce(); - } catch (e) { - console.error("Erreur dans init:", e); - } -} - -window.addEventListener("DOMContentLoaded", init); - \ No newline at end of file diff --git a/src/HL/site_controle/lidar.html b/src/HL/site_controle/lidar.html deleted file mode 100644 index 2cfc0256..00000000 --- a/src/HL/site_controle/lidar.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - LIDAR - - - - - - - - - - diff --git "a/src/HL/site_controle/ressources/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_transparent.png" "b/src/HL/site_controle/ressources/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_transparent.png" deleted file mode 100644 index 3a478e11..00000000 Binary files "a/src/HL/site_controle/ressources/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_transparent.png" and /dev/null differ diff --git a/src/Simulateur/.idea/.gitignore b/src/Simulateur/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/src/Simulateur/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/src/Simulateur/.idea/Simulateur.iml b/src/Simulateur/.idea/Simulateur.iml deleted file mode 100644 index 88391539..00000000 --- a/src/Simulateur/.idea/Simulateur.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Simulateur/.idea/inspectionProfiles/profiles_settings.xml b/src/Simulateur/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2da..00000000 --- a/src/Simulateur/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/Simulateur/.idea/misc.xml b/src/Simulateur/.idea/misc.xml deleted file mode 100644 index d9c6cb89..00000000 --- a/src/Simulateur/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Simulateur/.idea/modules.xml b/src/Simulateur/.idea/modules.xml deleted file mode 100644 index 276197d7..00000000 --- a/src/Simulateur/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Simulateur/.idea/vcs.xml b/src/Simulateur/.idea/vcs.xml deleted file mode 100644 index 6c0b8635..00000000 --- a/src/Simulateur/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Simulateur/Apprentissage par renforcement Webots Gym StableBaselines 2022/controllers/controller_Gym/controller_Gym.py b/src/Simulateur/Apprentissage par renforcement Webots Gym StableBaselines 2022/controllers/controller_Gym/controller_Gym.py deleted file mode 100644 index 4ed5f7fa..00000000 --- a/src/Simulateur/Apprentissage par renforcement Webots Gym StableBaselines 2022/controllers/controller_Gym/controller_Gym.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Voiture autonome avec utilisation d'un -LIDAR sur WEBOTS -Auteur: Chrysanthe et Jessica -""" - -import numpy as np -import random -import gymnasium as gym -import time - -from stable_baselines3 import PPO -from stable_baselines3.common.env_checker import check_env -from stable_baselines3.common.vec_env import SubprocVecEnv - -from vehicle import Driver -from controller import Lidar -from controller import Field -from controller import Supervisor - - -#--------------GYM---------------------------- - -#Création de l'environnement GYM -class WebotsGymEnvironment(gym.Env): - def __init__(self): - #Initialisation du driver - self.driver = Driver() - - basicTimeStep = int(self.driver.getBasicTimeStep()) - self.sensorTime = basicTimeStep//4 - - #Paramètre de la voiture (position, etc..) - self.voiture_robot = self.driver.getFromDef("vehicle") - - #Lidar - self.lidar = self.driver.getDevice('lidar') - self.lidar.enable(self.sensorTime) - self.lidar.enablePointCloud() - - #Capteur de distance - self.capteur_avant = self.driver.getDevice('front_center_sensor') - self.capteur_gauche = self.driver.getDevice('side_left_sensor') - self.capteur_droite = self.driver.getDevice('side_right_sensor') - self.capteur_avant.enable(self.sensorTime) - self.capteur_gauche.enable(self.sensorTime) - self.capteur_droite.enable(self.sensorTime) - - #Capteur de balise - self.capteur_balise = self.driver.getDevice('capteur_balise') - self.capteur_balise.enable(self.sensorTime) - - self.action_space = gym.spaces.Discrete(5) #actions disponibles - min = np.zeros(self.lidar.getNumberOfPoints()) - max = np.ones(self.lidar.getNumberOfPoints()) - self.observation_space = gym.spaces.Box(min, max, dtype=np.float32) #Etat venant du LIDAR - - self.trans_champs = self.voiture_robot.getField("translation") - self.rot_champs = self.voiture_robot.getField("rotation") # idk why but if this goes befor Lidar it will not work - - - #Vérification de l'état de la voiture - def observe(self): - try: - tableau = self.lidar.getRangeImage() - #Division par 10 pour que la valeur soient entre 0 et 1 - etat = np.array(tableau, dtype=np.float32)/10 - except: #En cas de non retour lidar - print("Pas de retour du lidar") - etat = np.zeros(self.lidar.getNumberOfPoints(), dtype=np.float32) - - return etat - - #Remise à 0 pour l'environnement GYM - def reset(self, seed=0): - #self.capteur_avant.disable() - #self.capteur_gauche.disable() - #self.capteur_droite.disable() - - #Valeur aléatoire - x = random.uniform(1.5, 1.65) - y = random.uniform(3.66, 3.8) - - #Fonction d'initialisation - INITIAL_trans = [x, y, 0.0195182] - INITIAL_rot=[-0.000257, 0.000618, 1 , -0.784] - self.trans_champs.setSFVec3f(INITIAL_trans) - self.rot_champs.setSFRotation(INITIAL_rot) - - time.sleep(0.3) #Temps de pause après réinitilialisation - - #self.capteur_avant.enable(self.sensorTime) - #self.capteur_gauche.enable(self.sensorTime) - #self.capteur_droite.enable(self.sensorTime) - #Retour état - obs = self.observe() - #super().step() - info = {} - return obs, info - - #Fonction step de l'environnement GYM - def step(self, action): - self.driver.setSteeringAngle([-.4, -.1, 0, .1, .4][action]) - self.driver.setCruisingSpeed(1.0) - - obs = self.observe() - - reward = 0 - done = False - truncated = False - - avant = self.capteur_avant.getValue() - gauche = self.capteur_gauche.getValue() - droite = self.capteur_droite.getValue() - balise = self.capteur_balise.getValue() - - if avant >= 900 and not(done): - print("Collision avant") - reward = -100 - done = True - elif ((avant >= 854 and gauche >= 896) or (avant >= 696 and gauche >= 910) or gauche >= 937) and not(done): - print("Collision gauche") - reward = -100 - done = True - elif ((avant >= 850 and droite >= 893) or (avant >= 584 and droite >= 910) or droite >= 961) and not(done): - print("Collision droite") - reward = -100 - done = True - elif balise > 700: - done = False - print("Balise passée") - reward = 20 - else: - done = False - reward = 0 - - self.driver.step() - - return obs, reward, done, truncated, {} - - #Fonction render de l'environnement GYM - def render(self, mode="human", close=False): - pass - - -#----------------Programme principal-------------------- -def main(): - n_vehicles = 4 - # env = SubprocVecEnv([lambda: WebotsGymEnvironment() for i in range(n_vehicles)]) - env = WebotsGymEnvironment() - check_env(env) - - logdir = "./Webots_tb/" - #-- , tensorboard_log = logdir -- , tb_log_name = "PPO_voiture_webots" - - #Définition modèle avec paramètre par défaut - model = PPO('MlpPolicy', env, - n_steps=2048, - n_epochs=10, - batch_size=32, - learning_rate=3e-3, - verbose=1, - device=device - ) - - #Entrainnement - model.learn(total_timesteps=1e6) - - #Sauvegarde - model.save("Voiture_autonome_Webots_PPO") - - #del model - - #Chargement des données d'apprentissage - #model = PPO.load("Voiture_autonome_Webots_PPO") - - obs = env.reset() - - for _ in range(1000000): - #Prédiction pour séléctionner une action à partir de l'observation - action, _states = model.predict(obs, deterministic=True) - obs, reward, done, info = env.step(action) - if done: - obs = env.reset() - - -if __name__ == '__main__': - main() - diff --git a/src/Simulateur/Apprentissage par renforcement Webots Gym StableBaselines 2022/controllers/controller_Gym/controller_Gym_v2.py b/src/Simulateur/Apprentissage par renforcement Webots Gym StableBaselines 2022/controllers/controller_Gym/controller_Gym_v2.py deleted file mode 100644 index c05ecff6..00000000 --- a/src/Simulateur/Apprentissage par renforcement Webots Gym StableBaselines 2022/controllers/controller_Gym/controller_Gym_v2.py +++ /dev/null @@ -1,213 +0,0 @@ -"""Voiture autonome avec utilisation d'un -LIDAR sur WEBOTS -Auteur : Chrysanthe et Jessica -""" - -import numpy as np -import random -import gymnasium as gym -import time - -from stable_baselines3 import PPO -from stable_baselines3.common.env_checker import check_env - -from vehicle import Driver -from controller import Lidar -from controller import Field -from controller import Supervisor - - -#--------------GYM------- --------------------- - -#Création de l'environnement GYM -class WebotsGymEnvironment(Driver, gym.Env) : - def __init__(self): - super().__init__() #Objet héritant la classe Driver - - basicTimeStep = int(super().getBasicTimeStep()) - self.sensorTime = basicTimeStep//4 - - #Lidar - self.lidar = Lidar("lidar") - - #Capteur LIDAR - self.lidar.enable(self.sensorTime) - self.lidar.enablePointCloud() - - self.action_space = gym.spaces.Discrete(5) #actions disponibles - - min = np.zeros(self.lidar.getNumberOfPoints()) - max = np.ones(self.lidar.getNumberOfPoints()) - self.observation_space = gym.spaces.Box(min, max, dtype=np.float32) #Etat venant du LIDAR - - - #Paramètre de la voiture (position, etc..) - self.voiture_robot = super().getFromDef("vehicle") - self.trans_champs = self.voiture_robot.getField("translation") - self.rot_champs = self.voiture_robot.getField("rotation") - - - #Capteur de distance - self.capteur_avant = super().getDevice('front_center_sensor') - self.capteur_gauche = super().getDevice('side_left_sensor') - self.capteur_droite = super().getDevice('side_right_sensor') - self.capteur_avant.enable(self.sensorTime) - self.capteur_gauche.enable(self.sensorTime) - self.capteur_droite.enable(self.sensorTime) - - #Capteur de balise - self.capteur_balise = super().getDevice('capteur_balise') - self.capteur_balise.enable(self.sensorTime) - - #Vérification de l'état de la voiture - def observe(self) : - try : - tableau = self.lidar.getRangeImage() - #Division par 10 pour que la valeur soient entre 0 et 1 - etat = np.divide(np.array(tableau),10) - print("etat : " + etat) - except : #En cas de non retour lidar - print("Pas de retour du lidar") - etat = np.zeros(self.lidar.getNumberOfPoints()) - - return np.array(etat).astype('float32') - - #Remise à 0 - def initialisation(self) : - #self.capteur_avant.disable() - #self.capteur_gauche.disable() - #self.capteur_droite.disable() - - #Valeur aléatoire - x = random.uniform(1.5, 1.65) - y = random.uniform(3.66, 3.8) - - #Fonction d'initialisation - INITIAL_trans = [x, y, 0.0195182] - INITIAL_rot=[-0.000257, 0.000618, 1 , -0.784] - self.trans_champs.setSFVec3f(INITIAL_trans) - self.rot_champs.setSFRotation(INITIAL_rot) - - time.sleep(0.3) #Temps de pause après réinitilialisation - - #self.capteur_avant.enable(self.sensorTime) - #self.capteur_gauche.enable(self.sensorTime) - #self.capteur_droite.enable(self.sensorTime) - - #Remise à 0 pour l'environnement GYM - def reset(self): - self.initialisation() - #Retour état - obs = self.observe() - #super().step() - return obs - - #Fonction pour detection de collision et attribution des récompenses - def evaluer(self): - recompense = 0 - done = False - - avant = self.capteur_avant.getValue() - gauche = self.capteur_gauche.getValue() - droite = self.capteur_droite.getValue() - balise = self.capteur_balise.getValue() - - if avant >= 900 and not(done) : - print("Collision avant") - recompense = -100 - done = True - elif ((avant >= 854 and gauche >= 896) or (avant >= 696 and gauche >= 910) or gauche >= 937) and not(done) : - print("Collision gauche") - recompense = -100 - done = True - elif ((avant >= 850 and droite >= 893) or (avant >= 584 and droite >= 910) or droite >= 961) and not(done) : - print("Collision droite") - recompense = -100 - done = True - elif balise > 700 : - done = False - print("Balise passée") - recompense = 200 - else : - done = False - recompense = 0 - - return recompense, done - - #Fonction pour déplacer la voiture - def rouler(self, action) : - super().setCruisingSpeed(1.0) - - if action == 0 : - super().setSteeringAngle(-0.4) - - elif action == 1 : - super().setSteeringAngle(-0.1) - - elif action == 2 : - super().setSteeringAngle(0) - - elif action == 3 : - super().setSteeringAngle(0.1) - - elif action == 4 : - super().setSteeringAngle(0.4) - - #Fonction step de l'environnement GYM - def step(self, action): - self.rouler(action) - - obs = self.observe() - - reward, done = self.evaluer() - - super().step() - - return obs, reward, done, {} - - #Fonction render de l'environnement GYM - def render(self, mode="human", close=False): - pass - - -#----------------Programme principal-------------------- -def main() : - env = WebotsGymEnvironment() - check_env(env) - - logdir = "./Webots_tb/" - #-- , tensorboard_log = logdir -- , tb_log_name = "PPO_voiture_webots" - - #Définition modèle avec paramètre par défaut - model = PPO('MlpPolicy', env, - n_steps = 256, - ent_coef = 0.01, - n_epochs = 5, - batch_size= 32, - learning_rate= 3e-3, - verbose=1) - - #Entrainnement - model.learn(total_timesteps=1e6) - - #Sauvegarde - model.save("Voiture_autonome_Webots_PPO") - - #del model - - #Chargement des données d'apprentissage - #model = PPO.load("Voiture_autonome_Webots_PPO") - - obs = env.reset() - - for _ in range(1000000): - #Prédiction pour séléctionner une action à partir de l'observation - action, _states = model.predict(obs, deterministic=True) - obs, reward, done, info = env.step(action) - if done: - obs = env.reset() - - -if __name__ == '__main__' : - main() - diff --git a/src/Simulateur/DynamicActionPlotCallback.py b/src/Simulateur/DynamicActionPlotCallback.py deleted file mode 100644 index 359b32b6..00000000 --- a/src/Simulateur/DynamicActionPlotCallback.py +++ /dev/null @@ -1,84 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np -from stable_baselines3.common.callbacks import BaseCallback -import torch -import torch.nn.functional as F -from config import * - - -steering_true = (n_actions_steering + 1) // 2 - -class DynamicActionPlotDistributionCallback(BaseCallback): - def __init__(self, verbose=0): - super().__init__(verbose) - self.fig, self.ax = plt.subplots(4, 1, figsize=(10, 8)) - - # Steering bars - self.steering_bars = self.ax[0].bar(range(n_actions_steering), np.zeros(n_actions_steering), color='blue') - self.steering_avg = [ - self.ax[0].plot([0, 0], [0, 1], color=(i/3, 1 - i/3, 0), label='Average')[0] - for i in range(4) - ] - self.ax[0].set_ylim(0, 1) # Probabilities range from 0 to 1 - self.ax[0].set_title('Steering Action Probabilities') - - # Speed bars - self.speed_bars = self.ax[1].bar(range(n_actions_speed), np.zeros(n_actions_speed), color='blue') - self.speed_avg = self.ax[1].plot([0, 0], [0, 1], color='red', label='Average')[0] - self.ax[1].set_ylim(0, 1) # Probabilities range from 0 to 1 - self.ax[1].set_title('Speed Action Probabilities') - - # LiDAR img - self.lidar_img = self.ax[2].imshow( - np.zeros((lidar_horizontal_resolution, lidar_horizontal_resolution)), - cmap='gray', vmin=0, vmax=np.log(31) - ) - self.ax[2].set_title('LiDAR Image') - - # Camera img - self.camera_img = self.ax[3].imshow( - np.zeros((camera_horizontal_resolution, camera_horizontal_resolution, 3)), - cmap='RdYlGn', vmin=-1, vmax=1 - ) - self.ax[3].set_title('Camera Image') - - def _on_step(self) -> bool: - global steering_true - # Get the action probabilities - - obs = self.locals["obs_tensor"].clone().detach() - - self.lidar_img.set_array(np.log(1 + obs[0, 0, :, :].cpu().numpy())) - self.camera_img.set_array(obs[0, 1, :, :].cpu().numpy()) - with torch.no_grad(): - latent = self.model.policy.features_extractor(obs) - extracted = self.model.policy.mlp_extractor.policy_net(latent) - output = self.model.policy.action_net(extracted)[0] - - T = [0.4, 0.6, 0.8, 1.0] - steer_probs = torch.softmax(output[:n_actions_steering], dim=-1).to("cpu") - speed_probs = torch.softmax(output[n_actions_steering:], dim=-1).to("cpu") - - - - # Update the probabilities - for i, bar in enumerate(self.steering_bars): - bar.set_height(steer_probs[i].item()) - - for i in range(4): - steer_probs_T = torch.softmax(output[:n_actions_steering] / T[i], dim=-1).to("cpu") - steering_avg = (steer_probs_T / steer_probs_T.sum() * torch.arange(16)).sum().item() - self.steering_avg[i].set_xdata([steering_avg, steering_avg]) - - - - for i, bar in enumerate(self.speed_bars): - bar.set_height(speed_probs[i].item()) - - speed_avg = (speed_probs * torch.arange(n_actions_speed)).sum().item() - self.speed_avg.set_xdata([speed_avg, speed_avg]) - - plt.draw() - plt.pause(1e-8) - - return True # Continue training diff --git a/src/Simulateur/WebotsSimulationGymEnvironment.py b/src/Simulateur/WebotsSimulationGymEnvironment.py deleted file mode 100644 index 213b0688..00000000 --- a/src/Simulateur/WebotsSimulationGymEnvironment.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -from typing import * -import numpy as np -import gymnasium as gym - -from config import * - - -class WebotsSimulationGymEnvironment(gym.Env): - """ - One environment for each vehicle - - n: index of the vehicle - supervisor: the supervisor of the simulation - """ - - def __init__(self, simulation_rank: int, vehicle_rank: int): - - - super().__init__() - self.simulation_rank = simulation_rank - self.vehicle_rank = vehicle_rank - - self.handler = logging.FileHandler(f"/tmp/autotech/Voiture_{self.simulation_rank}_{self.vehicle_rank}.log") - self.handler.setFormatter(FORMATTER) - self.log = logging.getLogger("SERVER") - self.log.setLevel(level=LOG_LEVEL) - self.log.addHandler(self.handler) - - - self.log.info("Initialisation started") - - # this is only true if lidar_horizontal_resolution = camera_horizontal_resolution - box_min = np.zeros([2, context_size, lidar_horizontal_resolution], dtype=np.float32) - box_max = np.ones([2, context_size, lidar_horizontal_resolution], dtype=np.float32) * 30 - - self.observation_space = gym.spaces.Box(box_min, box_max, dtype=np.float32) - self.action_space = gym.spaces.MultiDiscrete([n_actions_steering, n_actions_speed]) - - if not os.path.exists("/tmp/autotech"): - os.mkdir("/tmp/autotech") - - self.log.debug(f"Creation of the pipes") - - os.mkfifo(f"/tmp/autotech/{simulation_rank}_{vehicle_rank}toserver.pipe") - os.mkfifo(f"/tmp/autotech/serverto{simulation_rank}_{vehicle_rank}.pipe") - os.mkfifo(f"/tmp/autotech/{simulation_rank}_{vehicle_rank}tosupervisor.pipe") - - # --mode=fast --minimize --no-rendering --batch --stdout - if vehicle_rank == 0 : - os.system(f""" - webots {__file__.rsplit('/', 1)[0]}/worlds/piste{simulation_rank % n_map}.wbt --mode=fast --minimize --batch --stdout & - echo $! {simulation_rank}_{vehicle_rank} >>/tmp/autotech/simulationranks - """) - - self.log.debug("Connection to the vehicle") - self.fifo_w = open(f"/tmp/autotech/serverto{simulation_rank}_{vehicle_rank}.pipe", "wb") - self.log.debug("Connection to the supervisor") - self.fifo_r = open(f"/tmp/autotech/{simulation_rank}_{vehicle_rank}toserver.pipe", "rb") - - self.log.info("Initialisation finished\n") - - def reset(self, seed=0): - # basically useless function - - # lidar data - # this is true for lidar_horizontal_resolution = camera_horizontal_resolution - self.context = obs = np.zeros([2, context_size, lidar_horizontal_resolution], dtype=np.float32) - info = {} - self.log.info(f"reset finished\n") - return obs, info - - def step(self, action): - - self.log.info("Starting step") - self.log.info(f"sending {action=}") - self.fifo_w.write(action.tobytes()) - self.fifo_w.flush() - - # communication with the supervisor - self.log.debug("trying to get info from supervisor") - cur_state = np.frombuffer(self.fifo_r.read(np.dtype(np.float32).itemsize * (n_sensors + lidar_horizontal_resolution + camera_horizontal_resolution)), dtype=np.float32) - self.log.info(f"received {cur_state=}") - reward = np.frombuffer(self.fifo_r.read(np.dtype(np.float32).itemsize), dtype=np.float32)[0] # scalar - self.log.info(f"received {reward=}") - done = np.frombuffer(self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool)[0] # scalar - self.log.info(f"received {done=}") - truncated = np.frombuffer(self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool)[0] # scalar - self.log.info(f"received {truncated=}") - info = {} - - cur_state = np.nan_to_num(cur_state[n_sensors:], nan=0., posinf=30.) - - lidar_obs = cur_state[:lidar_horizontal_resolution] - camera_obs = cur_state[lidar_horizontal_resolution:] - - self.context = obs = np.concatenate([ - self.context[:, 1:], - [lidar_obs[None], camera_obs[None]] - ], axis=1) - - self.log.info("step over") - - return obs, reward, done, truncated, info - - diff --git a/src/Simulateur/config.py b/src/Simulateur/config.py deleted file mode 100644 index 6db6d990..00000000 --- a/src/Simulateur/config.py +++ /dev/null @@ -1,21 +0,0 @@ -# just a file that lets us define some constants that are used in multiple files the simulation -from torch.cuda import is_available -import logging - - -n_map = 2 -n_simulations = 2 -n_vehicles = 2 -n_stupid_vehicles = 0 -n_actions_steering = 16 -n_actions_speed = 16 -n_sensors = 1 -lidar_max_range = 12.0 -device = "cuda" if is_available() else "cpu" - -context_size = 1 -lidar_horizontal_resolution = 1024 # DON'T CHANGE THIS VALUE PLS -camera_horizontal_resolution = 1024 # DON'T CHANGE THIS VALUE PLS - -LOG_LEVEL = logging.DEBUG -FORMATTER = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') diff --git a/src/Simulateur/controllers/controllerVehicleDriver/controllerVehicleDriver.py b/src/Simulateur/controllers/controllerVehicleDriver/controllerVehicleDriver.py deleted file mode 100644 index 2caea5d7..00000000 --- a/src/Simulateur/controllers/controllerVehicleDriver/controllerVehicleDriver.py +++ /dev/null @@ -1,210 +0,0 @@ -import numpy as np -import psutil -import time -import os -import re -import sys -import logging - -# add src/Simulateur to sys.path -path = __file__.rsplit('/', 3)[0] -sys.path.insert(0, path) - -from config import * -from vehicle import Driver -from enum import Enum,auto - -def too_close(lidar,dir): - # if dir==True: we're near the right wall - R=0.83 - l=len(lidar) - straight=lidar[l//2] - if dir: - nearest=min(lidar[l//2:]) - else: - nearest=min(lidar[:l//2]) - theta=np.arccos(nearest/straight) - L=R*(1-np.sin(theta)) - return nearest < L - -class State(Enum): - AI=auto() - BACK=auto() - -class VehicleDriver(Driver): - """ - This class is a subclass of the Driver class and is used to control the vehicle. - It basically receives instructions from the controllerWorldSupervisor and follows them. - """ - - def __init__(self): - super().__init__() - self.state=State.AI - - - basicTimeStep = int(self.getBasicTimeStep()) - self.sensorTime = basicTimeStep // 4 - - self.v_min = 1 - self.v_max = 9 - - self.i = int(self.getName().split("_")[-1]) - # Lidar - self.lidar = self.getDevice("Hokuyo") - self.lidar.enable(self.sensorTime) - self.lidar.enablePointCloud() - - # Camera - self.camera = self.getDevice("RASPI_Camera_V2") - self.camera.enable(self.sensorTime) - - # Checkpoint sensor - self.touch_sensor = self.getDevice("touch_sensor") - self.touch_sensor.enable(self.sensorTime) - - # Communication - - proc = psutil.Process(os.getpid()) #current - parent = proc.parent() #parent - grandparent = parent.parent() if parent else None #grandparent - pppid = str(grandparent.pid) - - - self.simulation_rank = int( - re.search( - pppid + r" (\d+)", - open("/tmp/autotech/simulationranks", "r").read(), - re.MULTILINE - ).group(1) - ) - - self.handler = logging.FileHandler(f"/tmp/autotech/Voiture_{self.simulation_rank}_{self.i}.log") - self.handler.setFormatter(FORMATTER) - self.log = logging.getLogger("CLIENT") - self.log.setLevel(level=LOG_LEVEL) - self.log.addHandler(self.handler) - - self.log.debug("Connection to the server") - self.fifo_r = open(f"/tmp/autotech/serverto{self.simulation_rank}_{self.i}.pipe", "rb") - self.log.debug("Connection with the supervisor") - self.fifo_w = open(f"/tmp/autotech/{self.simulation_rank}_{self.i}tosupervisor.pipe", "wb") - - - - - #Vérification de l"état de la voiture - def observe(self): - try: - sensor_data = [np.array(self.touch_sensor.getValue(), dtype=np.float32)] - - lidar_data = np.array(self.lidar.getRangeImage(), dtype=np.float32) - - camera_data = np.array(self.camera.getImageArray(), dtype=np.float32) - # shape = (1080, 1, 3) - camera_data = camera_data.transpose(1, 2, 0)[0] - # shape = (3, 1080) - color = np.argmax(camera_data, axis=0) - camera_data = ( - (color == 0).astype(np.float32)*-1 + - (color == 1).astype(np.float32)*1 + - (color == 2).astype(np.float32)*0 - ) - # red -> -1 - # green -> 1 - # blue -> 0 - - return ( - sensor_data, - lidar_data, - camera_data - ) - except: - #En cas de non retour lidar - return ( - np.array(self.touch_sensor.getValue(), dtype=np.float32), - np.zeros(self.lidar.getNumberOfPoints(), dtype=np.float32) - ) - - #Fonction step de l"environnement GYM - def step(self): - match self.state: - case State.AI: - self.ai() - case State.BACK: - self.back() - - return super().step() - - - - def ai(self): - # sends observation to the supervisor - self.emitter.send(np.concatenate(self.observe()).tobytes()) - - # First to be executed - - self.log.info("Starting step") - obs = self.observe() - self.log.info(f"Observe {obs=}") - self.fifo_w.write(obs.tobytes()) - self.fifo_w.flush() - - self.log.debug("Trying to read action from the server") - action = np.frombuffer(self.fifo_r.read(np.dtype(np.int64).itemsize * 2), dtype=np.int64) - self.log.info(f"received {action=}") - - # Simulation step - - action_steering = np.linspace(-.4, .4, n_actions_steering, dtype=np.float32)[action[0], None] - action_speed = np.linspace(self.v_min, self.v_max, n_actions_speed, dtype=np.float32)[action[1], None] - - cur_angle = self.getSteeringAngle() - dt = self.getBasicTimeStep() - omega = 20 # rad/s (max angular speed of the steering servo) - - action_steering = cur_angle + np.clip(action_steering - cur_angle, -omega * dt, omega * dt) - - self.setSteeringAngle(action_steering) - self.setCruisingSpeed(action_speed) - - if self.touch_sensor.getValue(): - self.state=State.BACK - - - def back(self): - #si mur de "dir": braquer à "dir"" et reculer jusqu'à pouvoir réavancer (distance au mur à vérif) - lidar,cam=self.observe()[1:] - S=sum(cam) - dir = S>0 - if dir: - self.setSteeringAngle(0.33) - if too_close(lidar,dir): - self.setCruisingSpeed(-2) - else: self.state=State.AI - else: - self.setSteeringAngle(-0.33) - if too_close(lidar,dir): - self.setCruisingSpeed(-2) - else: self.state=State.AI - - def run(self): - # this call is just there to make sure at least one step - # is done in the entire simulation before we call lidar.getRangeImage() - # otherwise it will crash the controller with the message: - # WARNING: 'controllerVehicleDriver' controller crashed. - # WARNING: controllerVehicleDriver: The process crashed some time after starting successfully. - super().step() - while self.step() != -1: - pass - - -#----------------Programme principal-------------------- -def main(): - driver = VehicleDriver() - driver.log.info("Starting the vehicle driver\n") - driver.run() - - -if __name__ == "__main__": - print("Starting the vehicle driver") - main() diff --git a/src/Simulateur/controllers/controller_jaune/controller_jaune.py b/src/Simulateur/controllers/controller_jaune/controller_jaune.py deleted file mode 100644 index 9505e3df..00000000 --- a/src/Simulateur/controllers/controller_jaune/controller_jaune.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 1996-2022 Cyberbotics Ltd. -# -# Controle de la voiture TT-02 simulateur CoVAPSy pour Webots 2023b -# Inspiré de vehicle_driver_altino controller -# Kévin Hoarau, Anthony Juton, Bastien Lhopitallier, Martin Raynaud -# août 2023 - -from vehicle import Driver -from controller import Lidar -import numpy as np -import time - -driver = Driver() - -basicTimeStep = int(driver.getBasicTimeStep()) -sensorTimeStep = 4 * basicTimeStep - -#Lidar -lidar = Lidar("Hokuyo") -lidar.enable(sensorTimeStep) -lidar.enablePointCloud() - -#clavier -keyboard = driver.getKeyboard() -keyboard.enable(sensorTimeStep) - -# vitesse en km/h -speed = 0 -maxSpeed = 28 #km/h - -# angle de la direction -angle = 0 -maxangle_degre = 16 - - -# mise a zéro de la vitesse et de la direction -driver.setSteeringAngle(angle) -driver.setCruisingSpeed(speed) - -tableau_lidar_mm=[0]*360 - -def set_vitesse_m_s(vitesse_m_s): - speed = vitesse_m_s*3.6 - if speed > maxSpeed : - speed = maxSpeed - if speed < 0 : - speed = 0 - driver.setCruisingSpeed(speed) - -def set_direction_degre(angle_degre): - if angle_degre > maxangle_degre: - angle_degre = maxangle_degre - elif angle_degre < -maxangle_degre: - angle_degre = -maxangle_degre - angle = -angle_degre * 3.14/180 - driver.setSteeringAngle(angle) - -def recule(): #sur la voiture réelle, il y a un stop puis un recul pendant 1s. - driver.setCruisingSpeed(-1) - -# mode auto desactive -modeAuto = False -print("cliquer sur la vue 3D pour commencer") -print("a pour mode auto (pas de mode manuel sur TT02_jaune), n pour stop") - -while driver.step() != -1: - while True: - #acquisition des donnees du lidar - # recuperation de la touche clavier - currentKey = keyboard.getKey() - - if currentKey == -1: - break - - elif currentKey == ord('n') or currentKey == ord('N'): - if modeAuto : - modeAuto = False - print("--------Modes Auto TT-02 jaune Désactivé-------") - elif currentKey == ord('a') or currentKey == ord('A'): - if not modeAuto : - modeAuto = True - print("------------Mode Auto TT-02 jaune Activé-----------------") - - #acquisition des donnees du lidar - donnees_lidar_brutes = lidar.getRangeImage() - for i in range(360) : - if (donnees_lidar_brutes[-i]>0) and (donnees_lidar_brutes[-i]<20) : - tableau_lidar_mm[i] = 1000*donnees_lidar_brutes[-i] - else : - tableau_lidar_mm[i] = 0 - - if not modeAuto: - set_direction_degre(0) - set_vitesse_m_s(0) - - if modeAuto: - ######################################################## - # Programme etudiant avec - # - le tableau tableau_lidar_mm - # - la fonction set_direction_degre(...) - # - la fonction set_vitesse_m_s(...) - # - la fonction recule() - ####################################################### - - #un secteur par tranche de 20° donc 10 secteurs numérotés de 0 à 9 - angle_degre = 0.02*(tableau_lidar_mm[60]-tableau_lidar_mm[-60]) - set_direction_degre(angle_degre) - vitesse_m_s = 0.5 - set_vitesse_m_s(vitesse_m_s) - - ######################################################### - diff --git a/src/Simulateur/onnx_utils.py b/src/Simulateur/onnx_utils.py deleted file mode 100644 index 7dd7dd5b..00000000 --- a/src/Simulateur/onnx_utils.py +++ /dev/null @@ -1,76 +0,0 @@ -import os -from stable_baselines3 import PPO -import torch.nn as nn -import torch -from config import * -import numpy as np -from CNN1DExtractor import CNN1DExtractor -from TemporalResNetExtractor import TemporalResNetExtractor - -import onnxruntime as ort - - -def get_true_model(model): - return nn.Sequential( - model.policy.features_extractor.net, - model.policy.mlp_extractor.policy_net, - model.policy.action_net - ).to("cpu") - - -def export_onnx(model): - model.policy.eval() - device = model.policy.device - true_model = get_true_model(model) - x = torch.randn(1, 2, context_size, lidar_horizontal_resolution) - - with torch.no_grad(): - torch.onnx.export( - true_model, - x, - "model.onnx", - input_names=["input"], - output_names=["output"], - dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}} - ) - - true_model.to(device) - model.policy.to(device) - model.policy.train() - -def run_onnx_model(session : ort.InferenceSession,x : np.ndarray): - - return session.run(None, {"input": x})[0] - -def test_onnx(model): - device = model.policy.device - model.policy.eval() - true_model = get_true_model(model) - - loss_fn = nn.MSELoss() - x = torch.randn(1000, 2, context_size, lidar_horizontal_resolution) - - try: - session = ort.InferenceSession("model.onnx") - except Exception as e: - print(f"Error loading ONNX model: {e}") - return - - with torch.no_grad(): - y_true_test = true_model(x) - - true_model.train() - y_true_train = true_model(x) - true_model.eval() - - y_onnx = run_onnx_model(session,x.cpu().numpy()) - - loss_test = loss_fn(y_true_test, torch.tensor(y_onnx)) - loss_train = loss_fn(y_true_train, torch.tensor(y_onnx)) - print(f"onnx_test={loss_test}") - print(f"onnx_train={loss_train}") - - true_model.to(device) - model.policy.to(device) - model.policy.train() - diff --git a/src/VisionCamera/ColorDetection.py b/src/VisionCamera/ColorDetection.py deleted file mode 100644 index f4eeb780..00000000 --- a/src/VisionCamera/ColorDetection.py +++ /dev/null @@ -1,78 +0,0 @@ -import cv2 as cv -import numpy as np - - -URL = "udp://<192.168.1.10>:8554" - -cap = cv.VideoCapture(URL) -while(True): - # Take each frame - _, frame = cap.read() - # Convert BGR to HSV - hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV) - - # define range of blue color in HSV - - # upper and lower bounds for red color - lower_red1 = np.array([0, 120, 70]) - upper_red1 = np.array([10, 255, 255]) - - lower_red2 = np.array([170, 120, 70]) - upper_red2 = np.array([180, 255, 255]) - - # upper and lower bounds for green color - lower_green = np.array([40, 40, 40]) - upper_green = np.array([90, 255, 255]) - - # Threshold the HSV image to get only blue colors - - # Create a red mask - mask1 = cv.inRange(hsv, lower_red1, upper_red1) - mask2 = cv.inRange(hsv, lower_red2, upper_red2) - - # Bitwise-OR the masks - maskRed = mask1 | mask2 - - # Create a green mask - maskGreen = cv.inRange(hsv, lower_green, upper_green) - #Bitwise-OR the masks - mask = maskRed | maskGreen - - # Bitwise-AND mask and original image - res = cv.bitwise_and(frame,frame, mask= mask) - - # Find contours - contours_green, _ = cv.findContours(maskGreen, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) - contours_red, _ = cv.findContours(maskRed, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) - - if contours_green and contours_red: - # Assume the largest contour for each color is the wall - green_contour = max(contours_green, key=cv.contourArea) - red_contour = max(contours_red, key=cv.contourArea) - - # Compute the centroids using image moments - M_green = cv.moments(green_contour) - M_red = cv.moments(red_contour) - - if M_green["m00"] > 0 and M_red["m00"] > 0: - cx_green = int(M_green["m10"] / M_green["m00"]) - cx_red = int(M_red["m10"] / M_red["m00"]) - - # Check if the green (left wall) is to the left of the red (right wall) - if cx_green < cx_red: - print("right direction") - else: - print("wrong direction") - - - # Show the images - cv.imshow('frame',frame) - cv.imshow('mask',mask) - cv.imshow('res',res) - - # press q to close the window - if cv.waitKey(5) & 0xFF == ord('q'): - break - -cap.release() -cv.destroyAllWindows() \ No newline at end of file diff --git a/src/VisionCamera/ReadMe.md b/src/VisionCamera/ReadMe.md deleted file mode 100755 index 5737f2a9..00000000 --- a/src/VisionCamera/ReadMe.md +++ /dev/null @@ -1 +0,0 @@ -Un approche de la vision non fini par [Markousi](https://github.com/Markousi). Se refer plutot a [Camera.py](../HL/Camera.py) \ No newline at end of file diff --git a/src/Simulateur/__init__.py b/src/high_level/README.md similarity index 100% rename from src/Simulateur/__init__.py rename to src/high_level/README.md diff --git a/src/HL/model_CNN1D.onnx b/src/high_level/models/model_CNN1DExtractor.onnx similarity index 100% rename from src/HL/model_CNN1D.onnx rename to src/high_level/models/model_CNN1DExtractor.onnx diff --git a/src/HL/model_RSNET2D.onnx b/src/high_level/models/model_CNN1DResNetExtractor.onnx similarity index 100% rename from src/HL/model_RSNET2D.onnx rename to src/high_level/models/model_CNN1DResNetExtractor.onnx diff --git a/src/HL/modelTemporal1.onnx b/src/high_level/models/model_TemporalResNetExtractor.onnx similarity index 100% rename from src/HL/modelTemporal1.onnx rename to src/high_level/models/model_TemporalResNetExtractor.onnx diff --git a/src/high_level/pyproject.toml b/src/high_level/pyproject.toml new file mode 100644 index 00000000..03091aad --- /dev/null +++ b/src/high_level/pyproject.toml @@ -0,0 +1,51 @@ +[project] +name = "high-level" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12, <3.14" + + +dependencies = [ + # Note: picamera2 causes UV to auto-include rpi extras even when not requested + # This is likely a UV bug. Add back when needed: + "picamera2>=0.3.0", + # AI Inference + "onnxruntime>=1.8.0", + # Raspberry Pi specific hardware control + "rpi-hardware-pwm>=0.1.0", + "RPi.GPIO>=0.7.1", + "gpiozero>=1.6.0", + # I2C and SPI communication (Linux specific) + "smbus>=1.1.post2", + "spidev>=3.5", + # OLED display support + "luma.oled>=3.8.0", + "luma.core>=2.3.0", + "Adafruit-SSD1306>=1.6.0", + # Time-of-Flight sensor support + "adafruit-circuitpython-vl53l0x>=3.6.0", + "adafruit-blinka>=8.0.0", + # PS4 controller support + "pyPS4Controller>=1.2.0", + "netifaces>=0.11.0", + "matplotlib>=3.10.5", + "opencv-python>=4.12.0.88", + "scipy>=1.15.3", + "websockets>=16.0", + "fastapi>=0.128.0", + "uvicorn>=0.40.0", + "aiortc>=1.14.0", + "aiohttp>=3.13.3", + "zmq>=0.0.0", + "adafruit-circuitpython-vl53l1x>=1.2.7", +] + +[project.optional-dependencies] +controller = [ + "pygame>=2.6.1", +] + +[build-system] +requires = ["uv_build>=0.9.24,<0.10.0"] +build-backend = "uv_build" diff --git a/scripts/Live_display_lidar.py b/src/high_level/scripts/Live_display_lidar.py similarity index 77% rename from scripts/Live_display_lidar.py rename to src/high_level/scripts/Live_display_lidar.py index 643d155a..cda82b39 100644 --- a/scripts/Live_display_lidar.py +++ b/src/high_level/scripts/Live_display_lidar.py @@ -1,8 +1,8 @@ import time -from ..src.HL.Lidar import Lidar +from actionneur_capteur.lidar import Lidar -IP = '192.168.0.10' +IP = '192.168.0.20' PORT = 10940 if __name__ == '__main__': diff --git a/src/HL/animation/Car animation.svg b/src/high_level/scripts/animation/Car animation.svg similarity index 100% rename from src/HL/animation/Car animation.svg rename to src/high_level/scripts/animation/Car animation.svg diff --git a/src/HL/animation/README.MD b/src/high_level/scripts/animation/README.MD similarity index 100% rename from src/HL/animation/README.MD rename to src/high_level/scripts/animation/README.MD diff --git a/src/HL/animation/gen_animation.py b/src/high_level/scripts/animation/gen_animation.py similarity index 76% rename from src/HL/animation/gen_animation.py rename to src/high_level/scripts/animation/gen_animation.py index b388ce5d..9c303076 100644 --- a/src/HL/animation/gen_animation.py +++ b/src/high_level/scripts/animation/gen_animation.py @@ -11,29 +11,41 @@ # Redraw the car with a more detailed shape based on the inspiration image + def draw_race_car(draw, x, y): """Draws a stylized race car at position (x, y).""" # Car body (sleek shape) - draw.polygon([ - (x, y + 6), (x + 4, y + 2), (x + 12, y), (x + 22, y + 2), - (x + 28, y + 6), (x + 30, y + 10), (x + 28, y + 12), (x + 2, y + 12), - (x, y + 10) - ], fill=1, outline=1) - + draw.polygon( + [ + (x, y + 6), + (x + 4, y + 2), + (x + 12, y), + (x + 22, y + 2), + (x + 28, y + 6), + (x + 30, y + 10), + (x + 28, y + 12), + (x + 2, y + 12), + (x, y + 10), + ], + fill=1, + outline=1, + ) + # Wheels draw.ellipse([x + 4, y + 10, x + 10, y + 16], fill=1, outline=1) # Front wheel draw.ellipse([x + 20, y + 10, x + 26, y + 16], fill=1, outline=1) # Rear wheel - + # Windows draw.line([x + 6, y + 3, x + 18, y + 3], fill=1) # Windshield draw.line([x + 18, y + 3, x + 24, y + 6], fill=1) # Side window + # Generate new animation frames with the improved car design frames = [] for i in range(FRAME_COUNT): frame = Image.new("1", (WIDTH, HEIGHT), 0) draw = ImageDraw.Draw(frame) - + # Compute car position x = int(START_X + (END_X - START_X) * (i / (FRAME_COUNT - 1))) y = HEIGHT // 2 - 6 # Adjusted for new car height @@ -50,5 +62,6 @@ def draw_race_car(draw, x, y): # Save the improved animation gif_path = "./improved_race_car_animation.gif" -frames[0].save(gif_path, save_all=True, append_images=frames[1:], duration=1000//30, loop=0) -gif_path +frames[0].save( + gif_path, save_all=True, append_images=frames[1:], duration=1000 // 30, loop=0 +) diff --git a/src/HL/animation/gif_animate.py b/src/high_level/scripts/animation/gif_animate.py similarity index 95% rename from src/HL/animation/gif_animate.py rename to src/high_level/scripts/animation/gif_animate.py index e19e8a1d..11a85276 100644 --- a/src/HL/animation/gif_animate.py +++ b/src/high_level/scripts/animation/gif_animate.py @@ -1,9 +1,10 @@ import time + from luma.core.interface.serial import i2c from luma.core.render import canvas from luma.oled.device import ssd1306 +from PIL import Image, ImageFont, ImageSequence -from PIL import Image, ImageDraw, ImageFont, ImageSequence # import smbus #type: ignore #ignore the module could not be resolved error because it is a linux only module # bus = smbus.SMBus(1) # Create a new I2C bus diff --git a/src/HL/animation/gif_to_framdata.py b/src/high_level/scripts/animation/gif_to_framdata.py similarity index 100% rename from src/HL/animation/gif_to_framdata.py rename to src/high_level/scripts/animation/gif_to_framdata.py diff --git a/src/HL/animation/image.jpg b/src/high_level/scripts/animation/image.jpg similarity index 100% rename from src/HL/animation/image.jpg rename to src/high_level/scripts/animation/image.jpg diff --git a/src/HL/animation/improved_race_car_animation.gif b/src/high_level/scripts/animation/improved_race_car_animation.gif similarity index 100% rename from src/HL/animation/improved_race_car_animation.gif rename to src/high_level/scripts/animation/improved_race_car_animation.gif diff --git a/src/HL/animation/png_display.py b/src/high_level/scripts/animation/png_display.py similarity index 92% rename from src/HL/animation/png_display.py rename to src/high_level/scripts/animation/png_display.py index ed5b2cb8..a9cbb83c 100644 --- a/src/HL/animation/png_display.py +++ b/src/high_level/scripts/animation/png_display.py @@ -1,8 +1,9 @@ import time + from luma.core.interface.serial import i2c from luma.core.render import canvas from luma.oled.device import ssd1306 -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageFont # I2C configuration serial = i2c(port=1, address=0x3C) @@ -12,7 +13,6 @@ font = ImageFont.load_default() - # Convert SVG to PNG png_path = "animation/image.jpg" diff --git a/scripts/bluetooth_auto/bluethoot_mac.txt b/src/high_level/scripts/bluetooth_auto/bluethoot_mac.txt similarity index 100% rename from scripts/bluetooth_auto/bluethoot_mac.txt rename to src/high_level/scripts/bluetooth_auto/bluethoot_mac.txt diff --git a/scripts/bluetooth_auto/bluethootconnect.sh b/src/high_level/scripts/bluetooth_auto/bluethootconnect.sh similarity index 100% rename from scripts/bluetooth_auto/bluethootconnect.sh rename to src/high_level/scripts/bluetooth_auto/bluethootconnect.sh diff --git a/src/high_level/scripts/displayvoltage.py b/src/high_level/scripts/displayvoltage.py new file mode 100644 index 00000000..0cf16b84 --- /dev/null +++ b/src/high_level/scripts/displayvoltage.py @@ -0,0 +1,59 @@ +import struct + +import smbus # type: ignore #ignore the module could not be resolved error because it is a linux only module +from luma.core.interface.serial import i2c +from luma.core.render import canvas +from luma.oled.device import ssd1306 +from PIL import ImageFont + +bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1 + +# I2C address of the slave +SLAVE_ADDRESS = 0x08 +# I2C configuration +serial = i2c(port=1, address=0x3C) +oled_display = ssd1306(serial) +font = ImageFont.load_default() + + +def write_data(data): + # Convert string to list of ASCII values + data_list = [ord(char) for char in data] + bus.write_i2c_block_data(SLAVE_ADDRESS, 0, data_list) + + +def read_data(num_floats=3): + + # Each float is 4 bytes + length = num_floats * 4 + # Read a block of data from the slave + data = bus.read_i2c_block_data(SLAVE_ADDRESS, 0, length) + # Convert the byte data to floats + if len(data) >= length: + float_values = struct.unpack("f" * num_floats, bytes(data[:length])) + return list(float_values) + else: + raise ValueError("Not enough data received from I2C bus") + + +def display_voltage(): + received = read_data(2) # Adjust length as needed + received = [round(elem, 2) for elem in received] + for i in range(len(received)): + if received[i] < 6: + received[i] = 0.0 + # print(f"Received from slave: {received}") + with canvas(oled_display) as draw: + display_height = oled_display.height + text = f"LiP:{received[0]:.2f}V|NiH:{received[1]:.2f}V" + # _, text_height = draw.textsize(text, font=font) # Get the width and height of the text + # text_height = draw.textlength(text, font=font, direction="ttb") + text_height = 11 + print(f"Text height: {text_height}") + text_y_position = display_height - text_height # Position text at the bottom + draw.text((0, text_y_position), text, fill="white", font=font) + + +if __name__ == "__main__": + while True: + display_voltage() diff --git a/src/high_level/scripts/launch_serveur.py b/src/high_level/scripts/launch_serveur.py new file mode 100644 index 00000000..9d2e729e --- /dev/null +++ b/src/high_level/scripts/launch_serveur.py @@ -0,0 +1,27 @@ +# --------------------------------------------------------------------------------------------------- +# main +# --------------------------------------------------------------------------------------------------- +import logging + +from high_level import Serveur + +if __name__ == "__main__": + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", + handlers=[ + logging.FileHandler("/home/intech/CoVAPSy/covapsy.log"), + logging.StreamHandler(), + ], + ) + log_serveur = logging.getLogger("__main__") + log_serveur.setLevel(level=logging.DEBUG) + + log_serveur = logging.getLogger("src.HL") + log_serveur.setLevel(level=logging.DEBUG) + + log_lidar = logging.getLogger("src.HL.actionneur_capteur.Lidar") + log_lidar.setLevel(level=logging.INFO) + + boot = Serveur() + boot.main() diff --git a/src/high_level/scripts/live_display_lidar.py b/src/high_level/scripts/live_display_lidar.py new file mode 100644 index 00000000..6f4ab392 --- /dev/null +++ b/src/high_level/scripts/live_display_lidar.py @@ -0,0 +1,15 @@ +import time + +from actionneur_capteur import Lidar + +IP = "192.168.0.10" +PORT = 10940 + +if __name__ == "__main__": + sensor = Lidar(IP, PORT) + sensor.stop() + # sensor.singleRead(0, 1080) + time.sleep(2) + + sensor.start_continuous(0, 1080) + sensor.start_plotter() diff --git a/src/high_level/scripts/min_lidar.py b/src/high_level/scripts/min_lidar.py new file mode 100644 index 00000000..035a5a23 --- /dev/null +++ b/src/high_level/scripts/min_lidar.py @@ -0,0 +1,61 @@ +# you must run export PYTHONPATH=/home/intech/CoVAPSy/src/high_level before running this script + +import time +import numpy as np +from src.actionneur_capteur.lidar import Lidar +import signal +import sys + +IP = "192.168.0.10" +PORT = 10940 +START = 0 +END = 1080 + +running = True + + +def signal_handler(sig, frame): + global running + print("\nArrêt demandé (Ctrl+C).") + running = False + + +def main(): + global running + + signal.signal(signal.SIGINT, signal_handler) + + print("Initialisation du lidar...") + lidar = Lidar(IP, PORT, start_step=START) + + print("Démarrage scan continu...") + lidar.start_continuous(START, END) + + while np.all(lidar.r_distance == 0): + time.sleep(0.01) + + print("Scan reçu. Début enregistrement min...") + print("Appuie sur Ctrl+C pour arrêter proprement.") + + min_distance = np.full(lidar.r_distance.shape, np.inf, dtype=float) + + while running: + current = lidar.r_distance.astype(float) + valid = current > 0 + + min_distance[valid] = np.minimum(min_distance[valid], current[valid]) + + time.sleep(0.01) + + print("Arrêt du lidar...") + lidar.stop() + + result = min_distance + np.save("src/programs/data/min_lidar.npy", result) + + print("Sauvegardé dans ../src/high_level/programs/data/min_lidar.npy") + print("Shape:", result.shape) + + +if __name__ == "__main__": + main() diff --git a/scripts/Onetime_lidar.py b/src/high_level/scripts/onetime_lidar.py similarity index 60% rename from scripts/Onetime_lidar.py rename to src/high_level/scripts/onetime_lidar.py index 435f5461..2872d21f 100644 --- a/scripts/Onetime_lidar.py +++ b/src/high_level/scripts/onetime_lidar.py @@ -1,23 +1,25 @@ -from HL.Lidar import Lidar +from actionneur_capteur import Lidar -IP = '192.168.0.10' +IP = "192.168.0.10" PORT = 10940 + def main(): # Create an instance of HokuyoReader lidar = Lidar(IP, PORT) - + # Stop any previous measurements lidar.stop() - + # Start a single read - lidar.singleRead(0, 1080) - + lidar.single_read(0, 1080) + # Print the distance values - print(lidar.rDistance) - + print(lidar.r_distance) + # Stop the lidar lidar.stop() -if __name__ == '__main__': - main() \ No newline at end of file + +if __name__ == "__main__": + main() diff --git a/scripts/readSPI.py b/src/high_level/scripts/read_spi.py similarity index 50% rename from scripts/readSPI.py rename to src/high_level/scripts/read_spi.py index 91cbb479..16c36556 100644 --- a/scripts/readSPI.py +++ b/src/high_level/scripts/read_spi.py @@ -1,28 +1,31 @@ -import spidev #type: ignore #ignore the module could not be resolved error because it is a linux only module -import time import struct +import time + +import spidev # type: ignore #ignore the module could not be resolved error because it is a linux only module # Initialize SPI spi = spidev.SpiDev() spi.open(0, 0) # Open SPI bus 0, device (CS) 0 spi.max_speed_hz = 50000 + def read_voltage(): # Send a dummy byte to initiate SPI communication response = spi.xfer2([0x00] * 8) # 8 bytes to read two float values (4 bytes each) - + # Convert the received bytes to float values - voltage_LiPo = struct.unpack('f', bytes(response[0:4]))[0] - voltage_NiMh = struct.unpack('f', bytes(response[4:8]))[0] - - return voltage_LiPo, voltage_NiMh + voltage_lipo = struct.unpack("f", bytes(response[0:4]))[0] + voltage_nimh = struct.unpack("f", bytes(response[4:8]))[0] + + return voltage_lipo, voltage_nimh + try: while True: - voltage_LiPo, voltage_NiMh = read_voltage() - print(f"LiPo Voltage: {voltage_LiPo:.2f} V, NiMh Voltage: {voltage_NiMh:.2f} V") + voltage_lipo, voltage_nimh = read_voltage() + print(f"LiPo Voltage: {voltage_lipo:.2f} V, NiMh Voltage: {voltage_nimh:.2f} V") time.sleep(1) # Adjust the delay as needed except KeyboardInterrupt: spi.close() - print("SPI communication closed.") \ No newline at end of file + print("SPI communication closed.") diff --git a/src/high_level/scripts/remote_control_controller.py b/src/high_level/scripts/remote_control_controller.py new file mode 100644 index 00000000..ccbeac32 --- /dev/null +++ b/src/high_level/scripts/remote_control_controller.py @@ -0,0 +1,107 @@ +import socket +import struct +import time +from threading import Thread + +import pygame + +################################################### +# Init ZMQ +################################################### +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + +def send_data(): + global target_speed, direction + while True: + packet = struct.pack("ff", target_speed, direction) + sock.sendto(packet, ("192.168.1.10", 5556)) + time.sleep(0.05) + + +################################################### +# Vehicle control variables +################################################### +direction = 0 +target_speed = 0 + +max_target_speed = 2 +min_target_speed = -2 +angle_degree_max = 18 + + +def map_range(x, in_min, in_max, out_min, out_max): + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + + +def set_direction_degrees(angle_degrees): + global direction + direction = angle_degrees + print(direction, target_speed) + + +def set_target_speed(vit): + global target_speed + target_speed = vit + + +if __name__ == "__main__": + ################################################### + # Init pygame + controller + ################################################### + pygame.init() + pygame.joystick.init() + + if pygame.joystick.get_count() == 0: + print("no joystick detected") + exit(1) + + joy = pygame.joystick.Joystick(0) + joy.init() + print("joystick detected:", joy.get_name()) + + ################################################### + # Main loop + ################################################### + Thread(target=send_data, daemon=True).start() + + try: + while True: + pygame.event.pump() + + # Axes: + # For Xbox/PS4 USB: + # L2 = axis 2 (often 0..1) + # R2 = axis 5 (often 0..1) + # Left stick horizontal = axis 0 (-1..1) + + axis_lx = joy.get_axis(0) # Left right + axis_l2 = joy.get_axis(2) # Reverse acceleration + axis_r2 = joy.get_axis(5) # Forward acceleration + + # Direction + direction = map_range(axis_lx, -1, 1, -angle_degree_max, angle_degree_max) + set_direction_degrees(round(direction)) + + # Acceleration + accel = (axis_r2 + 1) / 2 + brake = (axis_l2 + 1) / 2 + + # Some controllers go from -1..1, others 0..1 + + # Forward + if accel > 0.05: + vit = accel * max_target_speed * 1000 + set_target_speed(round(vit)) + + # Reverse + elif brake > 0.05: + vit = brake * min_target_speed * 1000 + set_target_speed(round(vit)) + else: + set_target_speed(0) + time.sleep(0.01) + + except KeyboardInterrupt: + print("End of program.") + pygame.quit() diff --git a/scripts/sender.py b/src/high_level/scripts/sender.py similarity index 55% rename from scripts/sender.py rename to src/high_level/scripts/sender.py index 3c8752ea..610d3de1 100644 --- a/scripts/sender.py +++ b/src/high_level/scripts/sender.py @@ -1,15 +1,17 @@ -from time import sleep -import smbus #type: ignore #ignore the module could not be resolved error because it is a linux only module import struct +from time import sleep + +import smbus SLAVE_ADDRESS = 0x08 # Create an SMBus instance bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1 + def write_data(float_data): # Convert the float to bytes print(float_data) - byte_data = struct.pack('f', float_data) + byte_data = struct.pack("f", float_data) # Convert the bytes to a list of integers int_data = list(byte_data) print(int_data) @@ -17,19 +19,7 @@ def write_data(float_data): # Write the data to the I2C bus bus.write_i2c_block_data(SLAVE_ADDRESS, int_data[0], int_data[1:4]) -def read_data(num_floats=3): - # Each float is 4 bytes - length = num_floats * 4 - # Read a block of data from the slave - data = bus.read_i2c_block_data(SLAVE_ADDRESS, 0, length) - # Convert the byte data to floats - if len(data) >= length: - float_values = struct.unpack('f' * num_floats, bytes(data[:length])) - return list(float_values) - else: - raise ValueError("Not enough data received from I2C bus") - def read_data(num_floats=3): # Each float is 4 bytes length = num_floats * 4 @@ -37,14 +27,14 @@ def read_data(num_floats=3): data = bus.read_i2c_block_data(SLAVE_ADDRESS, 0, length) # Convert the byte data to floats if len(data) >= length: - float_values = struct.unpack('f' * num_floats, bytes(data[:length])) + float_values = struct.unpack("f" * num_floats, bytes(data[:length])) return list(float_values) else: raise ValueError("Not enough data received from I2C bus") -i=0.56 -while True : + +i = 0.56 +while True: write_data(i) sleep(0.5) - i+=1 - + i += 1 diff --git a/scripts/setup.sh b/src/high_level/scripts/setup.sh similarity index 100% rename from scripts/setup.sh rename to src/high_level/scripts/setup.sh diff --git a/src/high_level/scripts/simple.py b/src/high_level/scripts/simple.py new file mode 100644 index 00000000..29a13246 --- /dev/null +++ b/src/high_level/scripts/simple.py @@ -0,0 +1,127 @@ +import time + +from rpi_hardware_pwm import HardwarePWM + +from actionneur_capteur import Lidar + +IP = "192.168.0.10" +PORT = 10940 + +# parameters for speed_m_s function +drive_direction = ( + 1 # -1 for inverted ESCs or if a small ratio corresponds to forward motion +) +pwm_stop_drive = 7.53 +dead_zone_drive = 0.5 +delta_pwm_max_drive = 1.1 # PWM value at which maximum speed is reached + +max_speed_m_s_hard = 8 # physical maximum speed of the car +max_speed_m_s_soft = 2 # desired maximum speed + + +# parameters for set_steering_degree function +steering_direction = ( + 1 # 1 for min PWM angle to the left, -1 for min PWM angle to the right +) +steering_pwm_min = 6.91 +steering_pwm_max = 10.7 +steering_pwm_center = 8.805 + +max_steering_angle_deg = +18 # to the left +steering_angle_deg = 0 + +pwm_drive = HardwarePWM( + pwm_channel=0, hz=50, chip=2 +) # chip 2 on Pi 5 per documentation +pwm_drive.start(pwm_stop_drive) + + +def set_speed_m_s(speed_m_s): + if speed_m_s > max_speed_m_s_soft: + speed_m_s = max_speed_m_s_soft + elif speed_m_s < -max_speed_m_s_hard: + speed_m_s = -max_speed_m_s_hard + + if speed_m_s == 0: + pwm_drive.change_duty_cycle(pwm_stop_drive) + elif speed_m_s > 0: + speed = speed_m_s * delta_pwm_max_drive / max_speed_m_s_hard + pwm_drive.change_duty_cycle( + pwm_stop_drive + drive_direction * (dead_zone_drive + speed) + ) + elif speed_m_s < 0: + speed = speed_m_s * delta_pwm_max_drive / max_speed_m_s_hard + pwm_drive.change_duty_cycle( + pwm_stop_drive - drive_direction * (dead_zone_drive - speed) + ) + + +def reverse(): + set_speed_m_s(-max_speed_m_s_hard) + time.sleep(0.2) + set_speed_m_s(0) + time.sleep(0.2) + set_speed_m_s(-1) + + +pwm_steering = HardwarePWM( + pwm_channel=1, hz=50, chip=2 +) # chip 2 on Pi 5 per documentation +pwm_steering.start(steering_pwm_center) + + +def set_steering_degree(angle_deg): + global steering_pwm_min + global steering_pwm_max + global steering_pwm_center + + steering_pwm = steering_pwm_center + steering_direction * ( + steering_pwm_max - steering_pwm_min + ) * angle_deg / (2 * max_steering_angle_deg) + + if steering_pwm > steering_pwm_max: + steering_pwm = steering_pwm_max + if steering_pwm < steering_pwm_min: + steering_pwm = steering_pwm_min + + pwm_steering.change_duty_cycle(steering_pwm) + + +# lidar connection and startup +lidar = Lidar(IP, PORT) +lidar.stop() +lidar.start_continuous(0, 1080) + +lidar_table_mm = [0] * 360 # create a table of 360 zeros + +time.sleep(1) # lidar startup time + +try: + while True: + for angle in range(len(lidar_table_mm)): + # translation of lidar angle to table angle + if angle > 135 and angle < 225: + lidar_table_mm[angle] = float("nan") + else: + lidar_table_mm[angle] = lidar.r_distance[540 + (-angle * 4)] + + # steering angle is the difference between lidar rays + # measured at -60 and +60 degrees + steering_angle_deg = 0.02 * (lidar_table_mm[60] - lidar_table_mm[-60]) + print(lidar_table_mm[60], lidar_table_mm[-60], steering_angle_deg) + + set_steering_degree(steering_angle_deg) + speed_m_s = 0.05 + set_speed_m_s(speed_m_s) + + time.sleep(0.1) + ############################################## +except KeyboardInterrupt: # catch CTRL+C + speed_m_s = 0 + set_speed_m_s(speed_m_s) + print("end of acquisitions") + +# stop and disconnect lidar and motors +lidar.stop() +pwm_steering.stop() +pwm_drive.start(pwm_stop_drive) diff --git a/scripts/startup.sh b/src/high_level/scripts/startup.sh similarity index 100% rename from scripts/startup.sh rename to src/high_level/scripts/startup.sh diff --git a/scripts/envoie_camera_sur_web.py b/src/high_level/scripts/strream_camera_on_web.py similarity index 99% rename from scripts/envoie_camera_sur_web.py rename to src/high_level/scripts/strream_camera_on_web.py index b01bc5d0..a3218ce6 100644 --- a/scripts/envoie_camera_sur_web.py +++ b/src/high_level/scripts/strream_camera_on_web.py @@ -82,7 +82,7 @@ class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): daemon_threads = True -if __name__ = "__name__": +if __name__ == "__name__": # Create Picamera2 instance and configure it picam2 = Picamera2() picam2.configure(picam2.create_video_configuration(main={"size": (640, 480)})) diff --git a/src/high_level/scripts/systemctl/autotech_init.service b/src/high_level/scripts/systemctl/autotech_init.service new file mode 100644 index 00000000..d38a227a --- /dev/null +++ b/src/high_level/scripts/systemctl/autotech_init.service @@ -0,0 +1,14 @@ +[Unit] +Description=Initialisation de la voiture +After=network.target + +[Service] +Type=simple +User=intech +WorkingDirectory=/home/intech/CoVAPSy/src/high_level +ExecStart=/home/intech/.local/bin/uv run scripts/launch_serveur.py +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/src/high_level/scripts/systemctl/mediamtx_start.service b/src/high_level/scripts/systemctl/mediamtx_start.service new file mode 100644 index 00000000..bdef7328 --- /dev/null +++ b/src/high_level/scripts/systemctl/mediamtx_start.service @@ -0,0 +1,14 @@ +[Unit] +Description=lancement de mediamtx +After=network.target + +[Service] +Type=simple +User=intech +WorkingDirectory=/home/intech/ +ExecStart=/home/intech/mediamtx +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/scripts/ToF_print.py b/src/high_level/scripts/tof_print_vl53l0x.py similarity index 100% rename from scripts/ToF_print.py rename to src/high_level/scripts/tof_print_vl53l0x.py diff --git a/src/high_level/scripts/tof_print_vl53l1x.py b/src/high_level/scripts/tof_print_vl53l1x.py new file mode 100644 index 00000000..1db68e02 --- /dev/null +++ b/src/high_level/scripts/tof_print_vl53l1x.py @@ -0,0 +1,46 @@ +import time + +import board +import busio +from adafruit_vl53l1x import VL53L1X + +# Initialize I2C bus +i2c = busio.I2C(board.SCL, board.SDA) + +# Initialize the VL53L1X sensor +try: + tof = VL53L1X(i2c) + print("VL53L1X sensor initialized successfully.") +except Exception as e: + print("Failed to initialize VL53L1X sensor:", e) + exit(1) + +# --- CONFIGURATION (important for VL53L1X) --- + +# Distance mode: +# 1 = Short (~1.3m, faster, better precision) +# 2 = Long (~4m, a bit noisier) +tof.distance_mode = 2 # Long range (change if needed) + +# Timing budget (ms) +# larger = more accurate, slower +tof.timing_budget = 50 # good compromise + +# Start ranging +tof.start_ranging() + +print("Starting continuous ranging...") + +try: + while True: + if tof.data_ready: + distance = tof.distance # in cm + print(f"Distance in cm: {distance}") + tof.clear_interrupt() + + time.sleep(0.05) + +except KeyboardInterrupt: + print("Stopping ranging...") + tof.stop_ranging() + print("Program terminated.") diff --git a/src/HL/Vision/Analyse_Image.py b/src/high_level/scripts/vision/analyse_image.py similarity index 70% rename from src/HL/Vision/Analyse_Image.py rename to src/high_level/scripts/vision/analyse_image.py index 37f64c3d..af6e16d3 100644 --- a/src/HL/Vision/Analyse_Image.py +++ b/src/high_level/scripts/vision/analyse_image.py @@ -1,11 +1,7 @@ import numpy as np from PIL import Image -COLOUR_KEY = { - "green": 1, - "red": -1, - "none": 0 -} +COLOUR_KEY = {"green": 1, "red": -1, "none": 0} COLOR_THRESHOLD = 20 # Threshold for color intensity difference Y_OFFSET = 0.5 # Offset for the y-axis in the image counter = 1 @@ -15,6 +11,7 @@ def get_intensity(image, color): """Calculate the average intensity of a specific color channel in the image.""" return np.mean(image[:, :, color]) + def is_running_in_reversed(image, LEFT_IS_GREEN=True): """ Check if the car is running in reverse. @@ -22,21 +19,26 @@ def is_running_in_reversed(image, LEFT_IS_GREEN=True): """ print(image, type(image)) height, width, _ = image.shape - left_half = image[:, :width // 2] - right_half = image[:, width // 2:] + left_half = image[:, : width // 2] + right_half = image[:, width // 2 :] left_red_intensity = np.mean(left_half[:, :, 0]) # Red channel in RGB right_red_intensity = np.mean(right_half[:, :, 0]) # Red channel in RGB - + # Allow to change the color of the left and right side - return (left_red_intensity <= right_red_intensity) if LEFT_IS_GREEN else (left_red_intensity > right_red_intensity) + return ( + (left_red_intensity <= right_red_intensity) + if LEFT_IS_GREEN + else (left_red_intensity > right_red_intensity) + ) + def is_green_or_red(image): """ Check if the car is facing a green or red wall by analyzing the bottom half of the image. """ height, _, _ = image.shape - bottom_half = image[height // 2:, :, :] # Slice the bottom half of the image + bottom_half = image[height // 2 :, :, :] # Slice the bottom half of the image red_intensity = np.mean(bottom_half[:, :, 0]) # Red channel in RGB green_intensity = np.mean(bottom_half[:, :, 1]) # Green channel in RGB @@ -47,8 +49,8 @@ def is_green_or_red(image): return COLOUR_KEY["red"] return COLOUR_KEY["none"] - -def recreate_image_from_matrix(image, matrix,adjusted_width, vector_size=128): + +def recreate_image_from_matrix(image, matrix, adjusted_width, vector_size=128): """ Recreate an image from the matrix of -1, 0, and 1 and append it to the bottom of the sliced image. """ @@ -79,9 +81,12 @@ def recreate_image_from_matrix(image, matrix,adjusted_width, vector_size=128): # Append the recreated image to the bottom of the sliced image combined_image = np.vstack((image, recreated_image_resized)) - Image.fromarray(combined_image).convert("RGB").save(f"debug_combined_image{counter}.jpg") + Image.fromarray(combined_image).convert("RGB").save( + f"debug_combined_image{counter}.jpg" + ) counter += 1 + def camera_matrix(image, vector_size=128): """ Create a matrix of -1, 0, and 1 for a line in the image. The matrix size is 128. @@ -102,46 +107,59 @@ def camera_matrix(image, vector_size=128): bucket_size = adjusted_width // vector_size # Calculate red and green intensities for all segments at once - reshaped_red = sliced_image[:, :, 0].reshape(sliced_image.shape[0], vector_size, bucket_size) - reshaped_green = sliced_image[:, :, 1].reshape(sliced_image.shape[0], vector_size, bucket_size) + reshaped_red = sliced_image[:, :, 0].reshape( + sliced_image.shape[0], vector_size, bucket_size + ) + reshaped_green = sliced_image[:, :, 1].reshape( + sliced_image.shape[0], vector_size, bucket_size + ) red_intensities = np.mean(reshaped_red, axis=(0, 2)) green_intensities = np.mean(reshaped_green, axis=(0, 2)) # Determine the color for each segment - output_matrix[red_intensities > green_intensities + COLOR_THRESHOLD] = COLOUR_KEY["red"] - output_matrix[green_intensities > red_intensities + COLOR_THRESHOLD] = COLOUR_KEY["green"] - output_matrix[np.abs(red_intensities - green_intensities) <= COLOR_THRESHOLD] = COLOUR_KEY["none"] + output_matrix[red_intensities > green_intensities + COLOR_THRESHOLD] = COLOUR_KEY[ + "red" + ] + output_matrix[green_intensities > red_intensities + COLOR_THRESHOLD] = COLOUR_KEY[ + "green" + ] + output_matrix[np.abs(red_intensities - green_intensities) <= COLOR_THRESHOLD] = ( + COLOUR_KEY["none"] + ) # Recreate the image from the matrix recreate_image_from_matrix(sliced_image, output_matrix, adjusted_width, vector_size) return output_matrix + if __name__ == "__main__": # Example usage: # open image images_paths = [ - "src\HL\Vision\Image_Piste\image.jpg", - "src\HL\Vision\Image_Piste\image2.jpg", - "src\HL\Vision\Image_Piste\image3.jpg", - "src\HL\Vision\Image_Piste\image4.png", - "src\HL\Vision\Image_Piste\image5.png", - "src\HL\Vision\Image_Piste\image6.png", - "src\HL\Vision\Image_Piste\image7.png", - "src\HL\Vision\Image_Piste\image8.png", - "src\HL\Vision\Image_Piste\image9.png", - "src\HL\Vision\Image_Piste\image10.png", - "src\HL\Vision\Image_Piste\image11.png", - "src\HL\Vision\Image_Piste\image12.png", - "src\HL\Vision\Image_Piste\image13.png" + "src/HL/Vision/Image_Piste/image.jpg", + "src/HL/Vision/Image_Piste/image2.jpg", + "src/HL/Vision/Image_Piste/image3.jpg", + "src/HL/Vision/Image_Piste/image4.png", + "src/HL/Vision/Image_Piste/image5.png", + "src/HL/Vision/Image_Piste/image6.png", + "src/HL/Vision/Image_Piste/image7.png", + "src/HL/Vision/Image_Piste/image8.png", + "src/HL/Vision/Image_Piste/image9.png", + "src/HL/Vision/Image_Piste/image10.png", + "src/HL/Vision/Image_Piste/image11.png", + "src/HL/Vision/Image_Piste/image12.png", + "src/HL/Vision/Image_Piste/image13.png", ] for image_path in images_paths: # print(f"Processing image: {image_path}") - pil_image = Image.open(image_path).convert("RGB") # Open and ensure it's in RGB format + pil_image = Image.open(image_path).convert( + "RGB" + ) # Open and ensure it's in RGB format image = np.array(pil_image) # Convert to NumPy array - is_green_or_red_bool= is_green_or_red(image) + is_green_or_red_bool = is_green_or_red(image) is_running_in_reverse = is_running_in_reversed(image) camera_matrix(image) - print(f"Image: {image_path}, is_green_or_red: {is_green_or_red_bool}, is_running_in_reverse: {is_running_in_reverse}") - - \ No newline at end of file + print( + f"Image: {image_path}, is_green_or_red: {is_green_or_red_bool}, is_running_in_reverse: {is_running_in_reverse}" + ) diff --git a/src/HL/Vision/Cam.py b/src/high_level/scripts/vision/cam.py similarity index 71% rename from src/HL/Vision/Cam.py rename to src/high_level/scripts/vision/cam.py index e9b61823..600252af 100644 --- a/src/HL/Vision/Cam.py +++ b/src/high_level/scripts/vision/cam.py @@ -1,8 +1,10 @@ -from picamera2 import Picamera2, Preview # type: ignore -from PIL import Image # For saving images -import time import os -import shutil +import shutil +import time + +from picamera2 import Picamera2 +from PIL import Image # For saving images + # Initialize the camera # Set LIBCAMERA_LOG_LEVELS to suppress INFO logs @@ -11,16 +13,19 @@ picam2 = Picamera2() + def run_test(resolution): # Configure the camera for preview - config = picam2.create_preview_configuration(main={"size": resolution}) # full sensor resolution (3280, 2464) + config = picam2.create_preview_configuration( + main={"size": resolution} + ) # full sensor resolution (3280, 2464) picam2.configure(config) # Start the camera picam2.start() # Parameters - + frame_count = 0 # Counter to keep track of saved frames save_dir = "Captured_Frames" # Directory to save frames if os.path.exists(save_dir): @@ -34,7 +39,7 @@ def run_test(resolution): total_path_creation_time = 0 total_save_time = 0 - Start_time = time.time() # Start time for the image stream + start_time = time.time() # Start time for the image stream for i in range(N): init_time = time.time() # Initialize the time for the first frame @@ -42,27 +47,27 @@ def run_test(resolution): # Capture the frame frame = picam2.capture_array() capture_time = time.time() # Time after capturing the frame - total_capture_time += (capture_time - init_time) + total_capture_time += capture_time - init_time # Convert the frame to an image image = Image.fromarray(frame) array_time = time.time() # Time after converting the frame to an image - total_array_time += (array_time - capture_time) + total_array_time += array_time - capture_time # Convert the image to RGB - #image = image.convert("RGB") + image = image.convert("RGB") convert_time = time.time() # Time after converting the image to RGB - total_convert_time += (convert_time - array_time) + total_convert_time += convert_time - array_time # Create the file path - #frame_path = os.path.join(save_dir, f"frame_{frame_count:04d}.jpg") + # frame_path = os.path.join(save_dir, f"frame_{frame_count:04d}.jpg") path_creation_time = time.time() # Time after creating the path - total_path_creation_time += (path_creation_time - convert_time) + total_path_creation_time += path_creation_time - convert_time # Save the image - #image.save(frame_path) + # image.save(frame_path) save_time = time.time() # Time after saving the image - total_save_time += (save_time - path_creation_time) + total_save_time += save_time - path_creation_time frame_count += 1 picam2.stop() # Stop the camera @@ -72,7 +77,7 @@ def run_test(resolution): average_convert_time = total_convert_time / N average_path_creation_time = total_path_creation_time / N average_save_time = total_save_time / N - total_time = time.time() - Start_time + total_time = time.time() - start_time # Print results print(f"Average Capture time: {average_capture_time:.5f} seconds") @@ -80,12 +85,14 @@ def run_test(resolution): print(f"Average Convert time: {average_convert_time:.5f} seconds") print(f"Average Path creation time: {average_path_creation_time:.5f} seconds") print(f"Average Save time: {average_save_time:.5f} seconds") - print(f"Total time for {N} iterations: {total_time:.5f} seconds. FPS: {N / total_time:.2f}") - - + print( + f"Total time for {N} iterations: {total_time:.5f} seconds. FPS: {N / total_time:.2f}" + ) + + if __name__ == "__main__": N = 100 # Number of iterations to measure - - for resolution in [(3280, 2464), (1280, 720), (1920, 1080),(640, 480), (320, 240)]: + + for resolution in [(3280, 2464), (1280, 720), (1920, 1080), (640, 480), (320, 240)]: print(f"Testing resolution: {resolution}") - run_test(resolution) \ No newline at end of file + run_test(resolution) diff --git a/src/HL/Vision/Image_Piste/image.jpg b/src/high_level/scripts/vision/images_piste/image.jpg similarity index 100% rename from src/HL/Vision/Image_Piste/image.jpg rename to src/high_level/scripts/vision/images_piste/image.jpg diff --git a/src/HL/Vision/Image_Piste/image10.png b/src/high_level/scripts/vision/images_piste/image10.png similarity index 100% rename from src/HL/Vision/Image_Piste/image10.png rename to src/high_level/scripts/vision/images_piste/image10.png diff --git a/src/HL/Vision/Image_Piste/image11.png b/src/high_level/scripts/vision/images_piste/image11.png similarity index 100% rename from src/HL/Vision/Image_Piste/image11.png rename to src/high_level/scripts/vision/images_piste/image11.png diff --git a/src/HL/Vision/Image_Piste/image12.png b/src/high_level/scripts/vision/images_piste/image12.png similarity index 100% rename from src/HL/Vision/Image_Piste/image12.png rename to src/high_level/scripts/vision/images_piste/image12.png diff --git a/src/HL/Vision/Image_Piste/image13.png b/src/high_level/scripts/vision/images_piste/image13.png similarity index 100% rename from src/HL/Vision/Image_Piste/image13.png rename to src/high_level/scripts/vision/images_piste/image13.png diff --git a/src/HL/Vision/Image_Piste/image2.jpg b/src/high_level/scripts/vision/images_piste/image2.jpg similarity index 100% rename from src/HL/Vision/Image_Piste/image2.jpg rename to src/high_level/scripts/vision/images_piste/image2.jpg diff --git a/src/HL/Vision/Image_Piste/image3.jpg b/src/high_level/scripts/vision/images_piste/image3.jpg similarity index 100% rename from src/HL/Vision/Image_Piste/image3.jpg rename to src/high_level/scripts/vision/images_piste/image3.jpg diff --git a/src/HL/Vision/Image_Piste/image4.png b/src/high_level/scripts/vision/images_piste/image4.png similarity index 100% rename from src/HL/Vision/Image_Piste/image4.png rename to src/high_level/scripts/vision/images_piste/image4.png diff --git a/src/HL/Vision/Image_Piste/image5.png b/src/high_level/scripts/vision/images_piste/image5.png similarity index 100% rename from src/HL/Vision/Image_Piste/image5.png rename to src/high_level/scripts/vision/images_piste/image5.png diff --git a/src/HL/Vision/Image_Piste/image6.png b/src/high_level/scripts/vision/images_piste/image6.png similarity index 100% rename from src/HL/Vision/Image_Piste/image6.png rename to src/high_level/scripts/vision/images_piste/image6.png diff --git a/src/HL/Vision/Image_Piste/image7.png b/src/high_level/scripts/vision/images_piste/image7.png similarity index 100% rename from src/HL/Vision/Image_Piste/image7.png rename to src/high_level/scripts/vision/images_piste/image7.png diff --git a/src/HL/Vision/Image_Piste/image8.png b/src/high_level/scripts/vision/images_piste/image8.png similarity index 100% rename from src/HL/Vision/Image_Piste/image8.png rename to src/high_level/scripts/vision/images_piste/image8.png diff --git a/src/HL/Vision/Image_Piste/image9.png b/src/high_level/scripts/vision/images_piste/image9.png similarity index 100% rename from src/HL/Vision/Image_Piste/image9.png rename to src/high_level/scripts/vision/images_piste/image9.png diff --git a/src/HL/Vision/Imagestream.py b/src/high_level/scripts/vision/imagestream.py similarity index 63% rename from src/HL/Vision/Imagestream.py rename to src/high_level/scripts/vision/imagestream.py index facd5f98..2f51f0e0 100644 --- a/src/HL/Vision/Imagestream.py +++ b/src/high_level/scripts/vision/imagestream.py @@ -1,9 +1,8 @@ -from picamera2 import Picamera2, Preview # type: ignore #ignore the module could not be resolved error because it is a rpi only module -from PIL import Image # For saving images -import time import os -import threading +import time +from picamera2 import Picamera2 +from PIL import Image def main(): @@ -11,19 +10,20 @@ def main(): picam2 = Picamera2() # Configure the camera for preview - config = picam2.create_preview_configuration(main={"size": (1920, 1080)}, format= "BGR888") + config = picam2.create_preview_configuration( + main={"size": (1920, 1080), "format": "BGR888"} + ) picam2.configure(config) # Start the camera picam2.start() - - Start_time = time.time() # Start time for the image stream + start_time = time.time() # Start time for the image stream try: frame_count = 0 # Counter to keep track of saved frames save_dir = "Captured_Frames" # Directory to save frames os.makedirs(save_dir, exist_ok=True) # Create the directory if it doesn't exist - + while True: frame = picam2.capture_array() image = Image.fromarray(frame) @@ -31,18 +31,21 @@ def main(): frame_path = os.path.join(save_dir, f"frame_{frame_count:04d}.jpg") image.save(frame_path) frame_count += 1 - + # Display the frame in a window # cv2.imshow("Image Stream", frame) - + # Exit the stream when 'q' is pressed - if time.time() - Start_time > 1: - print(f"Captured {frame_count} frames in {time.time() - Start_time:.2f} seconds") + if time.time() - start_time > 1: + print( + f"Captured {frame_count} frames in {time.time() - start_time:.2f} seconds" + ) frame_count = 0 - Start_time = time.time() + start_time = time.time() finally: # Stop the camera and close the window picam2.stop() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/scripts/writeSPI.py b/src/high_level/scripts/write_spi.py similarity index 100% rename from scripts/writeSPI.py rename to src/high_level/scripts/write_spi.py diff --git a/src/high_level/src/actionneur_capteur/__init__.py b/src/high_level/src/actionneur_capteur/__init__.py new file mode 100644 index 00000000..c845b584 --- /dev/null +++ b/src/high_level/src/actionneur_capteur/__init__.py @@ -0,0 +1,6 @@ +from .camera import Camera +from .lidar import Lidar +from .master_i2c import I2CArduino +from .tof import ToF + +__all__ = ["Camera", "Lidar", "I2CArduino", "ToF"] diff --git a/src/high_level/src/actionneur_capteur/camera.py b/src/high_level/src/actionneur_capteur/camera.py new file mode 100644 index 00000000..cd8f57cf --- /dev/null +++ b/src/high_level/src/actionneur_capteur/camera.py @@ -0,0 +1,385 @@ +import asyncio +import logging +import os +import threading +import time +from typing import Optional, cast + +import aiohttp +import cv2 +import numpy as np +import scipy as sp +from aiortc import ( + RTCConfiguration, + RTCPeerConnection, + RTCSessionDescription, + VideoStreamTrack, +) +from av import VideoFrame +from PIL import Image + +N_IMAGES = 100 # Number of images to capture +SAVE_DIR = "Captured_Frames" # Directory to save frames +DEBUG_DIR = "Debug" # Directory for debug images +DEBUG_DIR_wayfinding = "Debug_Wayfinding" # Directory for wayfinding debug images +COLOUR_KEY = {"green": 1, "red": -1, "none": 0} +COLOR_THRESHOLD = 20 # Threshold for color intensity difference +Y_OFFSET = -80 # Offset for the y-axis in the image + + +class Camera: + """ + Camera = client WebRTC (WHEP) vers MediaMTX. + MediaMTX ouvre la PiCam (source: rpiCamera). Python ne fait que consommer. + """ + + def __init__(self, whep_url: str = "http://192.168.0.20:8889/cam/whep"): + self.log = logging.getLogger(__name__) + self.whep_url = whep_url + self.debug_counter = 0 + self.last_frame = np.zeros((0, 0, 0)) + self._lock = threading.Lock() + + self._stop_flag = threading.Event() + self._frame_queue: asyncio.Queue[VideoFrame] = asyncio.Queue(maxsize=2) + self._thread = threading.Thread(target=self._thread_main, daemon=True) + + self._thread.start() + + # --------- thread -> event loop asyncio --------- + def _thread_main(self): + asyncio.run(self._run_forever()) + + async def _run_forever(self): + # boucle de reconnexion automatique + while not self._stop_flag.is_set(): + try: + await self._run_once(self.whep_url) + except Exception as e: + self.log.warning("WHEP client error: %s", e) + + # petit backoff avant de retenter + await asyncio.sleep(0.5) + + async def _processing_loop(self): + while not self._stop_flag.is_set(): + frame = await self._frame_queue.get() + # H264 decoding -> numpy (OUTSIDE WebRTC) + img_bgr = frame.to_ndarray(format="bgr24") + img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) + + # optional but recommended (algorithm stability) + img_rgb = cv2.resize(img_rgb, (320, 240)) + + with self._lock: + self.last_frame = img_rgb + + async def _wait_ice_complete(self, pc: RTCPeerConnection, timeout: float = 2.0): + if pc.iceGatheringState == "complete": + return + + ev = asyncio.Event() + + @pc.on("icegatheringstatechange") + def _on_ice(): + if pc.iceGatheringState == "complete": + ev.set() + + try: + await asyncio.wait_for(ev.wait(), timeout=timeout) + except asyncio.TimeoutError: + self.log.warning("ICE gathering not complete after timeout") + pass + + async def _run_once(self, url: str): + config = RTCConfiguration(iceServers=[]) + + pc = RTCPeerConnection(configuration=config) + pc.addTransceiver("video", direction="recvonly") + + frame_received = asyncio.Event() + + @pc.on("connectionstatechange") + async def on_state(): + if pc.connectionState in ("failed", "disconnected", "closed"): + self.log.warning("WebRTC state: %s", pc.connectionState) + await pc.close() + + @pc.on("track") + async def on_track(track: VideoStreamTrack): + if track.kind != "video": + return + + self.log.info("WHEP: receiving video track") + frame_received.set() + + processing_task = asyncio.create_task(self._processing_loop()) + + try: + while not self._stop_flag.is_set(): + frame = cast(VideoFrame, await track.recv()) + + # NEVER block here + if self._frame_queue.full(): + try: + self._frame_queue.get_nowait() + except asyncio.QueueEmpty: + pass + + await self._frame_queue.put(frame) + + except Exception as e: + self.log.warning("WebRTC track ended: %s", e) + processing_task.cancel() + + # --- offer/answer WHEP --- + offer = await pc.createOffer() + await pc.setLocalDescription(offer) + + await self._wait_ice_complete(pc, timeout=3.0) + + async with aiohttp.ClientSession() as session: + async with session.post( + url, + data=pc.localDescription.sdp, + headers={"Content-Type": "application/sdp"}, + timeout=aiohttp.ClientTimeout(total=5), + ) as resp: + if resp.status != 201: + raise RuntimeError(f"WHEP failed: HTTP {resp.status}") + answer_sdp = await resp.text() + + await pc.setRemoteDescription( + RTCSessionDescription(sdp=answer_sdp, type="answer") + ) + + # wait until we have a video track (otherwise useless) + try: + await asyncio.wait_for(frame_received.wait(), timeout=5) + except asyncio.TimeoutError: + await pc.close() + raise RuntimeError("WHEP: no video track received") + + # stay alive as long as not stopped + while not self._stop_flag.is_set() and pc.connectionState != "closed": + await asyncio.sleep(0.1) + + await pc.close() + + # ---------------------------- + # Program interface + # ---------------------------- + def stop(self): + """Stops the WHEP client (does not stop MediaMTX).""" + self._stop_flag.set() + + # ---------- + # Public API + # ---------- + def get_last_image(self) -> np.ndarray: + with self._lock: + return self.last_frame.copy() + + def camera_matrix(self, vector_size=128, image=None) -> np.ndarray: + """ + Create a matrix of -1, 0, and 1 for a line in the image. The matrix size is 128. + """ + if image is None: + image = self.get_last_image() + height, width, _ = image.shape + if vector_size > width: + raise ValueError("Vector size cannot be greater than image width") + + # Slice the middle 5% of the image height + sliced_image = image[ + height // 2 - height // 40 + Y_OFFSET : height // 2 + + height // 40 + + Y_OFFSET, + :, + :, + ] + + # Ensure the width of the sliced image is divisible by vector_size + adjusted_width = (width // vector_size) * vector_size + sliced_image = sliced_image[:, :adjusted_width, :] + + # Initialize the output matrix + output_matrix = np.zeros(vector_size, dtype=int) + bucket_size = adjusted_width // vector_size + + # Calculate red and green intensities for all segments at once + reshaped_red = sliced_image[:, :, 0].reshape( + sliced_image.shape[0], vector_size, bucket_size + ) + reshaped_green = sliced_image[:, :, 1].reshape( + sliced_image.shape[0], vector_size, bucket_size + ) + red_intensities = np.mean(reshaped_red, axis=(0, 2)) + green_intensities = np.mean(reshaped_green, axis=(0, 2)) + + # Determine the color for each segment + output_matrix[red_intensities > green_intensities + COLOR_THRESHOLD] = ( + COLOUR_KEY["red"] + ) + output_matrix[green_intensities > red_intensities + COLOR_THRESHOLD] = ( + COLOUR_KEY["green"] + ) + output_matrix[ + np.abs(red_intensities - green_intensities) <= COLOR_THRESHOLD + ] = COLOUR_KEY["none"] + + # Recreate the image from the matrix + if self.log.isEnabledFor(logging.DEBUG): + path = os.path.join( + DEBUG_DIR, f"debug_combined_image{self.debug_counter}.jpg" + ) + self.recreate_image_from_matrix( + sliced_image, output_matrix, adjusted_width, vector_size + ).save(path) + + return output_matrix + + def recreate_image_from_matrix( + self, + image: np.ndarray, + matrix: np.ndarray, + adjusted_width: int, + vector_size: int = 128, + ) -> Image.Image: + """ + Recreate an image from the matrix of -1, 0, and 1 and append it to the bottom of the sliced image. + """ + + # Create a blank image (20 pixels high) + recreated_image = np.zeros((20, vector_size, 3), dtype=np.uint8) + recreated_image[:, matrix == COLOUR_KEY["red"], :] = [255, 0, 0] # Red + recreated_image[:, matrix == COLOUR_KEY["green"], :] = [0, 255, 0] # Green + recreated_image[:, matrix == COLOUR_KEY["none"], :] = [128, 128, 128] # Gray + + # Resize the recreated image to match the width of the sliced image + scale_factor = adjusted_width // vector_size + recreated_image_resized = np.repeat(recreated_image, scale_factor, axis=1) + + # Adjust the width of the recreated image to match the sliced image + if recreated_image_resized.shape[1] > adjusted_width: + recreated_image_resized = recreated_image_resized[:, :adjusted_width, :] + elif recreated_image_resized.shape[1] < adjusted_width: + padding = adjusted_width - recreated_image_resized.shape[1] + recreated_image_resized = np.pad( + recreated_image_resized, + ((0, 0), (0, padding), (0, 0)), + mode="constant", + constant_values=0, + ) + recreated_image_resized[:, -padding:, 2] = 255 # Blue channel for padding + + # Append the recreated image to the bottom of the sliced image + combined_image = np.vstack((image, recreated_image_resized)) + self.debug_counter += 1 + return Image.fromarray(combined_image).convert("RGB") + + def is_green_or_red(self, lidar): + """ + Check if the car is facing a green or red wall by analyzing the bottom half of the image. + """ + image = self.get_last_image() + height, _, _ = image.shape + bottom_half = image[height // 2 :, :, :] # Slice the bottom half of the image + lidar = np.max( + sp.ndimage.zoom( + lidar[595:855], image.shape[1] / len(lidar[595:855]), mode="nearest" + )[None, :], + 0, + ) # Resize lidar data to match the image size + print((lidar < 0.5).sum()) + print(f"min lidar: {lidar.min()}, max lidar: {lidar.max()}") + red_intensity = np.mean( + bottom_half[:, :, 0] * (lidar < 0.5) + ) # Red channel in RGB + green_intensity = np.mean( + bottom_half[:, :, 1] * (lidar < 0.5) + ) # Green channel in RGB + + if green_intensity > red_intensity + COLOR_THRESHOLD: + return COLOUR_KEY["green"] + elif red_intensity > green_intensity + COLOR_THRESHOLD: + return COLOUR_KEY["red"] + return COLOUR_KEY["none"] + + def is_running_in_reversed( + self, image: Optional[np.ndarray] = None, LEFT_IS_GREEN: bool = True + ): + """ + Check if the car is running in reverse. + If the car is in reverse, green will be on the right side of the image and red on the left. + """ + if image is None: + image = self.get_last_image() + matrix = self.camera_matrix(image=image) + if COLOUR_KEY["green"] not in matrix or COLOUR_KEY["red"] not in matrix: + # If there are no green or no red pixels, return False + return False + green_indices = (matrix == COLOUR_KEY["green"]) * np.arange(1, len(matrix) + 1) + average_green_index = np.mean( + green_indices[green_indices > 0] + ) # Average index of green + + red_indices = (matrix == COLOUR_KEY["red"]) * np.arange(1, len(matrix) + 1) + average_red_index = np.mean( + red_indices[red_indices > 0] + ) # Average index of redcolor is red + + if LEFT_IS_GREEN and average_red_index > average_green_index: + if self.log.isEnabledFor(logging.DEBUG): + self.log.debug( + f"green: {average_green_index}, red: {average_red_index}" + ) + vector_size = 128 + self.debug_counter += 1 + height, width, _ = image.shape + sliced_image = image[ + height // 2 - height // 40 + Y_OFFSET : height // 2 + + height // 40 + + Y_OFFSET, + :, + :, + ] + + # Ensure the width of the sliced image is divisible by vector_size + adjusted_width = (width // vector_size) * vector_size + sliced_image = sliced_image[:, :adjusted_width, :] + debug_slice_image = self.recreate_image_from_matrix( + sliced_image, matrix, adjusted_width, vector_size + ) + + debug_slice_image.save( + os.path.join( + DEBUG_DIR_wayfinding, + f"wrong_direction_{self.debug_counter}_slice.jpg", + ) + ) + Image.fromarray(image).convert("RGB").save( + os.path.join( + DEBUG_DIR_wayfinding, f"wrong_direction{self.debug_counter}.jpg" + ) + ) + return True + elif not LEFT_IS_GREEN and average_green_index > average_red_index: + return True + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + + cam = Camera("http://192.168.1.10:8889/cam/whep") + + print("Got frame. camera_matrix:", cam.camera_matrix()[:10]) + time.sleep(5) + print("seconde frame", cam.camera_matrix()[:10]) + print("Ctrl+C to exit") + try: + while True: + print(cam.camera_matrix()[:10]) + time.sleep(0.1) + except KeyboardInterrupt: + cam.stop() diff --git a/src/HL/actionneur_capteur/Lidar.py b/src/high_level/src/actionneur_capteur/lidar.py similarity index 54% rename from src/HL/actionneur_capteur/Lidar.py rename to src/high_level/src/actionneur_capteur/lidar.py index 42195572..d334f65a 100644 --- a/src/HL/actionneur_capteur/Lidar.py +++ b/src/high_level/src/actionneur_capteur/lidar.py @@ -21,51 +21,52 @@ # SOFTWARE. - -import socket -import os import _thread as thread -import numpy as np -import matplotlib.pyplot as plt import logging +import os +import socket + +import matplotlib.pyplot as plt +import numpy as np + -class Lidar(): - measureMsgHeads = {'ME', 'GE', 'MD', 'GD'} - - def deg2theta(self, deg): - return deg / 360 * 2 * np.pi +class Lidar: + measure_msg_heads = {"ME", "GE", "MD", "GD"} - def makeSocket(self, ip, port): + def deg_to_theta(self, deg): + return deg / 360 * 2 * np.pi + + def make_socket(self, ip, port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port)) sock.settimeout(5) return sock # decode 3 byte integer data line - def decodeDistance(self, data): - + def decode_distance(self, data): + def partition(n: int, lst): for i in range(0, len(lst), n): - yield lst[i:i + n] + yield lst[i : i + n] # remove checksum bytes for every 65 bytes of data - parts = [''.join(part[:-1]) for part in list(partition(65, data))] - # repack data - data = ''.join(parts) + parts = ["".join(part[:-1]) for part in list(partition(65, data))] + # repack data + data = "".join(parts) # transform 3-byte int ns = [ord(d) - 0x30 for d in data] - ns = [f'{d:06b}' for d in ns] + ns = [f"{d:06b}" for d in ns] ns = list(partition(3, ns)) - ns = [int(''.join(c3), 2) for c3 in ns] + ns = [int("".join(c3), 2) for c3 in ns] # set out-of-range value to zero ns = [0 if n == 65533 else n for n in ns] - self.rDistance = np.array(ns) - self.mStep = self.startStep + self.r_distance = np.array(ns) + self.m_step = self.start_step self.measuring = False return ns - def __init__(self, ip, port, startStep=0): + def __init__(self, ip, port, start_step=0): self.log = logging.getLogger(__name__) self.ip = ip self.port = port @@ -74,54 +75,52 @@ def __init__(self, ip, port, startStep=0): self.skip = 0 self.head = None # Starting step id - self.startStep = startStep + self.start_step = start_step # Current step id, for decoding polar distance data - self.mStep = startStep + self.m_step = start_step # Array of distance - self.rDistance = np.zeros(1081-startStep, dtype=int) + self.r_distance = np.zeros(1081 - start_step, dtype=int) # Buffer to receive packets self.buf = "" - self.expectedPacketSize = 65*50 + 44 # TODO hardcoded for full range measurement - - ids = np.arange(1081-startStep) - self.xTheta = self.deg2theta((ids + startStep) * 270.0 / 1080 + 45 - 90) + self.expected_packet_size = ( + 65 * 50 + 44 + ) # TODO hardcoded for full range measurement - self.sock = self.makeSocket(ip, port) - self.__startReader__() + ids = np.arange(1081 - start_step) + self.x_theta = self.deg_to_theta((ids + start_step) * 270.0 / 1080 + 45 - 90) + self.sock = self.make_socket(ip, port) + self.__start_reader__() def send(self, cmd: str): self.sock.sendall(cmd.encode()) + def start_plotter(self, autorange=False): - def startPlotter(self, autorange=False): - - - - def toCartesian(xTheta, xR): - X = np.cos(xTheta) * xR - Y = np.sin(xTheta) * xR - return X,Y + def to_cartesian(x_theta, x_r): + x = np.cos(x_theta) * x_r + y = np.sin(x_theta) * x_r + return x, y plt.show() - fig = plt.figure() + plt.figure() axc = plt.subplot(121) - axp = plt.subplot(122, projection='polar') + axp = plt.subplot(122, projection="polar") # axp.set_thetamax(deg2theta(45)) # axp.set_thetamax(deg2theta(270 + 45)) axp.grid(True) - self.log.info('Plotter started, press any key to exit') + self.log.info("Plotter started, press any key to exit") - self.log.debug(f'{self.xTheta}, {self.rDistance}') + self.log.debug(f"{self.x_theta}, {self.r_distance}") while True: - X, Y = toCartesian(self.xTheta, self.rDistance) + x, y = to_cartesian(self.x_theta, self.r_distance) axp.clear() axc.clear() - axp.plot(self.xTheta, self.rDistance) + axp.plot(self.x_theta, self.r_distance) - axc.plot(X, Y) + axc.plot(x, y) if not autorange: axp.set_rmax(8000) @@ -133,50 +132,46 @@ def toCartesian(xTheta, xR): if plt.waitforbuttonpress(timeout=0.02): os._exit(0) - - # Change hokuyo IP address, requires reboot - def changeIP(self, ip: str, gateway: str, netmask='255.255.255.0'): - def formatZeros(addr): - return ''.join([n.rjust(3, '0') for n in addr.split('.')]) - - ip = formatZeros(ip) - gateway = formatZeros(gateway) - netmask = formatZeros(netmask) - cmd = f'$IP{ip}{netmask}{gateway}\r\n' - self.log.debug(f'ChangeIP cmd: {cmd}') + def change_ip(self, ip: str, gateway: str, netmask="255.255.255.0"): + def format_zeros(addr): + return "".join([n.rjust(3, "0") for n in addr.split(".")]) + + ip = format_zeros(ip) + gateway = format_zeros(gateway) + netmask = format_zeros(netmask) + cmd = f"$IP{ip}{netmask}{gateway}\r\n" + self.log.debug(f"ChangeIP cmd: {cmd}") self.send(cmd) # Start continous read mode - def startContinuous(self, start: int, end: int, withIntensity=False): - head = 'ME' if withIntensity else 'MD' - cmd = f'{head}{start:04d}{end:04d}00000\r\n' + def start_continuous(self, start: int, end: int, with_intensity=False): + head = "ME" if with_intensity else "MD" + cmd = f"{head}{start:04d}{end:04d}00000\r\n" self.log.debug(cmd) self.head = cmd.strip() self.send(cmd) - # Start single read - def singleRead(self, start: int, end: int, withIntensity=False): - head = 'GE' if withIntensity else 'GD' - cmd = f'{head}{start:04d}{end:04d}01000\r\n' - self.send( cmd) + def single_read(self, start: int, end: int, with_intensity=False): + head = "GE" if with_intensity else "GD" + cmd = f"{head}{start:04d}{end:04d}01000\r\n" + self.send(cmd) def stop(self): - cmd = 'QT\r\n' - self.send( cmd) + cmd = "QT\r\n" + self.send(cmd) def reboot(self): - cmd = 'RB\r\n' + cmd = "RB\r\n" self.send(cmd) self.send(cmd) - - def handleMsgLine(self, line): + def handle_msg_line(self, line): if line == self.head: - self.measuring = True + self.measuring = True self.skip = 0 - self.mStep = self.startStep + self.m_step = self.start_step return True if self.measuring: @@ -186,31 +181,31 @@ def handleMsgLine(self, line): else: self.buf += line.strip() # self.log.debug(f'buf size {len(self.buf)}') - if len(self.buf) >= self.expectedPacketSize: - self.decodeDistance(self.buf) - self.buf = '' + if len(self.buf) >= self.expected_packet_size: + self.decode_distance(self.buf) + self.buf = "" return True return False - def __startReader__(self): - def handleMeasuring(msg): + def __start_reader__(self): + def handle_measuring(msg): lines = msg.split() for line in lines: - if not self.handleMsgLine(line): - self.log.debug(f'ignore {line}') + if not self.handle_msg_line(line): + self.log.debug(f"ignore {line}") def loop(): try: while True: try: - m, _ =self.sock.recvfrom(1024) + m, _ = self.sock.recvfrom(1024) msg = m.decode() - handleMeasuring(msg) - except socket.timeout as e: - self.log.error('Read timeout, sensor disconnected?') + handle_measuring(msg) + except socket.timeout: + self.log.error("Read timeout, sensor disconnected?") os._exit(1) finally: self.sock.close() - thread.start_new_thread(loop, ()) \ No newline at end of file + thread.start_new_thread(loop, ()) diff --git a/src/high_level/src/actionneur_capteur/master_i2c.py b/src/high_level/src/actionneur_capteur/master_i2c.py new file mode 100644 index 00000000..c5464c98 --- /dev/null +++ b/src/high_level/src/actionneur_capteur/master_i2c.py @@ -0,0 +1,74 @@ +import logging +import struct +import threading +import time + +import smbus + +from high_level.autotech_constant import ( + I2C_NUMBER_DATA_RECEIVED, + I2C_SLEEP_ERROR_LOOP, + I2C_SLEEP_RECEIVED, + SLAVE_ADDRESS, +) + + +class I2CArduino: + def __init__(self, serveur): + self.log = logging.getLogger(__name__) + self.serveur = serveur + self.current_speed = 0 + self.send_running = True + self.receive_running = True + + # battery info + self.voltage_lipo = 0 + self.voltage_nimh = 0 + + # initialization of i2c bus + self.bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1 + self.log.info("I2C: bus opened on /dev/i2c-1") + + # initialization of i2c send and received + threading.Thread(target=self.start_send, daemon=True).start() + threading.Thread(target=self.start_received, daemon=True).start() + + def start_send(self): + """send speed and direction to the microcontroller regularly.""" + time.sleep(1) # Give some time for the target_speed and direction to be set + self.log.info("Thread I2C loop started") + while self.send_running: + try: + data = struct.pack( + "= length: + float_values = struct.unpack( + "f" * I2C_NUMBER_DATA_RECEIVED, bytes(data[:length]) + ) + list_values = list(float_values) + + # we record the values + self.voltage_lipo = list_values[0] + self.voltage_nimh = list_values[1] + self.current_speed = list_values[2] + else: + self.log.warning( + "I2C: unexpected size (%d but %d excepted)", len(data), length + ) + time.sleep(I2C_SLEEP_RECEIVED) diff --git a/src/high_level/src/actionneur_capteur/tof.py b/src/high_level/src/actionneur_capteur/tof.py new file mode 100644 index 00000000..845a5a29 --- /dev/null +++ b/src/high_level/src/actionneur_capteur/tof.py @@ -0,0 +1,40 @@ +import board +import busio +from adafruit_vl53l1x import VL53L1X +import logging +import threading +import time + +class ToF: + """ + Class representing a Time of Flight (ToF) sensor. + """ + + def __init__(self): + self.log = logging.getLogger(__name__) + i2c = busio.I2C(board.SCL, board.SDA) + self.vl53 = VL53L1X(i2c) + self.distance = 0 + threading.Thread(target=self.get_tof_distance, daemon=True).start() + + def get_tof_distance(self): + """ + Get the distance from the rear ToF sensor. + """ + self.vl53.stop_ranging() # Ensure sensor is stopped before starting + time.sleep(0.1) # Short delay to ensure sensor is ready + self.vl53.start_ranging() + while True: + try: + if self.vl53.data_ready: + distance = self.vl53.distance + if distance is None: + self.distance = 0 # en cm + else: + self.distance = distance # en cm + self.vl53.clear_interrupt() + time.sleep(0.05) + except Exception as e: + self.log.error(f"Error reading rear ToF sensor: {e}") + return None + diff --git a/src/high_level/src/high_level/__init__.py b/src/high_level/src/high_level/__init__.py new file mode 100644 index 00000000..95dce6d6 --- /dev/null +++ b/src/high_level/src/high_level/__init__.py @@ -0,0 +1,4 @@ +from .serveur import Serveur +from .backend import BackendAPI + +__all__ = ["Serveur", "BackendAPI"] \ No newline at end of file diff --git a/src/HL/Autotech_constant.py b/src/high_level/src/high_level/autotech_constant.py similarity index 76% rename from src/HL/Autotech_constant.py rename to src/high_level/src/high_level/autotech_constant.py index 608badfc..dffa4061 100644 --- a/src/HL/Autotech_constant.py +++ b/src/high_level/src/high_level/autotech_constant.py @@ -3,8 +3,8 @@ import logging # Car control -MAX_IA_SPEED = 2 # maximum speed for ia -MIN_IA_SPEED = -2 # minimum speed for ia +MAX_IA_SPEED = 1000 # maximum speed for ia in centimeter per second +MIN_IA_SPEED = -500 # minimum speed for ia in centimeter per second MAX_CONTROL_SPEED = 2 # maximum speed for controling devices MIN_CONTROL_SPEED = -2 # minimum speed for controlig devices MAX_ANGLE = 18 # angle between the two extrem position @@ -20,8 +20,8 @@ PORT_REMOTE_CONTROL = 5556 # Port to send data for remote control on :PORT_REMOTE_CONTROL #Camera -PORT_STREAMING_CAMERA = 8000 # adresse where to see the stream of the camera if activate is :PORT_STREAMIN_CAMERA/STREAM_PATH.jpeg -STREAM_PATH = "stream" +PORT_STREAMING_CAMERA = 8889 # adresse where to see the stream of the camera if activate is :PORT_STREAMIN_CAMERA/STREAM_PATH/cam +STREAM_PATH = "map" SIZE_CAMERA_X = 1280 SIZE_CAMERA_Y = 720 FRAME_RATE = 30 # frame rate of the camera @@ -44,7 +44,7 @@ script_dir = os.path.dirname(os.path.abspath(__file__)) -MODEL_PATH = os.path.join(script_dir, "model_CNN1D.onnx") # Allows the model to be loaded from the same directory as the script regardless of the current working directory (aka where the script is run from) +MODEL_PATH = "/home/intech/CoVAPSy/src/high_level/models/" # Allows the model to be loaded from the same directory as the script regardless of the current working directory (aka where the script is run from) SOCKET_ADRESS = { @@ -53,7 +53,7 @@ } ANGLE_LOOKUP = np.linspace(-MAX_ANGLE, MAX_ANGLE, 16) -SPEED_LOOKUP = np.linspace(MIN_IA_SPEED, MAX_IA_SPEED, 16) +SPEED_LOOKUP = np.linspace(0, MAX_IA_SPEED, 16) Temperature = 0.7 # Temperature parameter for softmax function, used to control the sharpness of the distribution resols around 1 # the higher the temperature the more unprobalbe actions become probable, the lower the temperature the more probable actions become probable. @@ -65,4 +65,7 @@ #Startup CAMERA_STREAM_ON_START = True #If True the camera stream will start at the launch of the car BACKEND_ON_START = True #If True the backend will start at the launch of the car -LIDAR_STREAM_ON_START = True #If True the lidar stream will start at the launch of the car \ No newline at end of file +LIDAR_STREAM_ON_START = True #If True the lidar stream will start at the launch of the car + +#Backend +SITE_DIR_BACKEND = "/home/intech/CoVAPSy/src/high_level/src/site_controle" # the directory where the backend will look for the site to serve \ No newline at end of file diff --git a/src/HL/backend.py b/src/high_level/src/high_level/backend.py similarity index 57% rename from src/HL/backend.py rename to src/high_level/src/high_level/backend.py index 168bf1d0..b2ddf7cf 100644 --- a/src/HL/backend.py +++ b/src/high_level/src/high_level/backend.py @@ -1,32 +1,31 @@ -# BackendAPI.py - +import asyncio +import base64 +import logging +import os import threading import time -import logging -from typing import Any, Dict, Optional, List +from typing import Any, Dict, List, Optional +import numpy as np import uvicorn -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, Request, WebSocket from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles -import numpy as np -import asyncio -from fastapi import WebSocket from fastapi.websockets import WebSocketDisconnect -from src.HL.programme.programme import Program -from Autotech_constant import ( - LOGGING_LEVEL, - PORT_STREAMING_CAMERA, - STREAM_PATH, +from high_level.autotech_constant import ( BACKEND_ON_START, + MODEL_PATH, + PORT_STREAMING_CAMERA, ) +from programs.program import Program + class BackendAPI(Program): """ - Programme backend web de contrôle. - - Respecte Program: start/kill/running/controls_car + backend web for control and debug. + - Respect Program: start/kill/running/controls_car - Expose une API REST: GET /api/status GET /api/programs @@ -34,15 +33,17 @@ class BackendAPI(Program): POST /api/programs/{id}/start POST /api/programs/{id}/kill GET /api/stream/camera - - Sert un frontend statique (optionnel) via / (index.html) + - Give a static frontend by index.html """ def __init__( self, - server: Any, + server, host: str = "0.0.0.0", port: int = 8001, - site_dir: Optional[str] = None, # ex: "/home/intech/CoVAPSy/src/HL/site_controle" + site_dir: Optional[ + str + ] = None, # ex: "/home/intech/CoVAPSy/src/HL/site_controle" cors_allow_origins: Optional[List[str]] = None, ): super().__init__() @@ -51,8 +52,7 @@ def __init__( self.server = server self.controls_car = False self.running = False - self.lidar_yaw = 0 # rotation fixe pour que "devant" = haut écran - + self.lidar_yaw = -np.pi / 2 # for lidar coordinate correction self.host = host self.port = port @@ -61,7 +61,7 @@ def __init__( self.app = FastAPI(title="CoVAPSy Remote Control API", version="1.0.0") - # CORS: pratique si ton frontend est sur un autre port / autre machine + # CORS if cors_allow_origins is None: cors_allow_origins = ["*"] self.app.add_middleware( @@ -72,15 +72,12 @@ def __init__( allow_headers=["*"], ) - # Frontend statique (optionnel) - # - si site_dir contient un index.html, il sera servi à "/" if site_dir: self.app.mount("/static", StaticFiles(directory=site_dir), name="static") @self.app.get("/", response_class=HTMLResponse) def index(): - # Le frontend peut être /static/index.html ou /static/controle.html selon ton choix - # Ici on renvoie juste une page qui redirige vers /static/ + # the frontend is served at /static/index.html return """ CoVAPSy @@ -94,65 +91,67 @@ def index(): self._setup_routes() - time.sleep(1) # Petit délai pour s'assurer que tout est prêt avant de démarrer + time.sleep(1) # litle delay to ensure server is ready if BACKEND_ON_START: self.start() - - # ---------------------------- - # Helpers: lecture état voiture + # Helpers: reading data from server # ---------------------------- def _arduino(self): - # server.arduino_I2C est une property dans ton Serveur + # server.arduino_I2C is a property returning the I2C Arduino instance return getattr(self.server, "arduino_I2C", None) def _get_telemetry(self) -> Dict[str, Any]: ard = self._arduino() - # On protège tout: si pas initialisé, on renvoie 0 voltage_lipo = getattr(ard, "voltage_lipo", 0.0) if ard else 0.0 voltage_nimh = getattr(ard, "voltage_nimh", 0.0) if ard else 0.0 current_speed = getattr(ard, "current_speed", 0.0) if ard else 0.0 - # Programme qui contrôle la voiture actuellement - last_ctrl = int(getattr(self.server, "last_programme_control", 0)) - programmes = getattr(self.server, "programme", []) + # Programm which currently controls the car + last_ctrl = int(getattr(self.server, "last_program_control", 0)) + programs = getattr(self.server, "programs", []) prog_name = None - if isinstance(programmes, list) and 0 <= last_ctrl < len(programmes): - prog_name = type(programmes[last_ctrl]).__name__ + if isinstance(programs, list) and 0 <= last_ctrl < len(programs): + prog_name = type(programs[last_ctrl]).__name__ target_speed = float(getattr(self.server, "target_speed", 0.0)) direction = float(getattr(self.server, "direction", 0.0)) return { - "battery": { - "lipo": voltage_lipo, - "nimh": voltage_nimh - }, + "battery": {"lipo": voltage_lipo, "nimh": voltage_nimh}, "car": { - "vitesse_reelle": current_speed, - "vitesse_demandee": target_speed, - "direction_demandee": direction, - "programme_controle": prog_name, - "programme_id": last_ctrl + "current_speed": current_speed, + "target_speed": target_speed, + "direction": direction, + "car_control": prog_name, + "program_id": last_ctrl, + "tof": self.server.tof.distance, }, "timestamp": time.time(), } + def _fetch_name_models(self) -> list[str]: + models = os.listdir(MODEL_PATH) + models = [model for model in models if model.endswith(".onnx")] + return models + def _list_programs(self) -> List[Dict[str, Any]]: - programmes = getattr(self.server, "programme", []) + programs = getattr(self.server, "programs", []) out: List[Dict[str, Any]] = [] - if not isinstance(programmes, list): + if not isinstance(programs, list): return out - for i, p in enumerate(programmes): + for i, p in enumerate(programs): out.append( { "id": i, "name": type(p).__name__, "running": bool(getattr(p, "running", False)), "controls_car": bool(getattr(p, "controls_car", False)), - "display": p.display() if hasattr(p, "display") else type(p).__name__, + "display": p.display() + if hasattr(p, "display") + else type(p).__name__, } ) return out @@ -160,78 +159,107 @@ def _list_programs(self) -> List[Dict[str, Any]]: def _camera_stream_url(self) -> str: ip = getattr(getattr(self.server, "SOCKET_ADRESS", None), "IP", None) ip = getattr(self.server, "ip", None) or "192.168.1.10" - return f"http://{ip}:{PORT_STREAMING_CAMERA}/stream.mjpg" - + return f"http://{ip}:{PORT_STREAMING_CAMERA}/cam/" + def _lidar(self): return getattr(self.server, "lidar", None) - def _get_lidar_points_cartesian(self): + def _get_lidar_ranges(self) -> dict[str, Any] | None: lidar = self._lidar() - if not lidar or lidar.rDistance is None: + if not lidar or lidar.r_distance is None: return None - r = np.asarray(lidar.rDistance) - n = r.shape[0] - - # angles du lidar (repère capteur) - theta_lidar = np.linspace( - -3*np.pi/4, # -135° - +3*np.pi/4, # +135° - n, - endpoint=True - ) - - # correction d’orientation - theta_world = theta_lidar + self.lidar_yaw - - # projection - # Repère monde: +Y = devant le lidar - x = -np.sin(theta_world) * r - y = np.cos(theta_world) * r + r = np.asarray(lidar.r_distance) + # int16 suffit. + # Sinon passe en int32 . + r_i16 = r.astype(np.int16, copy=False) return { - "x": x.tolist(), - "y": y.tolist(), + "r": base64.b64encode(r_i16.tobytes()).decode("ascii"), + "dtype": "int16", "unit": "mm", + "yaw": float(self.lidar_yaw), + "tof": float(self.server.tof.distance), "timestamp": time.time(), + "n": int(r_i16.shape[0]), } - + def _get_car_border(self) -> str | None: + try: + data = np.load( + "/home/intech/CoVAPSy/src/high_level/src/programs/data/min_lidar.npy" + ) + self.logger.warning("car_border loaded: %s", data.shape) + data32 = data.astype(np.float32) + return base64.b64encode(data32.tobytes()).decode("ascii") + except FileNotFoundError: + self.logger.error("Car border not found") + return None # ---------------------------- # Routes # ---------------------------- def _setup_routes(self) -> None: @self.app.get("/api/status") - def status(): + def status() -> dict[str, Any]: return { - "backend": {"running": self.running, "host": self.host, "port": self.port}, + "backend": { + "running": self.running, + "host": self.host, + "port": self.port, + }, "telemetry": self._get_telemetry(), "programs": self._list_programs(), + "models": self._fetch_name_models(), } + @self.app.post("/api/ai/start") + async def start_ai_with_model(req: Request) -> dict[str, Any]: + body = await req.json() + model = body.get("model") + + if not model: + raise HTTPException(status_code=400, detail="Model name required") + + programs = getattr(self.server, "programs", []) + + ai_prog = next( + (p for p in programs if type(p).__name__ == "AIProgram"), None + ) + + if ai_prog is None: + raise HTTPException(status_code=404, detail="AI program not found") + + ai_prog.start(model_give=model) + + return {"status": "ok", "model": model} + @self.app.get("/api/programs") def programs(): return self._list_programs() @self.app.post("/api/programs/{prog_id}/toggle") def toggle_program(prog_id: int): - programmes = getattr(self.server, "programme", []) - if not isinstance(programmes, list) or not (0 <= prog_id < len(programmes)): + programs = getattr(self.server, "programs", []) + if not isinstance(programs, list) or not (0 <= prog_id < len(programs)): raise HTTPException(status_code=404, detail="Unknown program id") self.server.start_process(prog_id) - # après action, renvoyer état mis à jour - return {"status": "ok", "program_id": prog_id, "programs": self._list_programs()} + # after action, return updated state + return { + "status": "ok", + "program_id": prog_id, + "programs": self._list_programs(), + } @self.app.post("/api/programs/{prog_id}/start") def start_program(prog_id: int): - programmes = getattr(self.server, "programme", []) - if not isinstance(programmes, list) or not (0 <= prog_id < len(programmes)): + programs = getattr(self.server, "programs", []) + if not isinstance(programs, list) or not (0 <= prog_id < len(programs)): raise HTTPException(status_code=404, detail="Unknown program id") - p = programmes[prog_id] + p = programs[prog_id] if getattr(p, "running", False): return {"status": "already_running", "program_id": prog_id} @@ -240,11 +268,11 @@ def start_program(prog_id: int): @self.app.post("/api/programs/{prog_id}/kill") def kill_program(prog_id: int): - programmes = getattr(self.server, "programme", []) - if not isinstance(programmes, list) or not (0 <= prog_id < len(programmes)): + programs = getattr(self.server, "programs", []) + if not isinstance(programs, list) or not (0 <= prog_id < len(programs)): raise HTTPException(status_code=404, detail="Unknown program id") - p = programmes[prog_id] + p = programs[prog_id] if not getattr(p, "running", False): return {"status": "already_stopped", "program_id": prog_id} @@ -256,14 +284,35 @@ def kill_program(prog_id: int): def camera_stream(): # give the URL. return {"url": self._camera_stream_url()} - + @self.app.get("/api/lidar") def lidar_snapshot(): - data = self._get_lidar_points_cartesian() + data = self._get_lidar_ranges() if data is None: raise HTTPException(status_code=503, detail="Lidar not available") return data - + + @self.app.get("/api/lidar_init") + def lidar_init(): + lidar = self._lidar() + if not lidar: + raise HTTPException(status_code=503, detail="Lidar not available") + + xTheta = getattr(lidar, "x_theta", None) + if xTheta is None: + raise HTTPException( + status_code=503, detail="Lidar not ready (xTheta missing)" + ) + + xTheta = np.asarray(xTheta, dtype=np.float32) + return { + "xTheta": base64.b64encode(xTheta.tobytes()).decode("ascii"), + "car_border": self._get_car_border(), + "dtype": "float32", + "unit": "radian", + "n": int(xTheta.shape[0]), + } + @self.app.websocket("/api/lidar/ws") async def lidar_ws(ws: WebSocket): await ws.accept() @@ -271,12 +320,13 @@ async def lidar_ws(ws: WebSocket): try: while True: - data = self._get_lidar_points_cartesian() + data = self._get_lidar_ranges() if data: await ws.send_json(data) await asyncio.sleep(0.1) except WebSocketDisconnect: self.logger.info("Lidar WS client disconnected") + @self.app.websocket("/api/telemetry/ws") async def telemetry_ws(ws: WebSocket): await ws.accept() @@ -290,7 +340,6 @@ async def telemetry_ws(ws: WebSocket): except WebSocketDisconnect: self.logger.info("Telemetry WS client disconnected") - # ---------------------------- # Program interface # ---------------------------- @@ -300,12 +349,15 @@ def start(self): self.running = True # Uvicorn - config = uvicorn.Config(self.app, host=self.host, port=self.port, log_level="info") + config = uvicorn.Config( + self.app, host=self.host, port=self.port, log_level="info" + ) self._uvicorn_server = uvicorn.Server(config) def _run(): try: self.logger.info("BackendAPI starting on %s:%d", self.host, self.port) + assert self._uvicorn_server is not None self._uvicorn_server.run() except Exception as e: self.logger.error("BackendAPI crashed: %s", e, exc_info=True) diff --git a/src/high_level/src/high_level/serveur.py b/src/high_level/src/high_level/serveur.py new file mode 100644 index 00000000..c0351ce4 --- /dev/null +++ b/src/high_level/src/high_level/serveur.py @@ -0,0 +1,243 @@ +from actionneur_capteur.master_i2c import I2CArduino +from actionneur_capteur.tof import ToF + + +from actionneur_capteur.lidar import Lidar + + +from actionneur_capteur.camera import Camera + + +import logging +import textwrap +import time + +import smbus +from gpiozero import LED, Button, Buzzer +from luma.core.interface.serial import i2c +from luma.core.render import canvas +from luma.oled.device import ssd1306 +from PIL import Image, ImageDraw, ImageFont + +from high_level.autotech_constant import SITE_DIR_BACKEND, TEXT_HEIGHT +from programs.car import AIProgram +from programs.initialisation import Initialisation +from programs.poweroff import Poweroff +from programs.ps4_controller_program import PS4ControllerProgram +from programs.remote_control import RemoteControl +from programs.ssh_programme import SshProgramme +from programs.utils.ssh import check_ssh_connections + +from .backend import BackendAPI + + +class Serveur: + def __init__(self): + self.log = logging.getLogger(__name__) + # initialization of different modules + self.log.info("Server initialization") + + # initialization of GPIO buttons, LEDs, buzzer + self.bp_next = Button("GPIO5", bounce_time=0.1) + self.bp_entre = Button("GPIO6", bounce_time=0.1) + + self.led1 = LED("GPIO17") + self.led2 = LED("GPIO27") + self.buzzer = Buzzer("GPIO26") + self.log.info("GPIO: boutons, LEDs, buzzer initialized") + + self.serial = i2c(port=1, address=0x3C) + self.device = ssd1306(self.serial) + self.bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1 + self.log.info("I2C: bus open on /dev/i2c-1") + + # initialization of time command + self.initial_time = time.time() + self.last_cmd_time = time.time() + + # data of the car + self.process_output = "" + self.last_program_control = 0 + self.process = None + self.temp = None + + self.initialisation_module = Initialisation(self) + + self.programs = [ + SshProgramme(), + self.initialisation_module, + AIProgram(self), + PS4ControllerProgram(), + RemoteControl(), + # ProgramStreamCamera(self), + BackendAPI(self, host="0.0.0.0", port=8001, site_dir=SITE_DIR_BACKEND), + Poweroff(), + ] + self.log.debug("Programs ready: %s", [type(p).__name__ for p in self.programs]) + + # screen data + self.screen = 0 + self.state = 0 + self.scroll_offset = 3 + + @property + def camera(self) -> Camera: + return self.initialisation_module.camera + + @property + def lidar(self) -> Lidar: + return self.initialisation_module.lidar + + @property + def tof(self) -> ToF: + return self.initialisation_module.tof + + @property + def arduino_I2C(self) -> I2CArduino: + return self.initialisation_module.arduino_I2C + + @property + def target_speed(self) -> float: + return self.programs[self.last_program_control].target_speed + + @property + def direction(self) -> float: + return self.programs[self.last_program_control].direction + + # ----------------------------------------------------------------------------------------------------- + # Screen display functions + # ----------------------------------------------------------------------------------------------------- + def affichage_oled(self, selected: int): # test not use + im = Image.new("1", (128, 64), "black") + draw = ImageDraw.Draw(im) + font = ImageFont.load_default() + + for num, i in enumerate( + range( + max(selected - self.scroll_offset, 0), + min(len(self.programs), selected + self.scroll_offset), + ) + ): + y = num * TEXT_HEIGHT + + if i == selected: + draw.rectangle((0, y, 127, y + TEXT_HEIGHT), fill="white") + draw.text((3, y), self.programs[i]["name"], fill="black", font=font) + else: + draw.text((3, y), self.programs[i]["name"], fill="white", font=font) + + with canvas(self.device) as display: + display.bitmap((0, 0), im, fill="white") + + def make_voltage_im(self): + """Create an image showing the battery voltages to be pasted on the main display""" + if self.arduino_I2C is not None: + received = [self.arduino_I2C.voltage_lipo, self.arduino_I2C.voltage_nimh] + else: + received = [0.0, 0.0] + + # filter out values below 6V and round to 2 decimal places + received = [round(elem, 2) if elem > 6 else 0.0 for elem in received] + text = f"LiP:{received[0]:.2f}V|NiH:{received[1]:.2f}V" + im = Image.new("1", (128, TEXT_HEIGHT), "black") + draw = ImageDraw.Draw(im) + font = ImageFont.load_default() + draw.text((3, 0), text, fill="white", font=font) + return im + + def display_combined_im(self, text: str): + """function to display text with battery voltages on the oled screen""" + im = Image.new("1", (128, 64), "black") + draw = ImageDraw.Draw(im) + font = ImageFont.load_default() + + # Wrap the text to fit within the width of the display + wrapped_text = textwrap.fill(text, width=20) # Adjust width as needed + draw.text((3, 0), wrapped_text, fill="white", font=font) + + voltage_im = self.make_voltage_im() + im.paste(voltage_im, (0, 64 - TEXT_HEIGHT)) + + with canvas(self.device) as draw: + draw.bitmap((0, 0), im, fill="white") + + def idle(self): + """ + Manages the screen display based on the current or chosen function. + Screen changes are managed by the button functions just below. + """ + if check_ssh_connections(): + self.led1.on() + + if not check_ssh_connections(): + self.led1.off() + + if self.screen < len(self.programs): + text = self.programs[self.screen].display() + self.display_combined_im(text) + + def bouton_next(self): + """go to next screen on oled display""" + self.screen += 1 + if self.screen >= len(self.programs): + self.screen = 0 + + def bouton_entre(self, num=None): + """take action on the current screen on display and start the program""" + if num is not None: + self.screen = num + self.state = self.screen + self.start_process(self.screen) + + # --------------------------------------------------------------------------------------------------- + # Processus + # --------------------------------------------------------------------------------------------------- + + def start_process(self, number_program): + """Starts the program referenced by its number: + if it is a program that controls the car, it kills the old program that was controlling, + otherwise the program is started or stopped depending on whether it was already running or stopped before""" + self.log.info( + "User action: program %d (%s)", + number_program, + type(self.programs[number_program]).__name__, + ) + if self.programs[number_program].running: + self.programs[number_program].kill() + if self.programs[number_program].controls_car: + self.last_program_control = 0 + self.log.warning( + "Car control changed: %s -> %s", + type(self.programs[number_program]).__name__, + type(self.programs[self.last_program_control]).__name__, + ) + + self.log.info( + "Program %s stopped", type(self.programs[number_program]).__name__ + ) + + elif self.programs[number_program].controls_car: + self.programs[self.last_program_control].kill() + self.programs[number_program].start() + self.log.warning( + "Car control changed: %s -> %s", + type(self.programs[self.last_program_control]).__name__, + type(self.programs[number_program]).__name__, + ) + self.last_program_control = number_program + + else: + self.programs[number_program].start() + + # --------------------------------------------------------------------------------------------------- + # car function + # --------------------------------------------------------------------------------------------------- + + def main(self): + self.bp_next.when_pressed = self.bouton_next + self.bp_entre.when_pressed = self.bouton_entre + + self.log.info("Server main loop started") + + while True: + self.idle() diff --git a/src/high_level/src/programs/__init__.py b/src/high_level/src/programs/__init__.py new file mode 100644 index 00000000..3e907182 --- /dev/null +++ b/src/high_level/src/programs/__init__.py @@ -0,0 +1,21 @@ +from .camera_serv import StreamHandler, StreamOutput, StreamServer, frame_buffer +from .car import AIProgram, Car +from .initialisation import Initialisation +from .poweroff import Poweroff +from .program import Program # in first because other programs depend on it +from .remote_control import RemoteControl +from .ssh_programme import SshProgramme + +__all__ = [ + "Program", + "Car", + "AIProgram", + "RemoteControl", + "Initialisation", + "Poweroff", + "SshProgramme", + "StreamServer", + "StreamHandler", + "StreamOutput", + "frame_buffer", +] diff --git a/src/HL/programme/Camera_serv.py b/src/high_level/src/programs/camera_serv.py similarity index 96% rename from src/HL/programme/Camera_serv.py rename to src/high_level/src/programs/camera_serv.py index 447b203d..e84801c3 100644 --- a/src/HL/programme/Camera_serv.py +++ b/src/high_level/src/programs/camera_serv.py @@ -5,7 +5,7 @@ import socketserver from threading import Condition -from src.HL.Autotech_constant import STREAM_PATH # si tu veux un path configurable +from high_level.autotech_constant import STREAM_PATH streaming_enabled = True diff --git a/src/high_level/src/programs/car.py b/src/high_level/src/programs/car.py new file mode 100644 index 00000000..932d5894 --- /dev/null +++ b/src/high_level/src/programs/car.py @@ -0,0 +1,269 @@ +import logging +import os +import time +from threading import Thread +from typing import Optional + +from actionneur_capteur import Lidar +from actionneur_capteur.camera import Camera +from actionneur_capteur.tof import ToF +import numpy as np +import onnxruntime as ort + +# Import constants from HL.Autotech_constant to share them between files and ease of use +from high_level.autotech_constant import ( + CRASH_DIST, + LIDAR_DATA_AMPLITUDE, + LIDAR_DATA_OFFSET, + LIDAR_DATA_SIGMA, + MAX_ANGLE, + MODEL_PATH, + REAR_BACKUP_DIST, +) + +from .program import Program +from .utils.driver import Driver + + +class Car: + def __init__(self, driving_strategy, serveur, model) -> None: + self.log = logging.getLogger(__name__) + self.target_speed = 0 # Speed in millimeters per second + self.direction = 0 # Steering angle in degrees + self.serveur = serveur + self.reverse_count = 0 + + # Initialize AI session + try: + self.ai_session = ort.InferenceSession(MODEL_PATH) + self.log.info("AI session initialized successfully") + except Exception as e: + self.log.error(f"Error initializing AI session: {e}") + raise + + self.driving = driving_strategy + + self.log.info("Car initialization complete") + + # dynamic access to sensors + @property + def camera(self) -> Camera: + return self.serveur.camera + + @property + def lidar(self) -> Lidar: + return self.serveur.lidar + + @property + def tof(self) -> ToF: + return self.serveur.tof + + def stop(self) -> None: + self.target_speed = 0 + self.direction = 0 + self.log.info("Motor stop") + + def has_Crashed(self) -> bool: + + small_distances = [ + d for d in self.lidar.r_distance[200:880] if 0 < d < CRASH_DIST + ] # 360 to 720 is the front of the car. 1/3 of the fov of the lidar + self.log.debug(f"Distances: {small_distances}") + if len(small_distances) > 2: + # min_index = self.lidar.rDistance.index(min(small_distances)) + while self.tof.distance < REAR_BACKUP_DIST: + self.log.info(f"Rear obstacle detected {self.tof.distance}") + self.target_speed = 0 + time.sleep(0.1) + return True + return False + + def turn_around(self) -> None: + """Turn the car around.""" + self.log.info("Turning around") + + self.target_speed = 0 + self.direction = MAX_ANGLE + self.target_speed = -2 # blocing call + time.sleep(1.8) # Wait for the car to turn around + if self.camera.is_running_in_reversed(): + self.turn_around() + + def main(self) -> None: + # retrieve lidar data. We only take the first 1080 values and ignore the last one for simplicity for the ai + if self.camera is None or self.lidar is None: + self.log.debug("Sensors not yet ready") + return + lidar_data = self.lidar.r_distance[:1080] / 1000 + lidar_data_ai = ( + (lidar_data - 0.5) + * ( + LIDAR_DATA_OFFSET + + LIDAR_DATA_AMPLITUDE + * np.exp(-1 / 2 * ((np.arange(1080) - 135) / LIDAR_DATA_SIGMA**2)) + ) + ) # convert to meters and add Gaussian noise. We manipulate the data provided to the AI + self.direction, self.target_speed = self.driving( + lidar_data_ai, self.camera.camera_matrix() + ) # the ai takes distances in meters not in mm + self.log.debug(f"Min Lidar: {min(lidar_data)}, Max Lidar: {max(lidar_data)}") + """ + if self.camera.is_running_in_reversed(): + self.reverse_count += 1 + else: + self.reverse_count = 0 + if self.reverse_count > 2: + self.turn_around() + self.reverse_count = 0 + + if self.has_Crashed(): + self.log.info("Obstacle detected") + color= self.camera.is_green_or_red(lidar_data) + if color == 0: + small_distances = [ + d for d in self.lidar.r_distance if 0 < d < CRASH_DIST + ] + if len(small_distances) == 0: + self.log.info("No obstacle detected") + return + min_index = np.argmin(small_distances) + direction = ( + MAX_ANGLE if min_index < 540 else -MAX_ANGLE + ) # 540 is the middle of the lidar + color = direction / direction + self.log.info("Obstacle detected, Lidar Fallback") + if color == -1: + self.log.info("Red obstacle detected") + if color == 1: + self.log.info("Green obstacle detected") + angle = -color * MAX_ANGLE + self.target_speed = -2 + self.direction = angle""" + + +class AIProgram(Program): + def __init__(self, serveur) -> None: + super().__init__() + self.log = logging.getLogger(__name__) + self.serveur = serveur + self.driver = None + self.GR86 = None + self.running = False + self.controls_car = True + try: + self.models = [ + model for model in os.listdir(MODEL_PATH) if model.endswith(".onnx") + ] + except Exception as e: + self.log.error(f"Error retrieving models: {e}") + self.nb_models = len(self.models) + self.id_model = self.nb_models + # start with the last model which is the fallback for not running + + @property + def target_speed(self) -> float: + if self.GR86 is None: + return 0.0 + return self.GR86.target_speed + + @property + def direction(self) -> float: + if self.GR86 is None: + return 0.0 + return self.GR86.direction + + def run(self) -> None: + while self.running: + try: + if self.GR86 is not None: + self.GR86.main() + print("lolooibiiuib : " + self.running.__str__()) + except Exception as e: + self.log.error(f"AI error: {e}") + self.running = False + raise + + def initializeai(self, model: str) -> None: + self.driver = Driver(128, 128) + self.driver.load_model(model) + + # self.GR86 = Car(self.driver.ai, self.serveur, model) + self.GR86 = Car(self.driver.omniscent, self.serveur, model) + # self.GR86 = Car(self.driver.simple_minded, self.serveur, model) + + def start(self, model_give: Optional[str] = None) -> None: + + if self.serveur.camera is None or self.serveur.lidar is None: + self.log.error("Sensors not initialized") + return + if self.models is None: + self.log.error("No models available for AI") + return + if model_give is not None: + self.initializeai(model_give) + self.log.info(f"Starting AI with model {model_give}") + else: + try: + self.id_model = (self.id_model + 1) % (self.nb_models) + self.log.info(f"Selected model: {self.models[self.id_model]}") + model = self.models[self.id_model] + self.initializeai(model) + + except Exception as e: + self.log.error(f"Unable to start AI: {e}") + self.driver = None + self.GR86 = None + return + + self.running = True + Thread(target=self.run, daemon=True).start() + + def kill(self) -> None: + self.running = False + + def display(self) -> str: + text = self.__class__.__name__ + if self.running: + text += "*" + if not self.running: + for model in self.models: + text += f"\n {model[:8]}..." # display only the first 5 characters of the model name to avoid cuttoff + + else: + for model in self.models: + if model == self.models[self.id_model]: + text += f"\n -> {model[:8]}..." + else: + text += f"\n {model[:8]}..." + return text + + +""" +if __name__ == '__main__': # non functional + Format= '%(asctime)s:%(name)s:%(levelname)s:%(message)s' + if input("Press D to start in debug mode or any other key to start in normal mode") in ("D", "d"): + logging.basicConfig(level=logging.DEBUG, format=Format) + else: + logging.basicConfig(level=logging.INFO, format=Format) + bp2 = Button("GPIO6") + try: + Schumacher = Driver(128, 128) + GR86 = Car(Schumacher,None,None) + GR86._initialize_camera() + GR86._initialize_lidar() + logging.info("Initialization complete") + if input("Press D to start or any other key to quit") in ("D", "d") or bp2.is_pressed: + logging.info("Start") + while True: + GR86.main() + else: + raise Exception("The program was stopped by the user") + except KeyboardInterrupt: + GR86.stop() + logging.info("The program was stopped by the user") + + except Exception as e: # catch all exceptions to stop the car + GR86.stop() + logging.error("Unknown error") + raise e # re-raise the exception to see the error message + """ diff --git a/src/high_level/src/programs/initialisation.py b/src/high_level/src/programs/initialisation.py new file mode 100644 index 00000000..84e0016c --- /dev/null +++ b/src/high_level/src/programs/initialisation.py @@ -0,0 +1,95 @@ +import logging +import threading +from enum import Enum + +from actionneur_capteur import Camera, I2CArduino, Lidar, ToF +from high_level.autotech_constant import SOCKET_ADRESS + +from .program import Program + + +class ProgramState(Enum): + INITIALIZATION = 1 + RUNNING = 2 + STOPPED = 3 + + +class Initialisation(Program): + def __init__(self, server) -> None: + super().__init__() + self.log = logging.getLogger(__name__) + self.arduino_I2C_init = ProgramState.INITIALIZATION + self.camera_init = ProgramState.INITIALIZATION + self.lidar_init = ProgramState.INITIALIZATION + self.tof_init = ProgramState.INITIALIZATION + + threading.Thread(target=self.init_camera, daemon=True).start() + threading.Thread(target=self.init_lidar, daemon=True).start() + threading.Thread(target=self.init_tof, daemon=True).start() + threading.Thread( + target=self.init_I2C_arduino, + args=(server,), + daemon=True, + ).start() + + def init_I2C_arduino(self, server) -> None: + try: + self.arduino_I2C = I2CArduino(server) + self.arduino_I2C_init = ProgramState.RUNNING + self.log.info("I2C Arduino initialized successfully") + except Exception as e: + self.arduino_I2C_init = ProgramState.STOPPED + self.log.error("I2C Arduino init error : " + str(e)) + + def init_camera(self) -> None: + try: + self.camera = Camera() + self.camera_init = ProgramState.RUNNING + self.log.info("Camera initialized successfully") + except Exception as e: + self.camera_init = ProgramState.STOPPED + self.log.error("Camera init error : " + str(e)) + + def init_lidar(self) -> None: + try: + self.lidar = Lidar(SOCKET_ADRESS["IP"], SOCKET_ADRESS["PORT"]) + self.lidar.stop() + self.lidar.start_continuous(0, 1080) + self.log.info("Lidar initialized successfully") + self.lidar_init = ProgramState.RUNNING + except Exception as e: + self.lidar_init = ProgramState.STOPPED + self.log.error("Lidar init error : " + str(e)) + + def init_tof(self) -> None: + try: + self.tof = ToF() + self.tof_init = ProgramState.RUNNING + self.log.info("Camera initialized successfully") + except Exception as e: + self.tof_init = ProgramState.STOPPED + self.log.error("Tof init error : " + str(e)) + + def display(self) -> str: + + def state_to_text(state: ProgramState) -> str: + match state: + case ProgramState.INITIALIZATION: + return "(en cour)" + case ProgramState.RUNNING: + return "ready." + case ProgramState.STOPPED: + return "error" + + return "unknown" + + text = "\ncamera: " + text += state_to_text(self.camera_init) + text += "\n lidar: " + text += state_to_text(self.lidar_init) + text += "\n tof:" + text += state_to_text(self.tof_init) + text += "\n Arduino:" + text += state_to_text(self.arduino_I2C_init) + + return text diff --git a/src/high_level/src/programs/poweroff.py b/src/high_level/src/programs/poweroff.py new file mode 100644 index 00000000..e64d9372 --- /dev/null +++ b/src/high_level/src/programs/poweroff.py @@ -0,0 +1,25 @@ +import subprocess +import os +from programs.program import Program +import logging + + +class Poweroff(Program): + def __init__(self) -> None: + super().__init__() + self.log = logging.getLogger(__name__) + self.controls_car = False + self.running = False + + def kill(self) -> None: + pass + + def start(self) -> None: + self.log.info("Power off started") + subprocess.Popen( + "sudo poweroff", + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + preexec_fn=os.setsid, + ) diff --git a/src/high_level/src/programs/program.py b/src/high_level/src/programs/program.py new file mode 100644 index 00000000..38827b8a --- /dev/null +++ b/src/high_level/src/programs/program.py @@ -0,0 +1,33 @@ +import logging + +from high_level.autotech_constant import LOGGING_LEVEL + +""" Base class for all programs """ + + +class Program: + controls_car: bool # whether the program controls the car or not to know if we need to stop the old program that controls the car + running: bool # base state of the program (on car startup) + + def __init__(self) -> None: + self.logger = logging.getLogger(__name__) + self.logger.setLevel(LOGGING_LEVEL) + + def kill(self) -> None: + pass + + def start(self) -> None: + pass + + def display(self) -> str: + name = self.__class__.__name__ + if self.running: + return f"{name} \n (running)" + else: + return name + + @property + def target_speed(self) -> float: ... + + @property + def direction(self) -> float: ... diff --git a/src/high_level/src/programs/ps4_controller_program.py b/src/high_level/src/programs/ps4_controller_program.py new file mode 100644 index 00000000..a0925c52 --- /dev/null +++ b/src/high_level/src/programs/ps4_controller_program.py @@ -0,0 +1,159 @@ +import logging +import time +from threading import Thread + +from pyPS4Controller.controller import Controller + +from high_level.autotech_constant import MAX_ANGLE, MAX_CONTROL_SPEED, MIN_CONTROL_SPEED + +from .program import Program + + +def envoie_donnee(Voiture): # if __name__ == "__main__": + print("I2C startup") + import struct + + import smbus + + from high_level.autotech_constant import SLAVE_ADDRESS + + bus = smbus.SMBus(1) + while True: + try: + data = struct.pack( + " 0: + self.speed_mms = 0 + else: + self.speed_mms = vit + + def on_L2_release(self): # Stop the car when L2 is released + self.speed_mms = 0 + + def on_L3_up(self, value): + pass + + def on_L3_down(self, value): + pass + + def on_L3_y_at_rest(self): + pass + + +if __name__ == "__main__": + controller = MyController(interface="/dev/input/js0", connecting_using_ds4drv=False) + try: + Thread(target=envoie_donnee, args=(controller,), daemon=True).start() + controller.listen(timeout=60) + + except KeyboardInterrupt: + print("Program stop") + controller.stop = True + exit(0) diff --git a/src/high_level/src/programs/remote_control.py b/src/high_level/src/programs/remote_control.py new file mode 100644 index 00000000..ed8fcdbe --- /dev/null +++ b/src/high_level/src/programs/remote_control.py @@ -0,0 +1,54 @@ +from programs.program import Program +import struct +import socket +import threading +import logging +from high_level.autotech_constant import PORT_REMOTE_CONTROL + + +class RemoteControl(Program): + """This program allows remote control of the car using UDP packets. + You can take control with the script remote_control_controller.py""" + + def __init__(self) -> None: + super().__init__() + self.log = logging.getLogger(__name__) + self.controls_car = True + self.running = False + self._target_speed = 0 + self._direction = 0 + + # Initialization + self.public = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.public.bind(("0.0.0.0", PORT_REMOTE_CONTROL)) + + self.log.info("Remote control initialization finished") + + @property + def target_speed(self) -> float: + return self._target_speed + + @property + def direction(self) -> float: + return self._direction + + def car_controle(self, sock) -> None: + """Starts control from the PC.""" + sock.settimeout(0.1) + + while self.running: + try: + data, ip = sock.recvfrom(1024) + self._target_speed, self._direction = struct.unpack("ff", data) + except socket.timeout: + continue + + def start(self) -> None: + self.running = True + threading.Thread( + target=self.car_controle, args=(self.public,), daemon=True + ).start() + + def kill(self) -> None: + """Exits the thread from its loop""" + self.running = False diff --git a/src/HL/programme/SshProgramme.py b/src/high_level/src/programs/ssh_programme.py similarity index 56% rename from src/HL/programme/SshProgramme.py rename to src/high_level/src/programs/ssh_programme.py index f08381f7..7863c508 100644 --- a/src/HL/programme/SshProgramme.py +++ b/src/high_level/src/programs/ssh_programme.py @@ -1,38 +1,48 @@ -from src.HL.programme.programme import Program -from src.HL.programme.scripts.get_ip import check_ssh_connections +from _socket import _RetAddress + + import socket import time +from programs.program import Program +from programs.utils.ssh import check_ssh_connections + + class SshProgramme(Program): - """Montre le menu SSH de la voiture et force vitesse/direction à 0""" + """Give information about SSH connections and IP address and if the car is in standby mode (when this program is running)""" - def __init__(self): + def __init__(self) -> None: super().__init__() self.running = True self.controls_car = True - self.target_speed = 0 - self.direction = 0 - # Cache IP self.ip = None self._last_ip_check = 0 self._ip_refresh_interval = 1.0 # secondes - def start(self): + @property + def target_speed(self) -> float: + return 0.0 + + @property + def direction(self) -> float: + return 0.0 + + def start(self) -> None: self.running = True - def stop(self): + def stop(self) -> None: self.running = False - def _update_ip_if_needed(self): + def _update_ip_if_needed(self) -> None: now = time.monotonic() if now - self._last_ip_check >= self._ip_refresh_interval: self.ip = self.get_local_ip() self._last_ip_check = now @staticmethod - def get_local_ip(): + def get_local_ip() -> _RetAddress | None: try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(0.1) @@ -43,12 +53,12 @@ def get_local_ip(): except Exception: return None - def display(self): + def display(self) -> str: self._update_ip_if_needed() - text = f"Ssh to: {self.ip or 'non connecté'}" + text = f"Ssh to: {self.ip or 'not connected'}" if check_ssh_connections(): - text += "\n connecté" + text += "\n connected" if self.running: - text += "\n Voiture en stand by" + text += "\n Car in standby" return text diff --git a/src/high_level/src/programs/utils/__init__.py b/src/high_level/src/programs/utils/__init__.py new file mode 100644 index 00000000..bcbcc503 --- /dev/null +++ b/src/high_level/src/programs/utils/__init__.py @@ -0,0 +1,4 @@ +from .driver import Driver +from .ssh import check_ssh_connections + +__all__ = ['Driver', 'check_ssh_connections'] \ No newline at end of file diff --git a/src/HL/programme/scripts/Driver.py b/src/high_level/src/programs/utils/driver.py similarity index 51% rename from src/HL/programme/scripts/Driver.py rename to src/high_level/src/programs/utils/driver.py index 375eb5ba..f910dae3 100644 --- a/src/HL/programme/scripts/Driver.py +++ b/src/high_level/src/programs/utils/driver.py @@ -1,13 +1,20 @@ +import logging import math -from matplotlib import pyplot as plt -import scipy as sp -from scipy.special import softmax +from typing import Tuple, cast + import numpy as np import onnxruntime as ort -import logging - -from src.HL.Autotech_constant import SPEED_LOOKUP, ANGLE_LOOKUP, MODEL_PATH, Temperature +import scipy as sp +from matplotlib import pyplot as plt +from onnxruntime import InferenceSession +from scipy.special import softmax +from high_level.autotech_constant import ( + ANGLE_LOOKUP, + MODEL_PATH, + SPEED_LOOKUP, + Temperature, +) class Driver: @@ -16,87 +23,106 @@ def __init__(self, context_size=0, horizontal_size=0): self.context_size = context_size self.horizontal_size = horizontal_size self._loaded = False - self.ai_session = None - self.context = None + self.ai_session: InferenceSession + self.context: np.ndarray - if self.log.getLogger().isEnabledFor(self.log.DEBUG): + if self.log.isEnabledFor(logging.DEBUG): self.fig, self.ax = plt.subplots(4, 1, figsize=(10, 8)) - self.steering_bars = self.ax[0].bar(range(16), np.zeros(16), color='blue') + self.steering_bars = self.ax[0].bar(range(16), np.zeros(16), color="blue") self.steering_avg = [ - self.ax[0].plot([0, 0], [0, 1], color=(i/3, 1 - i/3, 0), label='Average')[0] + self.ax[0].plot( + [0, 0], [0, 1], color=(i / 3, 1 - i / 3, 0), label="Average" + )[0] for i in range(4) ] - self.ax[0].set_ylim(0, 1) # Probabilities range from 0 to 1 - self.ax[0].set_title('Steering Action Probabilities') + self.ax[0].set_ylim(0, 1) # Probabilities range from 0 to 1 + self.ax[0].set_title("Steering Action Probabilities") # Speed bars - self.speed_bars = self.ax[1].bar(range(16), np.zeros(16), color='blue') - self.speed_avg = self.ax[1].plot([0, 0], [0, 1], color='red', label='Average')[0] + self.speed_bars = self.ax[1].bar(range(16), np.zeros(16), color="blue") + self.speed_avg = self.ax[1].plot( + [0, 0], [0, 1], color="red", label="Average" + )[0] self.ax[1].set_ylim(0, 1) # Probabilities range from 0 to 1 - self.ax[1].set_title('Speed Action Probabilities') + self.ax[1].set_title("Speed Action Probabilities") # LiDAR img self.lidar_img = self.ax[2].imshow( - np.zeros((128, 128)), - cmap='gray', vmin=0, vmax=np.log(31) + np.zeros((128, 128)), cmap="gray", vmin=0, vmax=np.log(31) ) - self.ax[2].set_title('LiDAR Image') + self.ax[2].set_title("LiDAR Image") # Camera img self.camera_img = self.ax[3].imshow( - np.zeros((128, 128, 3)), - cmap='RdYlGn', vmin=-1, vmax=1 + np.zeros((128, 128, 3)), cmap="RdYlGn", vmin=-1, vmax=1 ) - self.ax[3].set_title('Camera Image') + self.ax[3].set_title("Camera Image") - def load_model(self): + def load_model(self, model: str): if self._loaded: return - self.log.info("Chargement du modèle IA...") - self.ai_session = ort.InferenceSession(MODEL_PATH) - self.context = np.zeros([2, self.context_size, self.horizontal_size], dtype=np.float32 ) + self.log.info("Loading AI model...") + self.ai_session = ort.InferenceSession(MODEL_PATH + "/" + model) + self.context = np.zeros( + [2, self.context_size, self.horizontal_size], dtype=np.float32 + ) self._loaded = True - self.log.info("Modèle IA chargé") + self.log.info("AI model loaded") def reset(self): - self.context = np.zeros([2, self.context_size, self.horizontal_size], dtype=np.float32) + self.context = np.zeros( + [2, self.context_size, self.horizontal_size], dtype=np.float32 + ) - def omniscent(self, lidar_data, camera_data): + def omniscent( + self, lidar_data: np.ndarray, camera_data: np.ndarray + ) -> Tuple[float, float]: return self.ai_update_lidar_camera(lidar_data, camera_data) - def ai(self, lidar_data): + def ai( + self, lidar_data: np.ndarray, camera_data: np.ndarray + ) -> Tuple[float, float]: + # take the camera data for uniformity with the omniscent driver but we dont use it in this driver return self.ai_update_lidar(lidar_data) - def simple_minded(self, lidar_data): + def simple_minded( + self, lidar_data: np.ndarray, camera_data: np.ndarray + ) -> Tuple[float, float]: + # take the camera data for uniformity with the omniscent driver but we dont use it in this driver return self.farthest_distants(lidar_data) - def ai_update_lidar_camera(self, lidar_data, camera_data): + def ai_update_lidar_camera( + self, lidar_data: np.ndarray, camera_data: np.ndarray + ) -> Tuple[float, float]: if not self._loaded: - raise RuntimeError("Driver non initialisé (modèle IA non chargé)") - self.log.info(f"MIN MAX lidar_data: {(min(lidar_data), max(lidar_data))}") + raise RuntimeError("Driver not initialized (AI model not loaded)") + + # self.log.info(f"MIN MAX lidar_data: {(min(lidar_data), max(lidar_data))}") - lidar_data = sp.ndimage.zoom( - np.array(lidar_data, dtype=np.float32), - 128/len(lidar_data) - ) / 1000 * 0.8 + lidar_data = ( + sp.ndimage.zoom( + np.array(lidar_data, dtype=np.float32), 128 / len(lidar_data) + ) + / 1000 + * 0.8 + ) camera_data = sp.ndimage.zoom( - np.array(camera_data, dtype=np.float32), - 128/len(camera_data) + np.array(camera_data, dtype=np.float32), 128 / len(camera_data) + ) + self.context = np.concatenate( + [self.context[:, 1:], [lidar_data[None], camera_data[None]]], axis=1 ) - - self.context = np.concatenate([ - self.context[:, 1:], - [lidar_data[None], camera_data[None]] - ], axis=1) # 2 vectors direction and speed. direction is between hard left at index 0 and hard right at index 1. speed is between min speed at index 0 and max speed at index 1 - vect = self.ai_session.run(None, {'input': self.context[None]})[0][0] + vect = cast( + np.ndarray, self.ai_session.run(None, {"input": self.context[None]})[0] + )[0] vect_dir, vect_prop = vect[:16], vect[16:] # split the vector in 2 - vect_dir = softmax(vect_dir) # distribution de probabilité + vect_dir = softmax(vect_dir) # probability distribution vect_prop = softmax(vect_prop) - if self.log.getLogger().isEnabledFor(log.DEBUG): + if self.log.isEnabledFor(logging.DEBUG): self.log.info(f"MIN MAX lidar_data: {(min(lidar_data), max(lidar_data))}") self.lidar_img.set_array(np.log(1 + self.context[0])) self.camera_img.set_array(self.context[1]) @@ -117,55 +143,57 @@ def ai_update_lidar_camera(self, lidar_data, camera_data): plt.draw() plt.pause(1e-8) - + """ print(" ".join([f"{x:.1f}" for x in vect_dir])) print(" ".join([f"{x:.1f}" for x in vect_prop]), flush=True) - - angle = sum(ANGLE_LOOKUP*vect_dir) # moyenne pondérée des angles - # moyenne pondérée des vitesses - vitesse = sum(SPEED_LOOKUP*vect_prop) + """ + angle = sum(ANGLE_LOOKUP * vect_dir) # weighted average of angles + # weighted average of speeds + vitesse = sum(SPEED_LOOKUP * vect_prop) return angle, vitesse - def ai_update_lidar(self, lidar_data): + def ai_update_lidar(self, lidar_data) -> Tuple[float, float]: if not self._loaded: - raise RuntimeError("Driver non initialisé (modèle IA non chargé)") + raise RuntimeError("Driver not initialized (AI model not loaded)") lidar_data = np.array(lidar_data, dtype=np.float32) * 1.6 # 2 vectors direction and speed. direction is between hard left at index 0 and hard right at index 1. speed is between min speed at index 0 and max speed at index 1 - vect = self.ai_session.run(None, {'input': lidar_data[None]})[0][0] + vect = cast( + np.ndarray, self.ai_session.run(None, {"input": lidar_data[None]})[0] + )[0] - vect_dir, vect_prop = vect[:16], vect[16:] # split the vector in 2 - vect_dir = softmax(vect_dir/Temperature) # distribution de probabilité - vect_prop = softmax(vect_prop) + vect_dir, vect_prop = vect[:16], vect[16:] # split the vector in - angle = sum(ANGLE_LOOKUP*vect_dir) # moyenne pondérée des angles - # moyenne pondérée des vitesses - vitesse = sum(SPEED_LOOKUP*vect_prop) + vect_dir = softmax(vect_dir / Temperature) # probability distribution + vect_prop = softmax(vect_prop) + angle = sum(ANGLE_LOOKUP * vect_dir) # weighted average of angles + # weighted average of speeds + vitesse = sum(SPEED_LOOKUP * vect_prop) return angle, vitesse - - - def farthest_distants(self, lidar_data): + def farthest_distants(self, lidar_data: np.ndarray) -> Tuple[float, float]: # Initialize variables - lidar_data_mm = [0] * 360 # Assuming 360 degrees for the lidar data + lidar_data_mm = [0.0] * 360 # Assuming 360 degrees for the lidar data filter_size = 15 max_value = 0 max_index = 0 - closest_distance = float('inf') + closest_distance = float("inf") average_distance = 0 valid_distance_count = 0 # Translate lidar angles to table angles for angle in range(len(lidar_data_mm)): if 135 < angle < 225: - lidar_data_mm[angle] = float('nan') + lidar_data_mm[angle] = float("nan") else: lidar_data_mm[angle] = lidar_data[540 + (-angle * 4)] # Find the maximum value in the lidar data for i in range(-45, 45): - sum_values = sum(lidar_data_mm[n] for n in range(i - filter_size, i + filter_size)) + sum_values = sum( + lidar_data_mm[n] for n in range(i - filter_size, i + filter_size) + ) if sum_values > max_value: max_value = sum_values max_index = i @@ -179,11 +207,13 @@ def farthest_distants(self, lidar_data): valid_distance_count += 1 if current_distance < closest_distance: closest_distance = current_distance - average_distance = average_distance / valid_distance_count if valid_distance_count != 0 else 0 + average_distance = ( + average_distance / valid_distance_count if valid_distance_count != 0 else 0 + ) speed = average_distance * 0.002 print("speed =", speed) - speed = 2 + speed = 2.0 # Calculate the steering angle if speed >= 0.057: @@ -193,9 +223,9 @@ def farthest_distants(self, lidar_data): print("val =", val) steering_angle = (math.atan(val) / math.pi) * 180 except Exception as e: - steering_angle = 0 + steering_angle = 0.0 print("error calculating angle:", e) else: - steering_angle = 0 + steering_angle = 0.0 return steering_angle, speed diff --git a/src/high_level/src/programs/utils/ssh.py b/src/high_level/src/programs/utils/ssh.py new file mode 100644 index 00000000..1a76dcbb --- /dev/null +++ b/src/high_level/src/programs/utils/ssh.py @@ -0,0 +1,15 @@ +import subprocess + + +def check_ssh_connections(): + result = subprocess.run(["who"], stdout=subprocess.PIPE) + output = result.stdout.decode("utf-8") + ssh_connections = [line for line in output.split("\n") if "pts/" in line] + return len(ssh_connections) > 0 # bool + + +if __name__ == "__main__": + if check_ssh_connections(): + print("There are active SSH connections.") + else: + print("No active SSH connections.") diff --git a/src/high_level/src/site_controle/app.js b/src/high_level/src/site_controle/app.js new file mode 100644 index 00000000..bd33a9e0 --- /dev/null +++ b/src/high_level/src/site_controle/app.js @@ -0,0 +1,518 @@ +const API = ""; // same origin (FastAPI) +const speedHistory = { + real: [], + demand: [], + maxPoints: 100 +}; + +function drawSpeedChart() { + const canvas = document.getElementById("speedChart"); + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + const w = canvas.width; + const h = canvas.height; + + ctx.clearRect(0, 0, w, h); + + // axes (drawing axes) + ctx.strokeStyle = "#444"; + ctx.beginPath(); + ctx.moveTo(40, 10); + ctx.lineTo(40, h - 20); + ctx.lineTo(w - 10, h - 20); + ctx.stroke(); + + + + const maxAbs = Math.max( + ...speedHistory.real.map(Math.abs), + ...speedHistory.demand.map(Math.abs), + 1 + ); + const yZero = h / 2; + const scaleY = (h - 40) / (2 * maxAbs); + // y=0 line + ctx.strokeStyle = "#666"; + ctx.setLineDash([5, 5]); + ctx.beginPath(); + ctx.moveTo(40, yZero); + ctx.lineTo(w - 10, yZero); + ctx.stroke(); + ctx.setLineDash([]); + + + function drawCurve(data, color) { + ctx.strokeStyle = color; + ctx.beginPath(); + + data.forEach((v, i) => { + const x = 40 + (i / (data.length - 1)) * (w - 60); + const y = yZero - v * scaleY; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + + ctx.stroke(); + } + + drawCurve(speedHistory.demand, "#ffaa00"); // target + drawCurve(speedHistory.real, "#00ff88"); // actual + + // legend + + ctx.fillStyle = "#ffaa00"; + ctx.fillText("Target", w - 100, 20); + ctx.fillStyle = "#00ff88"; + ctx.fillText("Actual", w - 100, 35); + ctx.font = "10px monospace"; + +} + +function drawSteering(directionDeg) { + const canvas = document.getElementById("steeringViz"); + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + const w = canvas.width; + const h = canvas.height; + + ctx.clearRect(0, 0, w, h); + + const centerX = w / 2; + const centerY = h * 0.85; + const radius = h * 0.65; + + /* -------- Protractor (-18° to +18°) -------- */ + ctx.strokeStyle = "#444"; + ctx.lineWidth = 1; + + const maxDeg = 18; + + ctx.beginPath(); + ctx.arc( + centerX, + centerY, + radius, + (-90 - maxDeg) * Math.PI / 180, + (-90 + maxDeg) * Math.PI / 180 + ); + ctx.stroke(); + + /* -------- Graduations (Marks) -------- */ + for (let d = -18; d <= 18; d += 6) { + const a = (d - 90) * Math.PI / 180; + + ctx.beginPath(); + ctx.moveTo( + centerX + Math.cos(a) * (radius - 10), + centerY + Math.sin(a) * (radius - 10) + ); + ctx.lineTo( + centerX + Math.cos(a) * radius, + centerY + Math.sin(a) * radius + ); + ctx.stroke(); + + ctx.fillStyle = "#777"; + ctx.font = "10px monospace"; + ctx.fillText( + `${d}°`, + centerX + Math.cos(a) * (radius + 10) - 8, + centerY + Math.sin(a) * (radius + 10) + 3 + ); + } + + /* -------- Rod (target direction) -------- */ + const angleRad = (directionDeg - 90) * Math.PI / 180; + + ctx.strokeStyle = "#00ff88"; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(centerX, centerY); + ctx.lineTo( + centerX + Math.cos(angleRad) * (radius - 20), + centerY + Math.sin(angleRad) * (radius - 20) + ); + ctx.stroke(); + + /* -------- Center -------- */ + ctx.fillStyle = "#00ff88"; + ctx.beginPath(); + ctx.arc(centerX, centerY, 5, 0, Math.PI * 2); + ctx.fill(); +} + + +async function fetchStatus() { + const res = await fetch(`${API}/api/status`); + if (!res.ok) { + throw new Error(`API status failed: ${res.status}`); + } + return await res.json(); +} + +async function fetchCameraUrl() { + const res = await fetch(`${API}/api/stream/camera`); + if (!res.ok) { + throw new Error(`Camera url API failed: ${res.status}`); + } + const data = await res.json(); + return data.url; +} + + +async function startProgram(id) { + await fetch(`${API}/api/programs/${id}/start`, { method: "POST" }); + refreshPrograms(); +} + +async function killProgram(id) { + await fetch(`${API}/api/programs/${id}/kill`, { method: "POST" }); + refreshPrograms(); +} + +function updateTelemetry(t) { + document.getElementById("lipo").textContent = t.battery.lipo.toFixed(2); + document.getElementById("nimh").textContent = t.battery.nimh.toFixed(2); + document.getElementById("vitesse").textContent = + t.car.current_speed.toFixed(2); + + document.getElementById("target_speed").textContent = + t.car.target_speed.toFixed(2); + + document.getElementById("direction").textContent = + t.car.direction.toFixed(2); + document.getElementById("active_program").textContent = + t.car.car_control ?? "None"; + document.getElementById("tof").textContent = + t.car.tof.toFixed(2); + speedHistory.real.push(t.car.current_speed); + speedHistory.demand.push(t.car.target_speed); + + if (speedHistory.real.length > speedHistory.maxPoints) { + speedHistory.real.shift(); + speedHistory.demand.shift(); + } + + drawSpeedChart(); + drawSteering(t.car.direction); + +} + +function updatePrograms(programs) { + const tbody = document.getElementById("program_table"); + tbody.innerHTML = ""; + + for (const p of programs) { + const tr = document.createElement("tr"); + + tr.innerHTML = ` + ${p.id} + ${p.name} + ${p.running ? "🟢" : "🔴"} + ${p.controls_car ? "🚗" : "-"} + + + + + `; + + tbody.appendChild(tr); + } +} +async function loadModels() { + try { + const res = await fetch("/api/status"); + if (!res.ok) throw new Error("Failed to load models"); + + const data = await res.json(); + const models = data.models; + + const select = document.getElementById("model_select"); + select.innerHTML = ""; + + for (const m of models) { + const option = document.createElement("option"); + option.value = m; + option.textContent = m; + select.appendChild(option); + } + + } catch (e) { + console.error("Failed to load models", e); + } +} + +async function startSelectedModel() { + const select = document.getElementById("model_select"); + const model = select.value; + + try { + const res = await fetch("/api/ai/start", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ model }) + }); + + if (!res.ok) throw new Error("Failed to start AI"); + + console.log("AI started with model:", model); + + } catch (e) { + console.error("Start model failed", e); + } +} + +async function refreshPrograms() { + try { + const res = await fetch("/api/programs"); + if (!res.ok) { + throw new Error(`Programs API failed: ${res.status}`); + } + const programs = await res.json(); + updatePrograms(programs); + } catch (e) { + console.error("Failed to refresh programs", e); + } +} +function decodeBase64ToInt16Array(b64) { + const bin = atob(b64); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); + return new Int16Array(bytes.buffer); +} +function decodeBase64ToFloat32Array(b64) { + const bin = atob(b64); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); + return new Float32Array(bytes.buffer); +} +async function fetchLidarInit() { + const res = await fetch(`${API}/api/lidar_init`); + if (!res.ok) throw new Error(`lidar_init failed: ${res.status}`); + const data = await res.json(); + return data; +} + +function initLidar(info, retryDelay = 1000) { + const theta = decodeBase64ToFloat32Array(info.xTheta); + const carBorder = info.car_border ? decodeBase64ToFloat32Array(info.car_border) : null; + console.log("Lidar theta:", theta); + console.log("Car border:", carBorder); + const canvas = document.getElementById("lidar"); + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + const scale = 0.50; // 10 cm = 15 px + + + // Precompute sin/cos once (fast) + const sinT = new Float32Array(theta.length); + const cosT = new Float32Array(theta.length); + for (let i = 0; i < theta.length; i++) { + sinT[i] = Math.sin(theta[i]); + cosT[i] = Math.cos(theta[i]); + } + + + + const proto = location.protocol === "https:" ? "wss" : "ws"; + const ws = new WebSocket(proto + "://" + location.host + "/api/lidar/ws"); + try { + ws.onmessage = (e) => { + const data = JSON.parse(e.data); + + const r = decodeBase64ToInt16Array(data.r); + const yaw = data.yaw ?? 0.0; + const tof = data.tof ?? 0.0; + // Safety: handle mismatch if lidar changes resolution + const n = Math.min(r.length, sinT.length); + + // yaw rotation scalars + const cosYaw = Math.cos(yaw); + const sinYaw = Math.sin(yaw); + + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.translate(canvas.width / 2, canvas.height / 2); + + /* ---------- Grid ---------- */ + const circleStepMM = 100; // 10 cm + const circleCount = 15; + + ctx.strokeStyle = "#333"; + ctx.lineWidth = 1; + + for (let i = 1; i <= circleCount; i++) { + const r = i * circleStepMM * scale; + ctx.beginPath(); + ctx.arc(0, 0, r, 0, Math.PI * 2); + ctx.stroke(); + + if (i % (circleCount / 3) === 0) { + // distance label + ctx.fillStyle = "#777"; + ctx.font = "10px monospace"; + ctx.fillText(`${i * 10} cm`, r + 2, 0); + } + } + // Lidar FOV (270°) + ctx.strokeStyle = "#444"; + ctx.lineWidth = 1; + + const fovMin = -135 * Math.PI / 180; + const fovMax = 135 * Math.PI / 180; + const fovRadius = 1500 * scale; + + ctx.beginPath(); + // left line + ctx.moveTo(0, 0); + ctx.lineTo( + Math.sin(fovMin) * fovRadius, + -Math.cos(fovMin) * fovRadius + ); + ctx.stroke(); + + // right line + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo( + Math.sin(fovMax) * fovRadius, + -Math.cos(fovMax) * fovRadius + ); + ctx.stroke(); + /* ---------- Axes (Axis) ---------- */ + ctx.strokeStyle = "#555"; + ctx.beginPath(); + ctx.moveTo(-canvas.width / 2, 0); + ctx.lineTo(canvas.width / 2, 0); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(0, -canvas.height / 2); + ctx.lineTo(0, canvas.height / 2); + ctx.stroke(); + + /* ---------- LIDAR Points ---------- */ + ctx.fillStyle = "#00ff88"; + for (let i = 0; i < n; i++) { + const ri = r[i]; // mm + + // x = -(sin(theta+yaw))*r + // y = (cos(theta+yaw))*r + // sin(theta+yaw)=sinT*cosYaw + cosT*sinYaw + // cos(theta+yaw)=cosT*cosYaw - sinT*sinYaw + const sinW = sinT[i] * cosYaw + cosT[i] * sinYaw; + const cosW = cosT[i] * cosYaw - sinT[i] * sinYaw; + + const x = (-sinW * ri) * scale; + const y = (cosW * ri) * scale; + + ctx.fillRect(x, -y, 2, 2); + } + /* ---------- Car Border (from min_lidar) ---------- */ + if (carBorder) { + ctx.fillStyle = "#fffb00"; + for (let i = 0; i < n; i++) { + // if (i >= 750) ctx.fillStyle = "#ff0000"; // front part in red + const ri = carBorder[i]; // mm + + // x = -(sin(theta+yaw))*r + // y = (cos(theta+yaw))*r + // sin(theta+yaw)=sinT*cosYaw + cosT*sinYaw + // cos(theta+yaw)=cosT*cosYaw - sinT*sinYaw + const sinW = sinT[i] * cosYaw + cosT[i] * sinYaw; + const cosW = cosT[i] * cosYaw - sinT[i] * sinYaw; + + const x = (-sinW * ri) * scale; + const y = (cosW * ri) * scale; + + ctx.fillRect(x, -y, 2, 2); + } + } + // Draw ToF point on lidar pov + ctx.fillStyle = "#00eaff"; + const tofY = (tof + 30) * scale * 10; // assuming tof[0] is the distance in mm and the 30 is an offset to place it correctly on the canvas comparing distance of the tof and the lidar + ctx.beginPath(); + ctx.arc(0, tofY, 5, 0, Math.PI); + ctx.fill(); + ctx.restore(); + }; + } catch (e) { + console.error("Error in LIDAR WS onmessage:", e); + } + + ws.onclose = () => { + console.warn("LIDAR WS disconnected"); + + setTimeout(() => { + initLidar(info, Math.min(retryDelay * 2, 8000)); + }, retryDelay); + } +} +function initTelemetryWS() { + const proto = location.protocol === "https:" ? "wss" : "ws"; + const ws = new WebSocket(proto + "://" + location.host + "/api/telemetry/ws"); + try { + ws.onmessage = (e) => { + const data = JSON.parse(e.data); + updateTelemetry(data); + }; + } catch (e) { + console.error("Error in Telemetry WS onmessage:", e); + } + ws.onclose = () => { + console.warn("Telemetry WS disconnected, retrying..."); + setTimeout(initTelemetryWS, 1000); + }; +} + +async function loadProgramsOnce() { + try { + const res = await fetch("/api/programs"); + if (!res.ok) { + throw new Error(`Programs API failed: ${res.status}`); + } + const programs = await res.json(); + updatePrograms(programs); + } catch (e) { + console.error("Failed to load programs", e); + } +} + + +async function init() { + try { + const camUrl = await fetchCameraUrl(); + const camEl = document.getElementById("camera_frame"); + const camLink = document.getElementById("camera"); + loadModels(); + // const url = "http://10.255.28.97:8889/cam/"; + + // if (Hls.isSupported()) { + // const hls = new Hls(); + // hls.loadSource(url); + // hls.attachMedia(video); + // } else if (video.canPlayType("application/vnd.apple.mpegurl")) { + // video.src = url; + // } + + + if (camEl && camLink) { + camEl.src = camUrl; + camLink.href = camUrl; + + } else { + console.warn("Element #camera not found at initialization"); + } + const data = await fetchLidarInit(); + initLidar(data); + initTelemetryWS(); + loadProgramsOnce(); + } catch (e) { + console.error("Error in init:", e); + } +} + +window.addEventListener("DOMContentLoaded", init); \ No newline at end of file diff --git a/src/HL/site_controle/index.html b/src/high_level/src/site_controle/index.html similarity index 73% rename from src/HL/site_controle/index.html rename to src/high_level/src/site_controle/index.html index dacbc6f5..1b03a71a 100644 --- a/src/HL/site_controle/index.html +++ b/src/high_level/src/site_controle/index.html @@ -4,6 +4,7 @@ CoVAPSy – Remote Control +
@@ -21,14 +22,21 @@

Télémétrie:

Vitesse réelle: --

Vitesse demandée: --

Direction demandée: --

- +

Distance ToF: -- cm

Programme actif: --

@@ -46,10 +54,16 @@

Télémétrie:

+ +
+

IA – Choisir un modèle

+ + +
-
+

Programmes:

@@ -64,6 +78,7 @@

Programmes:

+
diff --git a/src/high_level/src/site_controle/lidar.html b/src/high_level/src/site_controle/lidar.html new file mode 100644 index 00000000..3505cc94 --- /dev/null +++ b/src/high_level/src/site_controle/lidar.html @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + diff --git "a/docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_transparent.png" "b/src/high_level/src/site_controle/ressources/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_transparent.png" similarity index 100% rename from "docs/img/Logo/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_transparent.png" rename to "src/high_level/src/site_controle/ressources/Logo_\303\251quipe N\302\26020_AutoTech 2025 (long)_promo Ecole_transparent.png" diff --git a/src/HL/site_controle/style.css b/src/high_level/src/site_controle/style.css similarity index 59% rename from src/HL/site_controle/style.css rename to src/high_level/src/site_controle/style.css index ec2f9b43..bc1a399d 100644 --- a/src/HL/site_controle/style.css +++ b/src/high_level/src/site_controle/style.css @@ -81,3 +81,50 @@ a:hover > canvas { height: 50px; width: auto; } + + +/* --- Card Programmes large --- */ +.card-programs { + grid-column: span 2; +} + +/* Sur très grand écran → prend 3 colonnes */ +@media (min-width: 1400px) { + .card-programs { + grid-column: span 3; + } +} + +/* Sur petit écran → pleine largeur */ +@media (max-width: 800px) { + .card-programs { + grid-column: 1 / -1; + } +} + +/* --- Amélioration table --- */ +.card-programs table { + width: 100%; + border-collapse: collapse; + font-size: 14px; +} + +.card-programs th { + background: #2f3655; + font-weight: bold; + padding: 10px; +} + +.card-programs td { + padding: 8px; +} + +.card-programs tr:hover { + background: #2a2f45; + transition: 0.2s; +} + +/* Scroll horizontal si écran trop petit */ +.card-programs { + overflow-x: auto; +} \ No newline at end of file diff --git a/scripts/Urg_information_win-1.0.2/COPYING.txt b/src/low_level/scripts/Urg_information_win-1.0.2/COPYING.txt similarity index 100% rename from scripts/Urg_information_win-1.0.2/COPYING.txt rename to src/low_level/scripts/Urg_information_win-1.0.2/COPYING.txt diff --git a/scripts/Urg_information_win-1.0.2/README.txt b/src/low_level/scripts/Urg_information_win-1.0.2/README.txt similarity index 100% rename from scripts/Urg_information_win-1.0.2/README.txt rename to src/low_level/scripts/Urg_information_win-1.0.2/README.txt diff --git a/scripts/Urg_information_win-1.0.2/Urg_information.exe b/src/low_level/scripts/Urg_information_win-1.0.2/Urg_information.exe similarity index 100% rename from scripts/Urg_information_win-1.0.2/Urg_information.exe rename to src/low_level/scripts/Urg_information_win-1.0.2/Urg_information.exe diff --git a/scripts/Urg_viewer_win-2.0.7/COPYING.txt b/src/low_level/scripts/Urg_viewer_win-2.0.7/COPYING.txt similarity index 100% rename from scripts/Urg_viewer_win-2.0.7/COPYING.txt rename to src/low_level/scripts/Urg_viewer_win-2.0.7/COPYING.txt diff --git a/scripts/Urg_viewer_win-2.0.7/README.txt b/src/low_level/scripts/Urg_viewer_win-2.0.7/README.txt similarity index 100% rename from scripts/Urg_viewer_win-2.0.7/README.txt rename to src/low_level/scripts/Urg_viewer_win-2.0.7/README.txt diff --git a/scripts/Urg_viewer_win-2.0.7/Urg_viewer.exe b/src/low_level/scripts/Urg_viewer_win-2.0.7/Urg_viewer.exe similarity index 100% rename from scripts/Urg_viewer_win-2.0.7/Urg_viewer.exe rename to src/low_level/scripts/Urg_viewer_win-2.0.7/Urg_viewer.exe diff --git a/scripts/Urg_viewer_win-2.0.7/Urg_viewer_ja.qm b/src/low_level/scripts/Urg_viewer_win-2.0.7/Urg_viewer_ja.qm similarity index 100% rename from scripts/Urg_viewer_win-2.0.7/Urg_viewer_ja.qm rename to src/low_level/scripts/Urg_viewer_win-2.0.7/Urg_viewer_ja.qm diff --git a/scripts/Urg_viewer_win-2.0.7/address.txt b/src/low_level/scripts/Urg_viewer_win-2.0.7/address.txt similarity index 100% rename from scripts/Urg_viewer_win-2.0.7/address.txt rename to src/low_level/scripts/Urg_viewer_win-2.0.7/address.txt diff --git a/scripts/Urg_viewer_win-2.0.7/glew32.dll b/src/low_level/scripts/Urg_viewer_win-2.0.7/glew32.dll similarity index 100% rename from scripts/Urg_viewer_win-2.0.7/glew32.dll rename to src/low_level/scripts/Urg_viewer_win-2.0.7/glew32.dll diff --git a/src/low_level/scripts/tune_pwm_direction.py b/src/low_level/scripts/tune_pwm_direction.py new file mode 100644 index 00000000..c300e4e9 --- /dev/null +++ b/src/low_level/scripts/tune_pwm_direction.py @@ -0,0 +1,90 @@ +from rpi_hardware_pwm import HardwarePWM + +# startup parameters, with stops very close to the center +direction = -1 # 1 for angle_pwm_min to the left, -1 for angle_pwm_min to the right +angle_pwm_min = 6.91 # min +angle_pwm_max = 10.7 # max +angle_pwm_centre = 8.805 + +angle_degre_max = +18 # towards the left +angle_degre = 0 + +pwm_dir = HardwarePWM( + pwm_channel=1, hz=50, chip=2 +) # use chip 2 on pi 5 in accordance with the documentation +pwm_dir.start(angle_pwm_centre) + + +def set_direction_degrees(angle_degrees): + global angle_pwm_min + global angle_pwm_max + global angle_pwm_centre + angle_pwm = angle_pwm_centre + direction * ( + angle_pwm_max - angle_pwm_min + ) * angle_degrees / (2 * angle_degre_max) + if angle_pwm > angle_pwm_max: + angle_pwm = angle_pwm_max + if angle_pwm < angle_pwm_min: + angle_pwm = angle_pwm_min + pwm_dir.change_duty_cycle(angle_pwm) + + +print("Adjust stops, Q to quit") +print("Enter numeric value to test a direction angle") +print("I to reverse left and right") +print("g to decrease the left stop and G to increase it") +print("d to decrease the right stop and D to increase it") + +while True: + a = input("angle, I, g, G, d, D ?") + try: + angle_degre = int(a) + set_direction_degrees(angle_degre) + except Exception: + if a == "I": + direction = -direction + print("new direction: " + str(direction)) + elif a == "g": + if direction == 1: + angle_pwm_max -= 0.1 + print("new left stop: " + str(angle_pwm_max)) + else: + angle_pwm_min += 0.1 + print("new left stop: " + str(angle_pwm_min)) + angle_pwm_centre = (angle_pwm_max + angle_pwm_min) / 2 + set_direction_degrees(18) + elif a == "G": + if direction == 1: + angle_pwm_max += 0.1 + print("new left stop: " + str(angle_pwm_max)) + else: + angle_pwm_min -= 0.1 + print("new left stop: " + str(angle_pwm_min)) + angle_pwm_centre = (angle_pwm_max + angle_pwm_min) / 2 + set_direction_degrees(18) + elif a == "d": + if direction == -1: + angle_pwm_max -= 0.1 + print("new right stop: " + str(angle_pwm_max)) + else: + angle_pwm_min += 0.1 + print("new right stop: " + str(angle_pwm_min)) + angle_pwm_centre = (angle_pwm_max + angle_pwm_min) / 2 + set_direction_degrees(-18) + elif a == "D": + if direction == -1: + angle_pwm_max += 0.1 + print("new right stop: " + str(angle_pwm_max)) + else: + angle_pwm_min -= 0.1 + print("new right stop: " + str(angle_pwm_min)) + angle_pwm_centre = (angle_pwm_max + angle_pwm_min) / 2 + set_direction_degrees(-18) + else: + break + +print("new values") +print("direction: " + str(direction)) +print("angle_pwm_min: " + str(angle_pwm_min)) +print("angle_pwm_max: " + str(angle_pwm_max)) +print("angle_pwm_centre: " + str(angle_pwm_centre)) diff --git a/src/low_level/scripts/tune_pwm_propulsion.py b/src/low_level/scripts/tune_pwm_propulsion.py new file mode 100644 index 00000000..41767532 --- /dev/null +++ b/src/low_level/scripts/tune_pwm_propulsion.py @@ -0,0 +1,110 @@ +import time + +from rpi_hardware_pwm import HardwarePWM + +# parameters for the speed_m_s function, to be calibrated +direction_prop = ( + 1.0 # 1 for controllers on the right or a small ratio corresponds to forward drive +) +pwm_stop_prop = 1.0 +point_mort_prop = 1.0 +delta_pwm_max_prop = 1.0 # pwm at which maximum speed is reached + +vitesse_max_m_s_hard = 8 # maximum speed the car can achieve +vitesse_max_m_s_soft = 2 # maximum speed we want to achieve + +pwm_prop = HardwarePWM( + pwm_channel=0, hz=50, chip=2 +) # use chip 2 on pi 5 in accordance with the documentation +pwm_prop.start(pwm_stop_prop) + + +def set_speed_m_s(vitesse_m_s): + if vitesse_m_s > vitesse_max_m_s_soft: + vitesse_m_s = vitesse_max_m_s_soft + elif vitesse_m_s < -vitesse_max_m_s_hard: + vitesse_m_s = -vitesse_max_m_s_hard + if vitesse_m_s == 0: + pwm_prop.change_duty_cycle(pwm_stop_prop) + elif vitesse_m_s > 0: + vitesse = vitesse_m_s * (delta_pwm_max_prop) / vitesse_max_m_s_hard + pwm_prop.change_duty_cycle( + pwm_stop_prop + direction_prop * (point_mort_prop + vitesse) + ) + elif vitesse_m_s < 0: + vitesse = vitesse_m_s * (delta_pwm_max_prop) / vitesse_max_m_s_hard + pwm_prop.change_duty_cycle( + pwm_stop_prop - direction_prop * (point_mort_prop - vitesse) + ) + + +def reverse(): + set_speed_m_s(-vitesse_max_m_s_hard) + time.sleep(0.2) + set_speed_m_s(0) + time.sleep(0.2) + set_speed_m_s(-1) + + +print("Adjust stops, Q to quit") +print("Enter numeric value to test a speed in mm/s") +print("R to reverse") +print("I to reverse left and right") +print("p to decrease delta_pwm_max_prop and P to increase it") +print("z to decrease the zero point 1.5 ms and Z to increase it") +print("m to decrease the neutral point and M to increase it") + + +while True: + a = input("speed in mm/s, R, I, p, P, z, Z, m, M: ") + try: + vitesse_mm_s = int(a) + set_speed_m_s(vitesse_mm_s / 1000.0) + except Exception: + if a == "I" or a == "i": + direction_prop = -direction_prop + print("new direction: " + str(direction_prop)) + elif a == "R": + reverse() + print("reverse") + elif a == "p": + delta_pwm_max_prop -= 0.1 + print("new delta_pwm_max_prop: " + str(delta_pwm_max_prop)) + pwm_prop.change_duty_cycle( + pwm_stop_prop + direction_prop * (point_mort_prop + delta_pwm_max_prop) + ) + elif a == "P": + delta_pwm_max_prop += 0.1 + print("new delta_pwm_max_prop: " + str(delta_pwm_max_prop)) + pwm_prop.change_duty_cycle( + pwm_stop_prop + direction_prop * (point_mort_prop + delta_pwm_max_prop) + ) + elif a == "z": + pwm_stop_prop -= 0.01 + print("new pwm_stop_prop: " + str(pwm_stop_prop)) + pwm_prop.change_duty_cycle(pwm_stop_prop) + elif a == "Z": + pwm_stop_prop += 0.01 + print("new pwm_stop_prop: " + str(pwm_stop_prop)) + pwm_prop.change_duty_cycle(pwm_stop_prop) + elif a == "m": + point_mort_prop -= 0.01 + print("new point_mort_prop: " + str(point_mort_prop)) + pwm_prop.change_duty_cycle( + pwm_stop_prop + direction_prop * (point_mort_prop) + ) + elif a == "M": + point_mort_prop += 0.01 + print("new point_mort_prop: " + str(point_mort_prop)) + pwm_prop.change_duty_cycle( + pwm_stop_prop + direction_prop * (point_mort_prop) + ) + else: + break + +pwm_prop.change_duty_cycle(pwm_stop_prop) +print("new values") +print("direction: " + str(direction_prop)) +print("delta_pwm_max_prop: " + str(delta_pwm_max_prop)) +print("zero point 1.5 ms: " + str(pwm_stop_prop)) +print("neutral point: " + str(point_mort_prop)) diff --git a/src/LL/Asserv2/Asserv2.ino b/src/low_level/src/Asserv2/Asserv2.ino similarity index 100% rename from src/LL/Asserv2/Asserv2.ino rename to src/low_level/src/Asserv2/Asserv2.ino diff --git a/src/LL/Asserv2/I2Cslave.cpp b/src/low_level/src/Asserv2/I2Cslave.cpp similarity index 100% rename from src/LL/Asserv2/I2Cslave.cpp rename to src/low_level/src/Asserv2/I2Cslave.cpp diff --git a/src/LL/Asserv2/I2Cslave.hpp b/src/low_level/src/Asserv2/I2Cslave.hpp similarity index 100% rename from src/LL/Asserv2/I2Cslave.hpp rename to src/low_level/src/Asserv2/I2Cslave.hpp diff --git a/src/LL/Asserv2/VoltageReader.cpp b/src/low_level/src/Asserv2/VoltageReader.cpp similarity index 100% rename from src/LL/Asserv2/VoltageReader.cpp rename to src/low_level/src/Asserv2/VoltageReader.cpp diff --git a/src/LL/Asserv2/VoltageReader.hpp b/src/low_level/src/Asserv2/VoltageReader.hpp similarity index 100% rename from src/LL/Asserv2/VoltageReader.hpp rename to src/low_level/src/Asserv2/VoltageReader.hpp diff --git a/src/LL/Asserv2bazar/Asserv2bazar.ino b/src/low_level/src/Asserv2bazar/Asserv2bazar.ino similarity index 100% rename from src/LL/Asserv2bazar/Asserv2bazar.ino rename to src/low_level/src/Asserv2bazar/Asserv2bazar.ino diff --git a/src/LL/I2C_Scanner/I2C_Scanner.ino b/src/low_level/src/I2C_Scanner/I2C_Scanner.ino similarity index 100% rename from src/LL/I2C_Scanner/I2C_Scanner.ino rename to src/low_level/src/I2C_Scanner/I2C_Scanner.ino diff --git a/src/LL/Lecture_tension_I2C/Lecture_tension_I2C.ino b/src/low_level/src/Lecture_tension_I2C/Lecture_tension_I2C.ino similarity index 100% rename from src/LL/Lecture_tension_I2C/Lecture_tension_I2C.ino rename to src/low_level/src/Lecture_tension_I2C/Lecture_tension_I2C.ino diff --git a/src/LL/OLED_I2C/OLED_I2C.ino b/src/low_level/src/OLED_I2C/OLED_I2C.ino similarity index 100% rename from src/LL/OLED_I2C/OLED_I2C.ino rename to src/low_level/src/OLED_I2C/OLED_I2C.ino diff --git a/src/LL/SPI_master/SPI_master.ino b/src/low_level/src/SPI_master/SPI_master.ino similarity index 100% rename from src/LL/SPI_master/SPI_master.ino rename to src/low_level/src/SPI_master/SPI_master.ino diff --git a/src/LL/SPI_slave/SPI_slave.ino b/src/low_level/src/SPI_slave/SPI_slave.ino similarity index 100% rename from src/LL/SPI_slave/SPI_slave.ino rename to src/low_level/src/SPI_slave/SPI_slave.ino diff --git a/src/LL/ToF_I2C/ToF_I2C.ino b/src/low_level/src/ToF_I2C/ToF_I2C.ino similarity index 100% rename from src/LL/ToF_I2C/ToF_I2C.ino rename to src/low_level/src/ToF_I2C/ToF_I2C.ino diff --git a/src/LL/slaveI2C/slaveI2C.ino b/src/low_level/src/slaveI2C/slaveI2C.ino similarity index 100% rename from src/LL/slaveI2C/slaveI2C.ino rename to src/low_level/src/slaveI2C/slaveI2C.ino diff --git a/src/simulation/README.md b/src/simulation/README.md new file mode 100644 index 00000000..e69de29b diff --git a/src/simulation/pyproject.toml b/src/simulation/pyproject.toml new file mode 100644 index 00000000..6af7080d --- /dev/null +++ b/src/simulation/pyproject.toml @@ -0,0 +1,22 @@ +[project] +name = "simulation" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12, <3.14" +dependencies = [ + "gymnasium>=1.2.3", + "matplotlib>=3.10.8", + "numpy>=2.4.1", + "onnx>=1.20.1", + "onnxruntime>=1.23.2", + "onnxscript>=0.5.7", + "psutil>=7.2.1", + "stable-baselines3[extra]>=2.7.1", + "torch>=2.10.0", + "torchvision>=0.25.0", +] + +[build-system] +requires = ["uv_build>=0.9.24,<0.10.0"] +build-backend = "uv_build" diff --git a/scripts/lanch_one_simu.py b/src/simulation/scripts/lanch_one_simu.py similarity index 68% rename from scripts/lanch_one_simu.py rename to src/simulation/scripts/lanch_one_simu.py index dd39739a..8779f0b1 100644 --- a/scripts/lanch_one_simu.py +++ b/src/simulation/scripts/lanch_one_simu.py @@ -1,34 +1,35 @@ +raise NotImplementedError("This file is currently begin worked on") + import os import sys -from typing import * -import numpy as np import onnxruntime as ort -simu_path = __file__.rsplit('/', 2)[0] + '/src/Simulateur' -if simu_path not in sys.path: - sys.path.insert(0, simu_path) +from simulation import ( + VehicleEnv, +) +from simulation import config as c +from utils import onnx_utils -from onnx_utils import run_onnx_model -from config import * -from WebotsSimulationGymEnvironment import WebotsSimulationGymEnvironment -from TemporalResNetExtractor import TemporalResNetExtractor -from CNN1DResNetExtractor import CNN1DResNetExtractor # ------------------------------------------------------------------------- - # --- Chemin vers le fichier ONNX --- ONNX_MODEL_PATH = "model.onnx" + # --- Initialisation du moteur d'inférence ONNX Runtime (ORT) --- def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: if not os.path.exists(onnx_path): - raise FileNotFoundError(f"Le fichier ONNX est introuvable à : {onnx_path}. Veuillez l'exporter d'abord.") + raise FileNotFoundError( + f"Le fichier ONNX est introuvable à : {onnx_path}. Veuillez l'exporter d'abord." + ) # Crée la session d'inférence - return ort.InferenceSession(onnx_path) #On peut modifier le providers afin de mettre une CUDA + return ort.InferenceSession( + onnx_path + ) # On peut modifier le providers afin de mettre une CUDA if __name__ == "__main__": @@ -37,7 +38,6 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') - # 2. Initialisation de la session ONNX Runtime try: ort_session = init_onnx_runtime_session(ONNX_MODEL_PATH) @@ -48,11 +48,12 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: except FileNotFoundError as e: print(f"ERREUR : {e}") print( - "Veuillez vous assurer que vous avez exécuté une fois le script d'entraînement pour exporter 'model.onnx'.") + "Veuillez vous assurer que vous avez exécuté une fois le script d'entraînement pour exporter 'model.onnx'." + ) sys.exit(1) # 3. Boucle d'inférence (Test) - env = WebotsSimulationGymEnvironment(0,0) + env = VehicleEnv(0, 0) obs = env.reset() print("Début de la simulation en mode inférence...") @@ -60,8 +61,7 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: step_count = 0 while True: - - action = run_onnx_model(ort_session,obs) + action = onnx_utils.run_onnx_model(ort_session, obs) # 4. Exécuter l'action dans l'environnement obs, reward, done, info = env.step(action) @@ -74,8 +74,6 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: print(f"Épisode(s) terminé(s) après {step_count} étapes.") obs = env.reset() - - # Fermeture propre (très important pour les processus parallèles SubprocVecEnv) envs.close() - print("Simulation terminée. Environnements fermés.") \ No newline at end of file + print("Simulation terminée. Environnements fermés.") diff --git a/src/simulation/scripts/launch_train_multiprocessing.py b/src/simulation/scripts/launch_train_multiprocessing.py new file mode 100644 index 00000000..527baac8 --- /dev/null +++ b/src/simulation/scripts/launch_train_multiprocessing.py @@ -0,0 +1,107 @@ +import os +from logging import DEBUG +from pathlib import Path +from typing import Any, Dict + +import torch.nn as nn +from stable_baselines3 import PPO +from stable_baselines3.common.vec_env import SubprocVecEnv + +from extractors import ( # noqa: F401 + CNN1DExtractor, + CNN1DResNetExtractor, + TemporalResNetExtractor, +) +from simulation import VehicleEnv +from simulation import config as c +from utils import onnx_utils + +if __name__ == "__main__": + if not os.path.exists("/tmp/autotech/"): + os.mkdir("/tmp/autotech/") + + os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') + + envs = SubprocVecEnv( + [ + lambda sr=simulation_rank, vr=vehicle_rank: VehicleEnv(sr, vr) + for vehicle_rank in range(c.n_vehicles) + for simulation_rank in range(c.n_simulations) + ] + ) + + policy_kwargs: Dict[str, Any] = dict( + features_extractor_class=c.ExtractorClass, + # features_extractor_kwargs=dict( + # device=c.device, + # ), + activation_fn=nn.ReLU, + net_arch=[512, 512, 512], + ) + + ppo_args: Dict[str, Any] = dict( + n_steps=4096, + n_epochs=10, + batch_size=256, + learning_rate=3e-4, + gamma=0.99, + verbose=1, + normalize_advantage=True, + device=c.device, + ) + + save_path = ( + Path("~/.cache/autotech/checkpoints").expanduser() / c.ExtractorClass.__name__ + ) + + save_path.mkdir(parents=True, exist_ok=True) + + valid_files = [x for x in save_path.iterdir() if x.name.rstrip(".zip").isnumeric()] + + if valid_files: + model_path = max(valid_files, key=lambda x: int(x.name.rstrip(".zip"))) + print(f"Loading model {model_path.name}") + model = PPO.load(model_path, envs, **ppo_args, policy_kwargs=policy_kwargs) + i = int(model_path.name.rstrip(".zip")) + 1 + print(f"Model found, loading {model_path}") + + else: + model = PPO("MlpPolicy", envs, **ppo_args, policy_kwargs=policy_kwargs) + + i = 0 + print("Model not found, creating a new one") + + print("model hyper parameters:") + print(f"{model.learning_rate=}") + print(f"{model.gamma=}") + print(f"{model.verbose=}") + print(f"{model.n_steps=}") + print(f"{model.n_epochs=}") + print(f"{model.batch_size=}") + print(f"{model.device=}") + + while True: + onnx_utils.export_onnx( + model, + os.path.expanduser( + f"~/.cache/autotech/model_{c.ExtractorClass.__name__}.onnx" + ), + ) + onnx_utils.test_onnx(model) + + if c.LOG_LEVEL <= DEBUG: + from utils import PlotModelIO + + model.learn( + total_timesteps=500_000, + progress_bar=False, + callback=PlotModelIO(), + ) + else: + model.learn(total_timesteps=500_000, progress_bar=True) + + print("iteration over") + # TODO: we could just use a callback to save checkpoints or export the model to onnx + model.save(save_path / str(i)) + + i += 1 diff --git a/src/simulation/src/extractors/__init__.py b/src/simulation/src/extractors/__init__.py new file mode 100644 index 00000000..35d70f1f --- /dev/null +++ b/src/simulation/src/extractors/__init__.py @@ -0,0 +1,9 @@ +from .cnn_1d_extractor import CNN1DExtractor +from .cnn_1d_resnet_extractor import CNN1DResNetExtractor +from .temporal_resnet_extractor import TemporalResNetExtractor + +__all__ = [ + "CNN1DExtractor", + "CNN1DResNetExtractor", + "TemporalResNetExtractor", +] diff --git a/src/Simulateur/CNN1DExtractor.py b/src/simulation/src/extractors/cnn_1d_extractor.py similarity index 55% rename from src/Simulateur/CNN1DExtractor.py rename to src/simulation/src/extractors/cnn_1d_extractor.py index e45f8a79..c118f008 100644 --- a/src/Simulateur/CNN1DExtractor.py +++ b/src/simulation/src/extractors/cnn_1d_extractor.py @@ -1,63 +1,62 @@ import torch import torch.nn as nn from gymnasium import spaces - -from stable_baselines3 import PPO from stable_baselines3.common.torch_layers import BaseFeaturesExtractor class CNN1DExtractor(BaseFeaturesExtractor): - def __init__(self, space: spaces.Box, context_size: int, n_sensors: int, horizontal_resolution: int, device: str = "cpu"): - if context_size != 1: - raise ValueError("context_size must be 1 for CNN1DExtractor") - - # horizontal_resolution = 1080 - # n_sensors = 1 - self.n_sensors = n_sensors + context_size = 1 + lidar_horizontal_resolution = 1080 + camera_horizontal_resolution = 1080 + n_sensors = 1 + + # just an alias to avoid confusion because + # the lidar and camera have the same resolution + horizontal_resolution = 1080 + + def __init__( + self, + space: spaces.Box, + device: str = "cpu", + ): cnn = nn.Sequential( - # 1080 - - nn.Conv1d(2, 64, kernel_size=7, stride=2, padding=3, device=device), + # shape = [batch_size, 2, 1080] + nn.Conv1d(2, 64, kernel_size=7, stride=2, padding=3, device=device), nn.ReLU(), nn.MaxPool1d(3), nn.Dropout1d(0.2), - # 180 - + # shape = [batch_size, 64, 180] nn.Conv1d(64, 64, kernel_size=3, padding="same", device=device), nn.ReLU(), nn.MaxPool1d(3), nn.Dropout1d(0.3), - # 60 - + # shape = [batch_size, 64, 60] nn.Conv1d(64, 128, kernel_size=3, padding="same", device=device), nn.ReLU(), - nn.AvgPool1d(2), nn.Dropout1d(0.4), - # 30 - + # shape = [batch_size, 128, 30] nn.Conv1d(128, 128, kernel_size=3, padding="same", device=device), nn.ReLU(), nn.AvgPool1d(2), - # 15 - - nn.Flatten(-2, -1), + # shape = [batch_size, 128, 15] + nn.Flatten(), nn.Dropout(0.5), + # shape = [batch_size, 1920] ) # Compute shape by doing one forward pass with torch.no_grad(): n_flatten = cnn( - torch.zeros([2, horizontal_resolution], dtype=torch.float32, device=device) - ).shape[0] + torch.zeros([1, 2, self.horizontal_resolution], device=device) + ).shape[1] + super().__init__(space, n_flatten) # we cannot assign this directly to self.cnn before calling the super constructor self.net = cnn - def forward(self, observations: torch.Tensor) -> torch.Tensor: - # careful, this class takes as input the the sensor data AND the lidar data so is of size [batch_size, context_size, 1 + 1080 + 1080] - observations = observations[:, 0, self.n_sensors:].reshape(-1, 2, 1080) - # shape = [batch_size, 2, 1080] + # strip the context out + observations = observations[..., 0, :] return self.net(observations) diff --git a/src/Simulateur/CNN1DResNetExtractor.py b/src/simulation/src/extractors/cnn_1d_resnet_extractor.py similarity index 68% rename from src/Simulateur/CNN1DResNetExtractor.py rename to src/simulation/src/extractors/cnn_1d_resnet_extractor.py index 8830a5c8..469a1df4 100644 --- a/src/Simulateur/CNN1DResNetExtractor.py +++ b/src/simulation/src/extractors/cnn_1d_resnet_extractor.py @@ -1,11 +1,71 @@ import torch import torch.nn as nn -import torch.nn.functional as F from gymnasium import spaces - from stable_baselines3.common.torch_layers import BaseFeaturesExtractor +class CNN1DResNetExtractor(BaseFeaturesExtractor): + context_size = 1 + lidar_horizontal_resolution = 1024 + camera_horizontal_resolution = 1024 + n_sensors = 1 + + # just an alias to avoid confusion because + # the lidar and camera have the same resolution + horizontal_resolution = 1024 + + def __init__( + self, + space: spaces.Box, + device: str = "cpu", + ): + net = nn.Sequential( + # shape = [batch_size, 2, 1024] + Compressor(device), + # shape = [batch_size, 64, 256] + ResidualBlock(64, 64, device=device), + ResidualBlock(64, 64, device=device), + ResidualBlock(64, 64, downsample=True, device=device), + # shape = [batch_size, 128, 128] + ResidualBlock(64, 64, device=device), + ResidualBlock(64, 64, device=device), + ResidualBlock(64, 128, downsample=True, device=device), + # shape = [batch_size, 128, 64] + ResidualBlock(128, 128, device=device), + ResidualBlock(128, 128, device=device), + ResidualBlock(128, 128, downsample=True, device=device), + # shape = [batch_size, 256, 32] + ResidualBlock(128, 128, device=device), + ResidualBlock(128, 128, device=device), + ResidualBlock(128, 256, downsample=True, device=device), + # shape = [batch_size, 256, 16] + ResidualBlock(256, 256, device=device), + ResidualBlock(256, 256, device=device), + ResidualBlock(256, 256, downsample=True, device=device), + # shape = [batch_size, 256, 8] + nn.AvgPool1d(8), + # shape = [batch_size, 256, 1] + nn.Flatten(), + # shape = [batch_size, 256] + ) + + # Compute shape by doing one forward pass + with torch.no_grad(): + n_flatten = net( + torch.zeros( + [1, 2, self.context_size, self.horizontal_resolution], device=device + ) + ).shape[1] + + super().__init__(space, n_flatten) + + # we cannot assign this directly to self.cnn before calling the super constructor + self.net = net + + def forward(self, observations: torch.Tensor) -> torch.Tensor: + return self.net(observations) + + class Compressor(nn.Module): def __init__(self, device: str = "cpu"): super().__init__() @@ -16,36 +76,53 @@ def __init__(self, device: str = "cpu"): def forward(self, x: torch.Tensor) -> torch.Tensor: x = x[:, :, 0] - #print(x.shape, x.mean(dim=2), x[0, 0]) x = self.conv(x) x = self.dropout(x) x = self.pool(x) return x - class ResidualBlock(nn.Module): """ basic block with a residual connection """ - def __init__(self, in_channels: int, out_channels: int, downsample: bool = False, device: str = "cpu"): + def __init__( + self, + in_channels: int, + out_channels: int, + downsample: bool = False, + device: str = "cpu", + ): super().__init__() if downsample: stride = 2 - self.downsample = nn.Conv1d(in_channels, out_channels, kernel_size=1, stride=2, device=device) + self.downsample = nn.Conv1d( + in_channels, out_channels, kernel_size=1, stride=2, device=device + ) elif in_channels == out_channels: stride = 1 self.downsample = nn.Identity() else: stride = 1 - self.downsample = nn.Conv1d(in_channels, out_channels, kernel_size=1, stride=1, device=device) + self.downsample = nn.Conv1d( + in_channels, out_channels, kernel_size=1, stride=1, device=device + ) self.bn1 = nn.BatchNorm1d(in_channels, device=device) - self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, device=device) + self.conv1 = nn.Conv1d( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + device=device, + ) self.bn2 = nn.BatchNorm1d(out_channels, device=device) - self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size=3, padding=1, device=device) + self.conv2 = nn.Conv1d( + out_channels, out_channels, kernel_size=3, padding=1, device=device + ) self.relu = nn.ReLU(inplace=True) self.dropout = nn.Dropout1d(0.4) @@ -63,68 +140,3 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: y += self.downsample(x) return y - - - - -class CNN1DResNetExtractor(BaseFeaturesExtractor): - def __init__(self, space: spaces.Box, context_size: int, lidar_horizontal_resolution: int, camera_horizontal_resolution: int, device: str = "cpu"): - if (context_size, lidar_horizontal_resolution, camera_horizontal_resolution) != (1,) + 2*(1024,): - raise ValueError("context_size must be 1 and lidar_horizontal_resolution and camera_horizontal_resolution must be 1024 for TemporalResNetExtractor") - - self.lidar_horizontal_resolution = lidar_horizontal_resolution - self.camera_horizontal_resolution = camera_horizontal_resolution - - net = nn.Sequential( - # shape = [batch_size, 2, 1024] - Compressor(device), - # shape = [batch_size, 64, 256] - - ResidualBlock(64, 64, device=device), - ResidualBlock(64, 64, device=device), - ResidualBlock(64, 64, downsample=True, device=device), - # shape = [batch_size, 128, 128] - - ResidualBlock(64, 64, device=device), - ResidualBlock(64, 64, device=device), - ResidualBlock(64, 128, downsample=True, device=device), - # shape = [batch_size, 128, 64] - - ResidualBlock(128, 128, device=device), - ResidualBlock(128, 128, device=device), - ResidualBlock(128, 128, downsample=True, device=device), - # shape = [batch_size, 256, 32] - - ResidualBlock(128, 128, device=device), - ResidualBlock(128, 128, device=device), - ResidualBlock(128, 256, downsample=True, device=device), - # shape = [batch_size, 256, 16] - - ResidualBlock(256, 256, device=device), - ResidualBlock(256, 256, device=device), - ResidualBlock(256, 256, downsample=True, device=device), - # shape = [batch_size, 512, 8] - - nn.AvgPool1d(8), - # shape = [batch_size, 512, 1] - - nn.Flatten(), - # shape = [batch_size, 256] - ) - - # Compute shape by doing one forward pass - with torch.no_grad(): - n_flatten = net( - torch.zeros([1, 2, context_size, lidar_horizontal_resolution], dtype=torch.float32, device=device) - ).shape[1] - print("n_flatten: ", n_flatten) - super().__init__(space, n_flatten) - - # we cannot assign this directly to self.cnn before calling the super constructor - self.net = net - - - def forward(self, observations: torch.Tensor) -> torch.Tensor: - extracted = self.net(observations) - - return extracted diff --git a/src/Simulateur/TemporalResNetExtractor.py b/src/simulation/src/extractors/temporal_resnet_extractor.py similarity index 68% rename from src/Simulateur/TemporalResNetExtractor.py rename to src/simulation/src/extractors/temporal_resnet_extractor.py index 117fbb66..72573577 100644 --- a/src/Simulateur/TemporalResNetExtractor.py +++ b/src/simulation/src/extractors/temporal_resnet_extractor.py @@ -1,11 +1,63 @@ import torch import torch.nn as nn -import torch.nn.functional as F from gymnasium import spaces - from stable_baselines3.common.torch_layers import BaseFeaturesExtractor +class TemporalResNetExtractor(BaseFeaturesExtractor): + context_size = 128 + lidar_horizontal_resolution = 128 + camera_horizontal_resolution = 128 + n_sensors = 1 + + # just an alias to avoid confusion because + # the lidar and camera have the same resolution + horizontal_resolution = 128 + + def __init__( + self, + space: spaces.Box, + device: str = "cpu", + ): + net = nn.Sequential( + # shape = [batch_size, 2, 128, 128] + Compressor(device), + # shape = [batch_size, 64, 32, 32] + ResidualBlock(64, 64, device=device), + ResidualBlock(64, 64, device=device), + # shape = [batch_size, 64, 32, 32] + ResidualBlock(64, 128, downsample=True, device=device), + ResidualBlock(128, 128, device=device), + # shape = [batch_size, 128, 16, 16] + ResidualBlock(128, 256, downsample=True, device=device), + ResidualBlock(256, 256, device=device), + # shape = [batch_size, 256, 8, 8] + ResidualBlock(256, 512, downsample=True, device=device), + ResidualBlock(512, 512, device=device), + # shape = [batch_size, 512, 4, 4] + nn.AvgPool2d(4), + # shape = [batch_size, 512, 1, 1] + nn.Flatten(), + # shape = [batch_size, 512] + ) + + # Compute shape by doing one forward pass + with torch.no_grad(): + n_flatten = net( + torch.zeros( + [1, 2, self.context_size, self.horizontal_resolution], device=device + ) + ).shape[1] + + super().__init__(space, n_flatten) + + # we cannot assign this directly to self.net before calling the super constructor + self.net = net + + def forward(self, observations: torch.Tensor) -> torch.Tensor: + return self.net(observations) + + class Compressor(nn.Module): def __init__(self, device: str = "cpu"): super().__init__() @@ -25,30 +77,47 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: return x - - class ResidualBlock(nn.Module): """ basic block with a residual connection """ - def __init__(self, in_channels: int, out_channels: int, downsample: bool = False, device: str = "cpu"): + def __init__( + self, + in_channels: int, + out_channels: int, + downsample: bool = False, + device: str = "cpu", + ): super().__init__() if downsample: stride = 2 - self.downsample = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=2, device=device) + self.downsample = nn.Conv2d( + in_channels, out_channels, kernel_size=1, stride=2, device=device + ) elif in_channels == out_channels: stride = 1 self.downsample = nn.Identity() else: stride = 1 - self.downsample = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, device=device) + self.downsample = nn.Conv2d( + in_channels, out_channels, kernel_size=1, stride=1, device=device + ) self.bn1 = nn.BatchNorm2d(in_channels, device=device) - self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, device=device) + self.conv1 = nn.Conv2d( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + device=device, + ) self.bn2 = nn.BatchNorm2d(out_channels, device=device) - self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1, device=device) + self.conv2 = nn.Conv2d( + out_channels, out_channels, kernel_size=3, padding=1, device=device + ) self.relu = nn.ReLU(inplace=True) self.dropout = nn.Dropout2d(0.5) @@ -66,58 +135,3 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: y += self.downsample(x) return y - - - - -class TemporalResNetExtractor(BaseFeaturesExtractor): - def __init__(self, space: spaces.Box, context_size: int, lidar_horizontal_resolution: int, camera_horizontal_resolution: int, device: str = "cpu"): - if (context_size, lidar_horizontal_resolution, camera_horizontal_resolution) != 3*(128,): - raise ValueError("context_size must be 128 for TemporalResNetExtractor") - - self.lidar_horizontal_resolution = lidar_horizontal_resolution - self.camera_horizontal_resolution = camera_horizontal_resolution - - net = nn.Sequential( - Compressor(device), - # shape = [batch_size, 64, 32, 32] - - ResidualBlock(64, 64, device=device), - ResidualBlock(64, 64, device=device), - # shape = [batch_size, 64, 32, 32] - - ResidualBlock(64, 128, downsample=True, device=device), - ResidualBlock(128, 128, device=device), - # shape = [batch_size, 128, 16, 16] - - ResidualBlock(128, 256, downsample=True, device=device), - ResidualBlock(256, 256, device=device), - # shape = [batch_size, 256, 8, 8] - - ResidualBlock(256, 512, downsample=True, device=device), - ResidualBlock(512, 512, device=device), - # shape = [batch_size, 512, 4, 4] - - nn.AvgPool2d(4), - # shape = [batch_size, 512, 1, 1] - - nn.Flatten(), - # shape = [batch_size, 256] - ) - - # Compute shape by doing one forward pass - with torch.no_grad(): - n_flatten = net( - torch.zeros([1, 2, context_size, lidar_horizontal_resolution], device=device) - ).shape[1] - print("n_flatten: ", n_flatten) - super().__init__(space, n_flatten) - - # we cannot assign this directly to self.net before calling the super constructor - self.net = net - - - def forward(self, observations: torch.Tensor) -> torch.Tensor: - extracted = self.net(observations) - - return extracted diff --git a/src/simulation/src/simulation/__init__.py b/src/simulation/src/simulation/__init__.py new file mode 100644 index 00000000..7f8f7a6b --- /dev/null +++ b/src/simulation/src/simulation/__init__.py @@ -0,0 +1,3 @@ +from .vehicle_env import VehicleEnv + +__all__ = ["VehicleEnv"] diff --git a/src/simulation/src/simulation/config.py b/src/simulation/src/simulation/config.py new file mode 100644 index 00000000..1e8864db --- /dev/null +++ b/src/simulation/src/simulation/config.py @@ -0,0 +1,28 @@ +# just a file that lets us define some constants that are used in multiple files the simulation +import logging + +from torch.cuda import is_available + +from extractors import ( # noqa: F401 + CNN1DExtractor, + CNN1DResNetExtractor, + TemporalResNetExtractor, +) + +n_map = 2 +n_simulations = 1 +n_vehicles = 2 +n_stupid_vehicles = 0 +n_actions_steering = 16 +n_actions_speed = 16 +lidar_max_range = 12.0 +device = "cuda" if is_available() else "cpu" + +ExtractorClass = TemporalResNetExtractor +context_size = ExtractorClass.context_size +lidar_horizontal_resolution = ExtractorClass.lidar_horizontal_resolution +camera_horizontal_resolution = ExtractorClass.camera_horizontal_resolution +n_sensors = ExtractorClass.n_sensors + +LOG_LEVEL = logging.INFO +FORMATTER = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") diff --git a/src/simulation/src/simulation/vehicle_env.py b/src/simulation/src/simulation/vehicle_env.py new file mode 100644 index 00000000..9b4a3398 --- /dev/null +++ b/src/simulation/src/simulation/vehicle_env.py @@ -0,0 +1,168 @@ +import logging +import os +import subprocess +from typing import Dict, Tuple + +import gymnasium as gym +import numpy as np + +from webots import webots_path + +from . import config as c + +ObsType = np.ndarray # float32 array +ActType = np.ndarray # int64 array + + +class VehicleEnv(gym.Env): + """ + Gymnasium environment representing a single vehicle controlled inside + a Webots simulation. + + Creating an instance of this environment sets up all required IPC + mechanisms (named pipes) and may spawn a new Webots process, depending + on the vehicle index. + + Communication is organized as a closed control loop: + + VehicleEnv + | + | action + ▼ + controller_vehicle_driver + | + | observation + ▼ + controller_world_supervisor (shared by vehicle of the same simulation) + | + | observation, reward, done, truncated + ▼ + VehicleEnv + """ + + def __init__(self, simulation_rank: int, vehicle_rank: int): + super().__init__() + self.simulation_rank = simulation_rank + self.vehicle_rank = vehicle_rank + + self.handler = logging.FileHandler( + f"/tmp/autotech/Voiture_{self.simulation_rank}_{self.vehicle_rank}.log" + ) + self.handler.setFormatter(c.FORMATTER) + self.log = logging.getLogger( + f"SERVER_{self.simulation_rank}_{self.vehicle_rank}" + ) + self.log.setLevel(level=c.LOG_LEVEL) + self.log.addHandler(self.handler) + + self.log.info("Initialisation started") + + # this is only true if lidar_horizontal_resolution = camera_horizontal_resolution + box_min = np.zeros( + [2, c.context_size, c.lidar_horizontal_resolution], dtype=np.float32 + ) + box_max = ( + np.ones( + [2, c.context_size, c.lidar_horizontal_resolution], dtype=np.float32 + ) + * 30 + ) + + self.observation_space = gym.spaces.Box(box_min, box_max, dtype=np.float32) + self.action_space = gym.spaces.MultiDiscrete( + [c.n_actions_steering, c.n_actions_speed] + ) + + if not os.path.exists("/tmp/autotech"): + os.mkdir("/tmp/autotech") + + self.log.debug("Creation of the pipes") + + os.mkfifo(f"/tmp/autotech/{simulation_rank}_{vehicle_rank}toserver.pipe") + os.mkfifo(f"/tmp/autotech/serverto{simulation_rank}_{vehicle_rank}.pipe") + os.mkfifo(f"/tmp/autotech/{simulation_rank}_{vehicle_rank}tosupervisor.pipe") + + # --mode=fast --minimize --no-rendering --batch --stdout + if vehicle_rank == 0: + proc = subprocess.Popen( + [ + "webots", + f"{webots_path}/worlds/piste{simulation_rank % c.n_map}.wbt", + "--mode=fast", + "--minimize", + "--batch", + ] + ) + + with open("/tmp/autotech/simulationranks", "a") as f: + f.write(f"{proc.pid} {simulation_rank}_{vehicle_rank}\n") + + self.log.debug("Connection to the vehicle") + self.fifo_w = open( + f"/tmp/autotech/serverto{simulation_rank}_{vehicle_rank}.pipe", "wb" + ) + self.log.debug("Connection to the supervisor") + self.fifo_r = open( + f"/tmp/autotech/{simulation_rank}_{vehicle_rank}toserver.pipe", "rb" + ) + + self.log.info("Initialisation finished\n") + + def reset(self, seed: int | None = None, **options) -> Tuple[ObsType, Dict]: + # basically useless function + + # lidar data + # this is true for lidar_horizontal_resolution = camera_horizontal_resolution + self.context = obs = np.zeros( + [2, c.context_size, c.lidar_horizontal_resolution], dtype=np.float32 + ) + info = {} + self.log.info("reset finished\n") + return obs, info + + def step(self, action: ActType): + self.log.info("Starting step") + self.log.info(f"sending {action=}") + self.fifo_w.write(action.tobytes()) + self.fifo_w.flush() + + # communication with the supervisor + self.log.debug("trying to get info from supervisor") + cur_state = np.frombuffer( + self.fifo_r.read( + np.dtype(np.float32).itemsize + * ( + c.n_sensors + + c.lidar_horizontal_resolution + + c.camera_horizontal_resolution + ) + ), + dtype=np.float32, + ) + self.log.info(f"received {cur_state=}") + reward = np.frombuffer( + self.fifo_r.read(np.dtype(np.float32).itemsize), dtype=np.float32 + )[0] # scalar + self.log.info(f"received {reward=}") + done = np.frombuffer( + self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool + )[0] # scalar + self.log.info(f"received {done=}") + truncated = np.frombuffer( + self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool + )[0] # scalar + self.log.info(f"received {truncated=}") + info = {} + + cur_state = np.nan_to_num(cur_state[c.n_sensors :], nan=0.0, posinf=30.0) + + lidar_obs = cur_state[: c.lidar_horizontal_resolution] + camera_obs = cur_state[c.lidar_horizontal_resolution :] + + self.context = obs = np.concatenate( + [self.context[:, 1:], [lidar_obs[None], camera_obs[None]]], axis=1 + ) + + self.log.info("step over") + + return obs, reward, done, truncated, info diff --git a/src/simulation/src/utils/__init__.py b/src/simulation/src/utils/__init__.py new file mode 100644 index 00000000..4b7ee72c --- /dev/null +++ b/src/simulation/src/utils/__init__.py @@ -0,0 +1,3 @@ +from .plot_model_io import PlotModelIO + +__all__ = ["PlotModelIO"] diff --git a/src/simulation/src/utils/onnx_utils.py b/src/simulation/src/utils/onnx_utils.py new file mode 100644 index 00000000..9ff37840 --- /dev/null +++ b/src/simulation/src/utils/onnx_utils.py @@ -0,0 +1,80 @@ +import os + +import numpy as np +import onnxruntime as ort +import torch +import torch.nn as nn +from stable_baselines3.common.on_policy_algorithm import OnPolicyAlgorithm + +from simulation import config as c + + +def get_torch_model(sb_model): + return nn.Sequential( + sb_model.policy.features_extractor, + sb_model.policy.mlp_extractor.policy_net, + sb_model.policy.action_net, + ).to("cpu") + + +def export_onnx(sb_model: OnPolicyAlgorithm, path: str): + device = sb_model.policy.device + torch_model = get_torch_model(sb_model) + torch_model.eval() + + example_input = torch.randn(1, 2, c.context_size, c.lidar_horizontal_resolution) + + with torch.no_grad(): + torch.onnx.export( + torch_model, + (example_input,), + path, + input_names=["input"], + dynamo=True, + output_names=["output"], + dynamic_shapes={"input": {0: "batch_size"}}, + ) + + torch_model.to(device) + sb_model.policy.to(device) + sb_model.policy.train() + + +def run_onnx_model(session: ort.InferenceSession, x: np.ndarray): + return session.run(None, {"input": x})[0] + + +def test_onnx(model: OnPolicyAlgorithm): + device = model.policy.device + model.policy.eval() + true_model = get_torch_model(model) + + loss_fn = nn.MSELoss() + x = torch.randn(1000, 2, c.context_size, c.lidar_horizontal_resolution) + + try: + class_name = model.policy.features_extractor.__class__.__name__ + model_path = os.path.expanduser(f"~/.cache/autotech/model_{class_name}.onnx") + + session = ort.InferenceSession(model_path) + except Exception as e: + print(f"Error loading ONNX model: {e}") + return + + with torch.no_grad(): + y_true_test = true_model(x) + + true_model.train() + y_true_train = true_model(x) + true_model.eval() + + y_onnx = run_onnx_model(session, x.cpu().numpy()) + + loss_test = loss_fn(y_true_test, torch.tensor(y_onnx)) + loss_train = loss_fn(y_true_train, torch.tensor(y_onnx)) + print(f"onnx_test={loss_test}") + print(f"onnx_train={loss_train}") + + true_model.to(device) + model.policy.to(device) + model.policy.train() diff --git a/src/simulation/src/utils/plot_model_io.py b/src/simulation/src/utils/plot_model_io.py new file mode 100644 index 00000000..ac800fcf --- /dev/null +++ b/src/simulation/src/utils/plot_model_io.py @@ -0,0 +1,108 @@ +import matplotlib.pyplot as plt +import numpy as np +import torch +from stable_baselines3.common.callbacks import BaseCallback + +from simulation import config as c + +steering_true = (c.n_actions_steering + 1) // 2 + + +class PlotModelIO(BaseCallback): + """ + A stable_baselines3 callback used to plot live visualizations of model inputs and outputs. + """ + + def __init__(self, verbose=0): + super().__init__(verbose) + self.fig, self.ax = plt.subplots(4, 1, figsize=(10, 8)) + + # Steering bars + self.steering_bars = self.ax[0].bar( + range(c.n_actions_steering), np.zeros(c.n_actions_steering), color="blue" + ) + self.steering_avg = [ + self.ax[0].plot( + [0, 0], [0, 1], color=(i / 3, 1 - i / 3, 0), label="Average" + )[0] + for i in range(4) + ] + self.ax[0].set_ylim(0, 1) # Probabilities range from 0 to 1 + self.ax[0].set_title("Steering Action Probabilities") + + # Speed bars + self.speed_bars = self.ax[1].bar( + range(c.n_actions_speed), np.zeros(c.n_actions_speed), color="blue" + ) + self.speed_avg = self.ax[1].plot([0, 0], [0, 1], color="red", label="Average")[ + 0 + ] + self.ax[1].set_ylim(0, 1) # Probabilities range from 0 to 1 + self.ax[1].set_title("Speed Action Probabilities") + + # LiDAR img + self.lidar_img = self.ax[2].imshow( + np.zeros((c.lidar_horizontal_resolution, c.lidar_horizontal_resolution)), + cmap="gray", + vmin=0, + vmax=np.log(31), + ) + self.ax[2].set_title("LiDAR Image") + + # Camera img + self.camera_img = self.ax[3].imshow( + np.zeros( + (c.camera_horizontal_resolution, c.camera_horizontal_resolution, 3) + ), + cmap="RdYlGn", + vmin=-1, + vmax=1, + ) + self.ax[3].set_title("Camera Image") + + def _on_step(self) -> bool: + global steering_true + + obs = self.locals["obs_tensor"].clone().detach() + + self.lidar_img.set_array(np.log(1 + obs[0, 0, :, :].cpu().numpy())) + self.camera_img.set_array(obs[0, 1, :, :].cpu().numpy()) + with torch.no_grad(): + latent = self.model.policy.features_extractor(obs) + extracted = self.model.policy.mlp_extractor.policy_net(latent) + output = self.model.policy.action_net(extracted)[0] + + # Test different temperatures + T = [0.4, 0.6, 0.8, 1.0] + steer_probs = torch.softmax(output[: c.n_actions_steering], dim=-1).to( + "cpu" + ) + speed_probs = torch.softmax(output[c.n_actions_steering :], dim=-1).to( + "cpu" + ) + + # Update the probabilities + for i, bar in enumerate(self.steering_bars): + bar.set_height(steer_probs[i].item()) + + for i in range(4): + steer_probs_T = torch.softmax( + output[: c.n_actions_steering] / T[i], dim=-1 + ).to("cpu") + steering_avg = ( + (steer_probs_T / steer_probs_T.sum() * torch.arange(16)) + .sum() + .item() + ) + self.steering_avg[i].set_xdata([steering_avg, steering_avg]) + + for i, bar in enumerate(self.speed_bars): + bar.set_height(speed_probs[i].item()) + + speed_avg = (speed_probs * torch.arange(c.n_actions_speed)).sum().item() + self.speed_avg.set_xdata([speed_avg, speed_avg]) + + plt.draw() + plt.pause(1e-8) + + return True # Continue training diff --git a/src/simulation/src/utils/simulation_rank.py b/src/simulation/src/utils/simulation_rank.py new file mode 100644 index 00000000..fb6e9ef3 --- /dev/null +++ b/src/simulation/src/utils/simulation_rank.py @@ -0,0 +1,47 @@ +import os +import re + +import psutil + +import __main__ +from webots import webots_path + + +def get_simulation_rank(): + """ + This function has to be ran from a webots controller. + Get the simulation rank of the current python process. + """ + + # check if we are in a controller + if ( + result := re.match( + os.path.join( + webots_path, r"controllers/controller(\w+)/controller(\w+)\.py" + ), + __main__.__file__, + ) + ) is None or result.group(1) != result.group(2): + raise ValueError( + "get_simulation_rank has to be called inside a webots controller" + ) + + # webots precess launches webots-bin internally who is the process that launches the controllers + # that's why we need to get the pppid + proc = psutil.Process(os.getpid()) + parent = proc.parent() + assert parent is not None + grandparent = parent.parent() + assert grandparent is not None + pppid = str(grandparent.pid) + + # Simulation rank + with open("/tmp/autotech/simulationranks", "r") as f: + matches = re.search( + pppid + r" (\d+)", + f.read(), + re.MULTILINE, + ) + assert matches is not None + + return int(matches.group(1)) diff --git a/src/simulation/src/webots/__init__.py b/src/simulation/src/webots/__init__.py new file mode 100644 index 00000000..76248e7b --- /dev/null +++ b/src/simulation/src/webots/__init__.py @@ -0,0 +1,5 @@ +import os + +webots_path = os.path.dirname(__file__) + +__all__ = ["webots_path"] diff --git a/src/simulation/src/webots/controllers/controller_jaune/controller_jaune.py b/src/simulation/src/webots/controllers/controller_jaune/controller_jaune.py new file mode 100644 index 00000000..5b368729 --- /dev/null +++ b/src/simulation/src/webots/controllers/controller_jaune/controller_jaune.py @@ -0,0 +1,113 @@ +# Copyright 1996-2022 Cyberbotics Ltd. +# +# Control of the TT-02 car simulator CoVAPSy for Webots 2023b +# Inspired by vehicle_driver_altino controller +# Kévin Hoarau, Anthony Juton, Bastien Lhopitallier, Martin Raynaud +# August 2023 + +from controller import Lidar +from vehicle import Driver + +driver = Driver() + +basicTimeStep = int(driver.getBasicTimeStep()) +sensorTimeStep = 4 * basicTimeStep + +# Lidar +lidar = Lidar("Hokuyo") +lidar.enable(sensorTimeStep) +lidar.enablePointCloud() + +# clavier +keyboard = driver.getKeyboard() +keyboard.enable(sensorTimeStep) + +# speed in km/h +speed = 0 +maxSpeed = 28 # km/h + +# steering angle +angle = 0 +maxangle_degre = 16 + + +# reset speed and direction to zero +driver.setSteeringAngle(angle) +driver.setCruisingSpeed(speed) + +tableau_lidar_mm = [0.0] * 360 + + +def set_speed_m_s(speed_m_s): + speed = speed_m_s * 3.6 + if speed > maxSpeed: + speed = maxSpeed + if speed < 0: + speed = 0 + driver.setCruisingSpeed(speed) + + +def set_direction_degrees(angle_degrees): + if angle_degrees > maxangle_degre: + angle_degrees = maxangle_degre + elif angle_degrees < -maxangle_degre: + angle_degrees = -maxangle_degre + angle = -angle_degrees * 3.14 / 180 + driver.setSteeringAngle(angle) + + +def reverse(): # on the real car, there is a stop then a reverse for 1 second. + driver.setCruisingSpeed(-1) + + +# auto mode disabled +modeAuto = False +print("Click on the 3D view to start") +print("a for auto mode (no manual mode on TT02_jaune), n for stop") + +while driver.step() != -1: + while True: + # lidar data acquisition + # keyboard key retrieval + currentKey = keyboard.getKey() + + if currentKey == -1: + break + + elif currentKey == ord("n") or currentKey == ord("N"): + if modeAuto: + modeAuto = False + print("--------Auto Mode TT-02 Yellow Disabled-------") + elif currentKey == ord("a") or currentKey == ord("A"): + if not modeAuto: + modeAuto = True + print("------------Auto Mode TT-02 Yellow Enabled-----------------") + + # lidar data acquisition + raw_lidar_data = lidar.getRangeImage() + for i in range(360): + if (raw_lidar_data[-i] > 0) and (raw_lidar_data[-i] < 20): + tableau_lidar_mm[i] = 1000 * raw_lidar_data[-i] + else: + tableau_lidar_mm[i] = 0 + + if not modeAuto: + set_direction_degrees(0) + set_speed_m_s(0) + + if modeAuto: + ######################################################## + # Student program with + # - the tableau_lidar_mm array + # - the set_direction_degrees(...) function + # - the set_speed_m_s(...) function + # - the reverse() function + ####################################################### + + # one sector per 20° slice, so 10 sectors numbered 0 to 9 + angle_degre = 0.02 * (tableau_lidar_mm[60] - tableau_lidar_mm[-60]) + set_direction_degrees(angle_degre) + speed_m_s = 0.5 + set_speed_m_s(speed_m_s) + + ######################################################### diff --git a/src/simulation/src/webots/controllers/controller_vehicle_driver/controller_vehicle_driver.py b/src/simulation/src/webots/controllers/controller_vehicle_driver/controller_vehicle_driver.py new file mode 100644 index 00000000..6913ed6b --- /dev/null +++ b/src/simulation/src/webots/controllers/controller_vehicle_driver/controller_vehicle_driver.py @@ -0,0 +1,212 @@ +import logging +from enum import Enum, auto +from typing import Tuple, cast + +import numpy as np +from controller import Camera, Lidar, TouchSensor +from vehicle import Driver + +from simulation import config as c +from utils.simulation_rank import get_simulation_rank + + +def too_close(lidar, dir): + R = 0.83 + length = len(lidar) + straight = lidar[length // 2] + if dir: + nearest = min(lidar[length // 2 :]) + else: + nearest = min(lidar[: length // 2]) + + cos = nearest / straight + + # I don't know why sometimes this happens + if cos < -1 or cos > 1: + return True + + theta = np.arccos(cos) + L = R * (1 - np.sin(theta)) + return nearest < L + + +class State(Enum): + AI = auto() + BACK = auto() + + +class VehicleDriver(Driver): + """ + This class is a subclass of the Driver class and is used to control the vehicle. + It basically receives instructions from the controller_world_supervisor and follows them. + """ + + def __init__(self): + super().__init__() + self.state = State.AI + + basicTimeStep = int(self.getBasicTimeStep()) + self.sensorTime = basicTimeStep // 4 + + self.v_min = 1 + self.v_max = 9 + + self.vehicle_rank = int(self.getName().split("_")[-1]) + # Lidar + self.lidar = cast(Lidar, self.getDevice("Hokuyo")) + self.lidar.enable(self.sensorTime) + self.lidar.enablePointCloud() + + # Camera + self.camera = cast(Camera, self.getDevice("RASPI_Camera_V2")) + self.camera.enable(self.sensorTime) + + # Checkpoint sensor + self.touch_sensor = cast(TouchSensor, self.getDevice("touch_sensor")) + self.touch_sensor.enable(self.sensorTime) + + self.simulation_rank = get_simulation_rank() + + # Logger + self.handler = logging.FileHandler( + f"/tmp/autotech/Voiture_{self.simulation_rank}_{self.vehicle_rank}.log" + ) + self.handler.setFormatter(c.FORMATTER) + self.log = logging.getLogger( + f"CLIENT_{self.simulation_rank}_{self.vehicle_rank}" + ) + self.log.setLevel(level=c.LOG_LEVEL) + self.log.addHandler(self.handler) + + self.log.debug("Connection to the server") + self.fifo_r = open( + f"/tmp/autotech/serverto{self.simulation_rank}_{self.vehicle_rank}.pipe", + "rb", + ) + self.log.debug("Connection with the supervisor") + self.fifo_w = open( + f"/tmp/autotech/{self.simulation_rank}_{self.vehicle_rank}tosupervisor.pipe", + "wb", + ) + + # Check the state of the car + def observe(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + # try: + sensor_data = np.array([self.touch_sensor.getValue()], dtype=np.float32) + + lidar_data = np.array(self.lidar.getRangeImage(), dtype=np.float32) + + camera_data = np.array(self.camera.getImageArray(), dtype=np.float32) + # shape = (1080, 1, 3) + camera_data = camera_data.transpose(1, 2, 0)[0] + # shape = (3, 1080) + color = np.argmax(camera_data, axis=0) + camera_data = ( + (color == 0).astype(np.float32) * -1 + + (color == 1).astype(np.float32) * 1 + + (color == 2).astype(np.float32) * 0 + ) + # red -> -1 + # green -> 1 + # blue -> 0 + + return (sensor_data, lidar_data, camera_data) + + # except: + # # In case of no lidar return + # return ( + # np.array(self.touch_sensor.getValue(), dtype=np.float32), + # np.zeros(self.lidar.getNumberOfPoints(), dtype=np.float32), + # ) + + # Step function of the GYM environment + def step(self): + match self.state: + case State.AI: + self.ai() + case State.BACK: + # TODO: this is a very poor fix and not a definitive solution + # we HAVE to remove this line and properly manage the communication + # in State.BACK so that the ai doesn't train the data during that + # period to make sure that the training is not polluted + self.ai() + + self.back() + + return super().step() + + def ai(self): + self.log.info("Starting step") + obs = self.observe() + self.log.info(f"Observe {obs=}") + self.fifo_w.write(np.concatenate(obs).tobytes()) + self.fifo_w.flush() + + self.log.debug("Trying to read action from the server") + action = np.frombuffer( + self.fifo_r.read(np.dtype(np.int64).itemsize * 2), dtype=np.int64 + ) + self.log.info(f"received {action=}") + + # Simulation step + + action_steering = np.linspace( + -0.4, 0.4, c.n_actions_steering, dtype=np.float32 + )[action[0]] + + action_speed = np.linspace( + self.v_min, self.v_max, c.n_actions_speed, dtype=np.float32 + )[action[1]] + + cur_angle = self.getSteeringAngle() + dt = self.getBasicTimeStep() + omega = 20 # rad/s (max angular speed of the steering servo) + + action_steering = cur_angle + np.clip( + action_steering - cur_angle, -omega * dt, omega * dt + ) + + self.setSteeringAngle(float(action_steering)) + self.setCruisingSpeed(float(action_speed)) + + if self.touch_sensor.getValue(): + self.state = State.BACK + + def back(self): + # if wall on "dir": turn to "dir" and reverse until able to move forward (wall distance to verify) + lidar, cam = self.observe()[1:] + S = sum(cam) + dir = S > 0 + if dir: + self.setSteeringAngle(0.33) + if too_close(lidar, dir): + self.setCruisingSpeed(-2) + else: + self.state = State.AI + else: + self.setSteeringAngle(-0.33) + if too_close(lidar, dir): + self.setCruisingSpeed(-2) + else: + self.state = State.AI + + def run(self): + # this call is just there to make sure at least one step + # is done in the entire simulation before we call lidar.getRangeImage() + # otherwise it will crash the controller with the message: + # WARNING: 'controller_vehicle_driver' controller crashed. + # WARNING: controller_vehicle_driver: The process crashed some time after starting successfully. + super().step() + while self.step() != -1: + pass + + +# ----------------Main Program-------------------- +def main(): + driver = VehicleDriver() + driver.log.info("Starting the vehicle driver") + driver.run() + + +if __name__ == "__main__": + main() diff --git a/src/Simulateur/controllers/controller_violet/controller_violet.py b/src/simulation/src/webots/controllers/controller_violet/controller_violet.py similarity index 63% rename from src/Simulateur/controllers/controller_violet/controller_violet.py rename to src/simulation/src/webots/controllers/controller_violet/controller_violet.py index 171fd89e..5d36c648 100644 --- a/src/Simulateur/controllers/controller_violet/controller_violet.py +++ b/src/simulation/src/webots/controllers/controller_violet/controller_violet.py @@ -1,15 +1,17 @@ # Copyright 1996-2022 Cyberbotics Ltd. # -# Controle de la voiture TT-02 simulateur CoVAPSy pour Webots 2023b -# Inspiré de vehicle_driver_altino controller +# Control of the TT-02 car simulator CoVAPSy for Webots 2023b +# Inspired by vehicle_driver_altino controller # Kévin Hoarau, Anthony Juton, Bastien Lhopitallier, Martin Taynaud -# juillet 2023 +# July 2023 +from typing import cast + import numpy as np +from controller import Camera, Lidar +from controller.touch_sensor import TouchSensor from vehicle import Driver -from controller import Lidar - driver = Driver() @@ -20,26 +22,26 @@ lidar.enable(sensorTime) lidar.enablePointCloud() -camera = driver.getDevice("RASPI_Camera_V2") +camera = cast(Camera, driver.getDevice("RASPI_Camera_V2")) camera.enable(sensorTime) -touch_sensor = driver.getDevice("touch_sensor") +touch_sensor = cast(TouchSensor, driver.getDevice("touch_sensor")) touch_sensor.enable(sensorTime) -# vitesse en km/h +# speed in km/h speed = 0 -maxSpeed = 28 #km/h +maxSpeed = 28 # km/h -# angle de la direction +# steering angle angle = 0 -maxangle = 0.28 #rad (étrange, la voiture est défini pour une limite à 0.31 rad... +maxangle = 0.28 # rad (strange, the car is defined with a limit of 0.31 rad... -backwards_duration = 2000 # ms -stop_duration = 3000 # ms +backwards_duration = 2000 # ms +stop_duration = 3000 # ms death_count = 0 -# mise a zéro de la vitesse et de la direction +# reset speed and direction to zero driver.setSteeringAngle(angle) driver.setCruisingSpeed(speed) @@ -49,7 +51,7 @@ def backwards(lidar_data, camera_data): speed = -1 avg_color = np.mean(camera_data, axis=0) / 255 - angle = -0.5*avg_color[0] + 0.5*avg_color[1] + angle = -0.5 * avg_color[0] + 0.5 * avg_color[1] driver.setCruisingSpeed(speed) driver.setSteeringAngle(angle) @@ -63,17 +65,18 @@ def backwards(lidar_data, camera_data): driver.setSteeringAngle(angle) driver.step() + def stop(): driver.setCruisingSpeed(0) driver.setSteeringAngle(0) for _ in range(stop_duration // basicTimeStep): driver.step() - # will be reset by the controllerWorldSupervisor.py + # will be reset by the controller_world_supervisor.py while driver.step() != -1: - lidar_data = np.nan_to_num(lidar.getRangeImage(), nan=0., posinf=30.) - camera_data = np.nan_to_num(camera.getImageArray(), nan=0., posinf=30.).squeeze() + lidar_data = np.nan_to_num(lidar.getRangeImage(), nan=0.0, posinf=30.0) + camera_data = np.nan_to_num(camera.getImageArray(), nan=0.0, posinf=30.0).squeeze() sensor_data = touch_sensor.getValue() # goes backwards @@ -87,17 +90,16 @@ def stop(): death_count = 0 stop() - - speed = 1 #km/h - #l'angle de la direction est la différence entre les mesures des rayons + speed = 1 # km/h + # the steering angle is the difference between ray measurements avg_color = np.mean(camera_data, axis=0) / 255 i = np.argmin(lidar_data) m = lidar_data[i] - if m <= .7: + if m <= 0.7: angle = 0.3 if i <= 64 else -0.3 else: - angle = 0.3*avg_color[0] - 0.3*avg_color[1] + angle = 0.3 * avg_color[0] - 0.3 * avg_color[1] driver.setCruisingSpeed(speed) driver.setSteeringAngle(angle) diff --git a/src/Simulateur/controllers/controllerWorldInit/controllerWorldInit.py b/src/simulation/src/webots/controllers/controller_world_init/controller_world_init.py similarity index 52% rename from src/Simulateur/controllers/controllerWorldInit/controllerWorldInit.py rename to src/simulation/src/webots/controllers/controller_world_init/controller_world_init.py index d249ad4e..003e3adc 100644 --- a/src/Simulateur/controllers/controllerWorldInit/controllerWorldInit.py +++ b/src/simulation/src/webots/controllers/controller_world_init/controller_world_init.py @@ -1,27 +1,16 @@ -from typing import Tuple -import numpy as np from controller import Supervisor -import os -import sys +from simulation import config as c -# this is necessary because we are not in a package context -# so we cannot do from ..config import * -# SO EVEN IF IT LOOKS UGLY, WE HAVE TO DO THIS -script_dir = os.path.dirname(os.path.abspath(__file__)) -controllers_path = os.path.join(script_dir, '../..') -sys.path.append(controllers_path) -from config import * - - - -def create_nodes(supervisor: Supervisor, n_vehicles: int): +def create_nodes(): """ Creates n_vehicles vehicles in the simulation for each vehicle, create an emitter and a receiver in the supervisor """ + supervisor = Supervisor() + root = supervisor.getRoot() root_children_field = root.getField("children") @@ -29,41 +18,39 @@ def create_nodes(supervisor: Supervisor, n_vehicles: int): DEF WorldSupervisor Robot {{ supervisor TRUE name "WorldSupervisor" - controller "controllerWorldSupervisor" + controller "controller_world_supervisor" children [ - {"\n".join([f' Emitter {{name "supervisor_emitter_{i}"}}' for i in range(n_vehicles)])} - {"\n".join([f' Receiver {{name "supervisor_receiver_{i}"}}' for i in range(n_vehicles)])} + {"\n".join([f' Emitter {{name "supervisor_emitter_{i}"}}' for i in range(c.n_vehicles)])} + {"\n".join([f' Receiver {{name "supervisor_receiver_{i}"}}' for i in range(c.n_vehicles)])} ] }} """ root_children_field.importMFNodeFromString(-1, proto_string) - for i in range(n_vehicles): + for i in range(c.n_vehicles): proto_string = f""" DEF TT02_{i} TT02_2023b {{ name "TT02_{i}" - controller "controllerVehicleDriver" + controller "controller_vehicle_driver" color 0.0 0.0 1.0 - lidar_horizontal_resolution {lidar_horizontal_resolution} - camera_horizontal_resolution {camera_horizontal_resolution} + lidar_horizontal_resolution {c.lidar_horizontal_resolution} + camera_horizontal_resolution {c.camera_horizontal_resolution} }} """ root_children_field.importMFNodeFromString(-1, proto_string) - - for i in range(n_vehicles, n_vehicles + n_stupid_vehicles): + for i in range(c.n_vehicles, c.n_vehicles + c.n_stupid_vehicles): proto_string = f""" DEF TT02_{i} TT02_2023b {{ name "TT02_{i}" controller "controller_violet" color 0.0 0.0 0.0 - lidar_horizontal_resolution {lidar_horizontal_resolution} - camera_horizontal_resolution {camera_horizontal_resolution} + lidar_horizontal_resolution {c.lidar_horizontal_resolution} + camera_horizontal_resolution {c.camera_horizontal_resolution} }} """ root_children_field.importMFNodeFromString(-1, proto_string) if __name__ == "__main__": - S = Supervisor() - create_nodes(S, n_vehicles) + create_nodes() diff --git a/src/simulation/src/webots/controllers/controller_world_supervisor/__init__.py b/src/simulation/src/webots/controllers/controller_world_supervisor/__init__.py new file mode 100644 index 00000000..933f966e --- /dev/null +++ b/src/simulation/src/webots/controllers/controller_world_supervisor/__init__.py @@ -0,0 +1,4 @@ +from .checkpoint import Checkpoint +from .checkpoint_manager import CheckpointManager, checkpoints + +__all__ = ["Checkpoint", "CheckpointManager", "checkpoints"] diff --git a/src/Simulateur/controllers/controllerWorldSupervisor/checkpoint.py b/src/simulation/src/webots/controllers/controller_world_supervisor/checkpoint.py similarity index 84% rename from src/Simulateur/controllers/controllerWorldSupervisor/checkpoint.py rename to src/simulation/src/webots/controllers/controller_world_supervisor/checkpoint.py index 732925fa..21232e32 100644 --- a/src/Simulateur/controllers/controllerWorldSupervisor/checkpoint.py +++ b/src/simulation/src/webots/controllers/controller_world_supervisor/checkpoint.py @@ -1,5 +1,9 @@ +from logging import DEBUG + import numpy as np +from simulation import config as c + class Checkpoint: def __init__(self, theta, x0, y0): @@ -17,10 +21,11 @@ def create_vector_2d(self, supervisor): length = 0.5 # debug flag to generate the cylinder and cone shapes - DEBUG = False - if DEBUG: + if c.LOG_LEVEL <= DEBUG: # Cylinder (shaft) - supervisor.getRoot().getField("children").importMFNodeFromString(-1, f""" + supervisor.getRoot().getField("children").importMFNodeFromString( + -1, + f""" Transform {{ translation {self.x0} {self.y0} 0 rotation 0 1 0 {-np.pi / 2} @@ -43,12 +48,15 @@ def create_vector_2d(self, supervisor): }} ] }} - """) + """, + ) # Cone (arrowhead) - supervisor.getRoot().getField("children").importMFNodeFromString(-1, f""" + supervisor.getRoot().getField("children").importMFNodeFromString( + -1, + f""" Transform {{ - translation {self.x0 + np.cos(self.theta)*length/2} {self.y0 + np.sin(self.theta)*length/2} 0 + translation {self.x0 + np.cos(self.theta) * length / 2} {self.y0 + np.sin(self.theta) * length / 2} 0 rotation 0 1 0 {-np.pi / 2} children [ Transform {{ @@ -69,7 +77,8 @@ def create_vector_2d(self, supervisor): }} ] }} - """) + """, + ) def check_plane(self, x, y): """ @@ -79,4 +88,6 @@ def check_plane(self, x, y): which is equivalent to checking u . (x, y) >= 0 but with an offset of (x0, y0) where u is a vector orthogonal to the hyperplane """ - return np.cos(self.theta)*(x - self.x0) + np.sin(self.theta)*(y - self.y0) >= 0 + return ( + np.cos(self.theta) * (x - self.x0) + np.sin(self.theta) * (y - self.y0) >= 0 + ) diff --git a/src/Simulateur/controllers/controllerWorldSupervisor/checkpointmanager.py b/src/simulation/src/webots/controllers/controller_world_supervisor/checkpoint_manager.py similarity index 95% rename from src/Simulateur/controllers/controllerWorldSupervisor/checkpointmanager.py rename to src/simulation/src/webots/controllers/controller_world_supervisor/checkpoint_manager.py index 350091a6..c5d8e25d 100644 --- a/src/Simulateur/controllers/controllerWorldSupervisor/checkpointmanager.py +++ b/src/simulation/src/webots/controllers/controller_world_supervisor/checkpoint_manager.py @@ -1,13 +1,21 @@ +import os from typing import List -from checkpoint import Checkpoint + import numpy as np from controller import Supervisor -import os + +from . import Checkpoint np.random.seed(os.getpid()) + class CheckpointManager: - def __init__(self, supervisor: Supervisor, checkpoints: List[Checkpoint], next_checkpoint=None): + def __init__( + self, + supervisor: Supervisor, + checkpoints: List[Checkpoint], + next_checkpoint=None, + ): self.supervisor = supervisor self.checkpoints = checkpoints self.reset(next_checkpoint) @@ -20,7 +28,6 @@ def update(self, x=None, y=None): """ # if x is None then y is None because of default value rules in python - if x is None or self.checkpoints[self.next_checkpoint].check_plane(x, y): self.next_checkpoint = (self.next_checkpoint + 1) % len(self.checkpoints) return True @@ -53,7 +60,7 @@ def reset(self, i=None): checkpoints = [ - [ # piste0.wbt + [ # piste0.wbt Checkpoint(0, -0.314494, -2.47211), Checkpoint(0, 1.11162, -2.56708), Checkpoint(0.8, 2.54552, -2.27446), @@ -86,7 +93,7 @@ def reset(self, i=None): Checkpoint(-0.3, -2.89371, -2.49154), Checkpoint(0, -2.01029, -2.51669), ], - [ # piste1.wbt + [ # piste1.wbt Checkpoint(1.57, 5.52, -2.25), Checkpoint(1.57, 5.52, -1.25), Checkpoint(1.57, 5.52, -0.25), @@ -144,9 +151,7 @@ def reset(self, i=None): Checkpoint(1.05, 5.43, -5.00), Checkpoint(1.57, 5.52, -3.25), Checkpoint(1.57, 5.52, -4.25), - ], - [ # piste2.wbt - - ] + [ # piste2.wbt + ], ] diff --git a/src/Simulateur/controllers/controllerWorldSupervisor/controllerWorldSupervisor.py b/src/simulation/src/webots/controllers/controller_world_supervisor/controller_world_supervisor.py similarity index 52% rename from src/Simulateur/controllers/controllerWorldSupervisor/controllerWorldSupervisor.py rename to src/simulation/src/webots/controllers/controller_world_supervisor/controller_world_supervisor.py index 953e0b1c..86473007 100644 --- a/src/Simulateur/controllers/controllerWorldSupervisor/controllerWorldSupervisor.py +++ b/src/simulation/src/webots/controllers/controller_world_supervisor/controller_world_supervisor.py @@ -1,26 +1,18 @@ -import re -from typing import * -import numpy as np -import gymnasium as gym - -from checkpointmanager import CheckpointManager, checkpoints +import logging +import numpy as np from controller import Supervisor -supervisor = Supervisor() -import torch.nn as nn -import psutil -import os -import sys +from simulation import config as c +from utils.simulation_rank import get_simulation_rank -# this is necessary because we are not in a package context -# so we cannot do from ..config import * -# SO EVEN IF IT LOOKS UGLY, WE HAVE TO DO THIS -script_dir = os.path.dirname(os.path.abspath(__file__)) -controllers_path = os.path.join(script_dir, '../..') -sys.path.append(controllers_path) +# I cannot use relative imports because the file is directly launched by webots +from webots.controllers.controller_world_supervisor import ( + CheckpointManager, + checkpoints, +) -from config import * +supervisor = Supervisor() checkpoint_positions = [ [-0.314494, -2.47211, 0.0391], @@ -56,25 +48,7 @@ [-2.01029, -2.51669, 0.0391], ] -# webots precess launches webots-bin internally who is the process that launches the controllers -# that's why we need to get the pppid -proc = psutil.Process(os.getpid()) #current -parent = proc.parent() #parent -grandparent = parent.parent() if parent else None #grandparent -pppid = str(grandparent.pid) - - -simulation_rank = int( - re.search( - pppid + r" (\d+)", - open("/tmp/autotech/simulationranks", "r").read(), - re.MULTILINE - ).group(1) -) - - -print(f"CLIENT ?{pppid}? {simulation_rank=}") - +simulation_rank = get_simulation_rank() class WebotsVehicleManager: @@ -87,58 +61,68 @@ class WebotsVehicleManager: def __init__(self, vehicle_rank: int): self.vehicle_rank = vehicle_rank - self.checkpoint_manager = CheckpointManager(supervisor, checkpoints[simulation_rank % n_map], vehicle_rank) + self.checkpoint_manager = CheckpointManager( + supervisor, checkpoints[simulation_rank % c.n_map], vehicle_rank + ) - self.v_min = 1 #np.random.rand()*2 + 0.5 # 0.5 to 2.5 - self.v_max = 9 #np.random.rand()*5 + 4 # 5 to 10 + self.v_min = 1 + self.v_max = 9 basicTimeStep = int(supervisor.getBasicTimeStep()) self.sensorTime = basicTimeStep // 4 # negative value so that the first reset is not skipped self.last_reset = -1e6 - proc = psutil.Process(os.getpid()) #current - parent = proc.parent() #parent - grandparent = parent.parent() if parent else None #grandparent - pppid = str(grandparent.pid) - + self.simulation_rank = get_simulation_rank() - self.simulation_rank = int( - re.search( - pppid + r" (\d+)", - open("/tmp/autotech/simulationranks", "r").read(), - re.MULTILINE - ).group(1) + self.handler = logging.FileHandler( + f"/tmp/autotech/Voiture_{self.simulation_rank}_{self.vehicle_rank}.log" ) - - self.handler = logging.FileHandler(f"/tmp/autotech/Voiture_{self.simulation_rank}_{self.vehicle_rank}.log") - self.handler.setFormatter(FORMATTER) - self.log = logging.getLogger("SUPERVISOR") - self.log.setLevel(level=LOG_LEVEL) + self.handler.setFormatter(c.FORMATTER) + self.log = logging.getLogger( + f"SUPERVISOR_{self.simulation_rank}_{self.vehicle_rank}" + ) + self.log.setLevel(level=c.LOG_LEVEL) self.log.addHandler(self.handler) - self.log.debug("Connection to the vehicle") - self.fifo_r = open(f"/tmp/autotech/{self.simulation_rank}_{self.vehicle_rank}tosupervisor.pipe", "rb") + self.fifo_r = open( + f"/tmp/autotech/{self.simulation_rank}_{self.vehicle_rank}tosupervisor.pipe", + "rb", + ) self.log.debug("Connection to the server") - self.fifo_w = open(f"/tmp/autotech/{simulation_rank}_{vehicle_rank}toserver.pipe", "wb") - + self.fifo_w = open( + f"/tmp/autotech/{simulation_rank}_{vehicle_rank}toserver.pipe", "wb" + ) - self.translation_field = supervisor.getFromDef(f"TT02_{self.vehicle_rank}").getField("translation") # may cause access issues ... - self.rotation_field = supervisor.getFromDef(f"TT02_{self.vehicle_rank}").getField("rotation") # may cause access issues ... + self.translation_field = supervisor.getFromDef( + f"TT02_{self.vehicle_rank}" + ).getField("translation") # may cause access issues ... + self.rotation_field = supervisor.getFromDef( + f"TT02_{self.vehicle_rank}" + ).getField("rotation") # may cause access issues ... # returns the lidar data of all vehicles def observe(self): # gets from Vehicle self.log.debug("trying to observe") - obs = np.frombuffer(self.fifo_r.read(np.dtype(np.float32).itemsize * (n_sensors + lidar_horizontal_resolution + camera_horizontal_resolution)), dtype=np.float32) - self.log.info(f"observing {obs=}") + obs = np.frombuffer( + self.fifo_r.read( + np.dtype(np.float32).itemsize + * ( + c.n_sensors + + c.lidar_horizontal_resolution + + c.camera_horizontal_resolution + ) + ), + dtype=np.float32, + ) + self.log.debug(f"observing {obs=}") return obs - # reset the gym environment reset def reset(self, seed=None): - self.log.debug("trying to reset vehicle") + self.log.info("reseting vehicle") # this has to be done otherwise thec cars will shiver for a while sometimes when respawning and idk why if supervisor.getTime() - self.last_reset >= 1e-1: self.log.debug("getting info from vehicle") @@ -156,39 +140,39 @@ def reset(self, seed=None): vehicle.resetPhysics() self.log.info("vehicle reset done") - - obs = np.zeros(n_sensors + lidar_horizontal_resolution + camera_horizontal_resolution, dtype=np.float32) + obs = np.zeros( + c.n_sensors + + c.lidar_horizontal_resolution + + c.camera_horizontal_resolution, + dtype=np.float32, + ) info = {} return obs, info # step function of the gym environment def step(self): - #action_steering = np.linspace(-.4, .4, n_actions_steering, dtype=np.float32)[action[0], None] - #action_speed = np.linspace(self.v_min, self.v_max, n_actions_speed, dtype=np.float32)[action[1], None] - # we should add a beacon sensor pointing upwards to detect the beacon self.log.debug("getting observation") obs = self.observe() self.log.info(f"observed {obs=}") - sensor_data = obs[:n_sensors] + sensor_data = obs[: c.n_sensors] reward = 0 done = np.False_ truncated = np.False_ x, y, z = self.translation_field.getSFVec3f() b_past_checkpoint = self.checkpoint_manager.update(x, y) - b_collided, = sensor_data # unpack sensor data + (b_collided,) = sensor_data # unpack sensor data - if (z < -10): - #print(f"CLIENT{simulation_rank}/{self.vehicle_rank} : {b_collided=}, {z=}") + if z < -10: reward = np.float32(0.0) done = np.True_ elif b_collided: reward = np.float32(-0.5) done = np.False_ elif b_past_checkpoint: - reward = np.float32(1.0) #* np.cos(self.checkpoint_manager.getAngle() - self.rotation_field.getSFRotation()[3], dtype=np.float32) + reward = np.float32(1.0) done = np.False_ else: reward = np.float32(0.05) @@ -198,36 +182,27 @@ def step(self): def main(): - - envs = [WebotsVehicleManager(i) for i in range(n_vehicles)] - print("CLIENT ALL : envs created") - # check_env(env) - - logdir = "./Webots_tb/" + envs = [WebotsVehicleManager(i) for i in range(c.n_vehicles)] supervisor.step() - + for i, e in enumerate(envs): - e.log.debug("CLIENT : reset") e.reset(i) - for i in range(n_vehicles, n_vehicles + n_stupid_vehicles): + for i in range(c.n_vehicles, c.n_vehicles + c.n_stupid_vehicles): ( - supervisor - .getFromDef(f"TT02_{i}") + supervisor.getFromDef(f"TT02_{i}") .getField("translation") .setSFVec3f(checkpoint_positions[i]) ) - last_moved = [0 for _ in range(n_stupid_vehicles)] + last_moved = np.zeros(c.n_stupid_vehicles) while supervisor.step() != -1: - #print("CLIENT ALL : begin step") - #Prédiction pour séléctionner une action à partir de l"observation for e in envs: obs, reward, done, truncated, info = e.step() - if done: - obs, info = e.reset() + if done: + obs, info = e.reset() e.log.info(f"sending {obs=}") e.fifo_w.write(obs.tobytes()) @@ -237,25 +212,25 @@ def main(): e.fifo_w.write(done.tobytes()) e.log.info(f"sending {truncated=}") e.fifo_w.write(truncated.tobytes()) - e.fifo_w.flush() + e.fifo_w.flush() - - for i in range(n_stupid_vehicles): - tr_field = supervisor.getFromDef(f"TT02_{n_vehicles + i}").getField("translation") + for i in range(c.n_stupid_vehicles): + tr_field = supervisor.getFromDef(f"TT02_{c.n_vehicles + i}").getField( + "translation" + ) speed = np.linalg.norm(tr_field.getSFVec3f()) if speed >= 0.1: last_moved[i] = supervisor.getTime() else: - print("DIDNT MOVE") + envs[i].log.debug("did not move") if supervisor.getTime() - last_moved[i]: - print("DIDNT MOVE FOR 1s") + envs[i].log.info( + "resetting position because did not move for more than 1s" + ) tr_field.setSFVec3f(checkpoint_positions[0]) - - if __name__ == "__main__": - print("BEGINS MAIN FROM CONTROLLER WORLD SUPERVISOR", flush=True) main() diff --git a/src/Simulateur/protos/.~lock.TT02.proto# b/src/simulation/src/webots/protos/.~lock.TT02.proto# similarity index 100% rename from src/Simulateur/protos/.~lock.TT02.proto# rename to src/simulation/src/webots/protos/.~lock.TT02.proto# diff --git a/src/Simulateur/protos/Carrosserie_Audi_Light.stl b/src/simulation/src/webots/protos/Carrosserie_Audi_Light.stl similarity index 100% rename from src/Simulateur/protos/Carrosserie_Audi_Light.stl rename to src/simulation/src/webots/protos/Carrosserie_Audi_Light.stl diff --git a/src/Simulateur/protos/Carrosserie_BMW_light.stl b/src/simulation/src/webots/protos/Carrosserie_BMW_light.stl similarity index 100% rename from src/Simulateur/protos/Carrosserie_BMW_light.stl rename to src/simulation/src/webots/protos/Carrosserie_BMW_light.stl diff --git a/src/Simulateur/protos/ChevroletCamaroLight.stl b/src/simulation/src/webots/protos/ChevroletCamaroLight.stl similarity index 100% rename from src/Simulateur/protos/ChevroletCamaroLight.stl rename to src/simulation/src/webots/protos/ChevroletCamaroLight.stl diff --git a/src/Simulateur/protos/Hokuyo.proto b/src/simulation/src/webots/protos/Hokuyo.proto similarity index 100% rename from src/Simulateur/protos/Hokuyo.proto rename to src/simulation/src/webots/protos/Hokuyo.proto diff --git a/src/Simulateur/protos/TT02Wheel.proto b/src/simulation/src/webots/protos/TT02Wheel.proto similarity index 100% rename from src/Simulateur/protos/TT02Wheel.proto rename to src/simulation/src/webots/protos/TT02Wheel.proto diff --git a/src/Simulateur/protos/TT02_2023b.proto b/src/simulation/src/webots/protos/TT02_2023b.proto similarity index 100% rename from src/Simulateur/protos/TT02_2023b.proto rename to src/simulation/src/webots/protos/TT02_2023b.proto diff --git a/src/Simulateur/protos/icons/Altino.png b/src/simulation/src/webots/protos/icons/Altino.png similarity index 100% rename from src/Simulateur/protos/icons/Altino.png rename to src/simulation/src/webots/protos/icons/Altino.png diff --git a/src/Simulateur/protos/icons/Car.png b/src/simulation/src/webots/protos/icons/Car.png similarity index 100% rename from src/Simulateur/protos/icons/Car.png rename to src/simulation/src/webots/protos/icons/Car.png diff --git a/src/Simulateur/protos/icons/RpLidarA2.png b/src/simulation/src/webots/protos/icons/RpLidarA2.png similarity index 100% rename from src/Simulateur/protos/icons/RpLidarA2.png rename to src/simulation/src/webots/protos/icons/RpLidarA2.png diff --git a/src/Simulateur/worlds/ImageToStl.com_virage.obj b/src/simulation/src/webots/worlds/ImageToStl.com_virage.obj similarity index 100% rename from src/Simulateur/worlds/ImageToStl.com_virage.obj rename to src/simulation/src/webots/worlds/ImageToStl.com_virage.obj diff --git a/src/Simulateur/worlds/[DEPRECATED]piste.wbt b/src/simulation/src/webots/worlds/[DEPRECATED]piste.wbt similarity index 99% rename from src/Simulateur/worlds/[DEPRECATED]piste.wbt rename to src/simulation/src/webots/worlds/[DEPRECATED]piste.wbt index a3545c45..dc8c4b5d 100644 --- a/src/Simulateur/worlds/[DEPRECATED]piste.wbt +++ b/src/simulation/src/webots/worlds/[DEPRECATED]piste.wbt @@ -235,8 +235,6 @@ Wall { DEF WorldSupervisor Robot { supervisor TRUE name "WorldSupervisor" - controller "controllerWorldSupervisor" + controller "controller_world_supervisor" children [] } - - diff --git a/src/Simulateur/worlds/piste0.wbt b/src/simulation/src/webots/worlds/piste0.wbt similarity index 99% rename from src/Simulateur/worlds/piste0.wbt rename to src/simulation/src/webots/worlds/piste0.wbt index fa3bd602..e3a4e638 100644 --- a/src/Simulateur/worlds/piste0.wbt +++ b/src/simulation/src/webots/worlds/piste0.wbt @@ -2230,7 +2230,7 @@ Solid { DEF WorldInit Robot { supervisor TRUE name "WorldInit" - controller "controllerWorldInit" + controller "controller_world_init" children [ ] } diff --git a/src/Simulateur/worlds/piste1.wbt b/src/simulation/src/webots/worlds/piste1.wbt similarity index 99% rename from src/Simulateur/worlds/piste1.wbt rename to src/simulation/src/webots/worlds/piste1.wbt index 4f320a44..30782538 100644 --- a/src/Simulateur/worlds/piste1.wbt +++ b/src/simulation/src/webots/worlds/piste1.wbt @@ -3166,7 +3166,7 @@ Solid { DEF WorldInit Robot { supervisor TRUE name "WorldInit" - controller "controllerWorldInit" + controller "controller_world_init" children [ ] } diff --git a/src/Simulateur/worlds/piste2.wbt b/src/simulation/src/webots/worlds/piste2.wbt similarity index 100% rename from src/Simulateur/worlds/piste2.wbt rename to src/simulation/src/webots/worlds/piste2.wbt diff --git a/uv.lock b/uv.lock index 3e88c510..7effdcbb 100644 --- a/uv.lock +++ b/uv.lock @@ -2,8 +2,28 @@ version = 1 revision = 3 requires-python = ">=3.12, <3.14" resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version < '3.13'", + "python_full_version >= '3.13' and sys_platform == 'win32'", + "python_full_version >= '3.13' and sys_platform == 'emscripten'", + "python_full_version >= '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.13' and sys_platform == 'win32'", + "python_full_version < '3.13' and sys_platform == 'emscripten'", + "python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[manifest] +members = [ + "autotech", + "high-level", + "simulation", +] + +[[package]] +name = "absl-py" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543, upload-time = "2026-01-28T10:17:05.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750, upload-time = "2026-01-28T10:17:04.19Z" }, ] [[package]] @@ -89,6 +109,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/22/0b5248484832260612a5d6a71adc2efa92d1efb87d07d05a7ff5761db9e0/adafruit_circuitpython_vl53l0x-3.6.17-py3-none-any.whl", hash = "sha256:03fabd87f912695acaf56fdde7bbc4c4426306af1c6b531a3e96771deaa418d8", size = 9779, upload-time = "2025-10-20T20:34:15.53Z" }, ] +[[package]] +name = "adafruit-circuitpython-vl53l1x" +version = "1.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "adafruit-blinka" }, + { name = "adafruit-circuitpython-busdevice" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/e2/02e75dfeec38982240e179aa02681cba1331172a1ca7fee644b97afa6013/adafruit_circuitpython_vl53l1x-1.2.7.tar.gz", hash = "sha256:6f9531f369ac99eead799a59d2e894bb37c90e910a8cf9651be1d659fd533496", size = 28826, upload-time = "2025-10-29T17:23:00.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/98/8467185dbd2d3531f3ae4453aeb049a0974d2fa09004b65a27772d596028/adafruit_circuitpython_vl53l1x-1.2.7-py3-none-any.whl", hash = "sha256:3ff0865344ca9b6adc512ae78d26de91aa805168e5cb2f80d7d711ba05b451f5", size = 8367, upload-time = "2025-10-29T17:22:59.526Z" }, +] + [[package]] name = "adafruit-gpio" version = "1.0.3" @@ -101,11 +134,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/db/1c/2dc8a674514219f28 [[package]] name = "adafruit-platformdetect" -version = "3.86.0" +version = "3.87.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/d8/71fb0371fcf0aee9a77baba5b6b2e6aaf5ab1e9bcf1ea596f4341522e7aa/adafruit_platformdetect-3.86.0.tar.gz", hash = "sha256:5517298c9a7a43614d0f9bbc5a44aed11d1580a8195da81fa094bd437bf9812f", size = 49322, upload-time = "2025-12-31T17:30:15.321Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/2f/ba7f6ce68efef16f23437186c9364b36db630ed5d24ff25949f07b0e62c2/adafruit_platformdetect-3.87.0.tar.gz", hash = "sha256:4549211a04f1c9d48220292105ebd585a1580d83536727be8ce2e21f0c811486", size = 49357, upload-time = "2026-02-01T03:32:36.198Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/67/13c31854559794c938b17a818bf531930b3697bab8bf0084fcb5d98b7835/adafruit_platformdetect-3.86.0-py3-none-any.whl", hash = "sha256:a1194f60e1a9fdbb6354007334409a04caeabc4da13fcffbc8c5bd8c3ccb62eb", size = 26763, upload-time = "2025-12-31T17:30:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/39/d1fb0a42dd37e676c87a9d056d6742e9e42948c32f118bcfb5a2d986f7a5/adafruit_platformdetect-3.87.0-py3-none-any.whl", hash = "sha256:339db4a9bd2ea172fca6254b32fbade0be446e8b902d1d669756b9600cdfd7ec", size = 26782, upload-time = "2026-02-01T03:32:35.144Z" }, ] [[package]] @@ -126,6 +159,131 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/de/0a176512f6fab96eb3e6adde2b267bab843b3b541d8b83a3783d79c6ff43/Adafruit_SSD1306-1.6.2.tar.gz", hash = "sha256:4768518204037db82eb6b90838938f8519ecd29fe3d6cc3efa0d1100ed527727", size = 5206, upload-time = "2018-05-30T20:03:34.628Z" } +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, +] + +[[package]] +name = "aioice" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "ifaddr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/04/df7286233f468e19e9bedff023b6b246182f0b2ccb04ceeb69b2994021c6/aioice-0.10.2.tar.gz", hash = "sha256:bf236c6829ee33c8e540535d31cd5a066b531cb56de2be94c46be76d68b1a806", size = 44307, upload-time = "2025-11-28T15:56:48.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e3/0d23b1f930c17d371ce1ec36ee529f22fd19ebc2a07fe3418e3d1d884ce2/aioice-0.10.2-py3-none-any.whl", hash = "sha256:14911c15ab12d096dd14d372ebb4aecbb7420b52c9b76fdfcf54375dec17fcbf", size = 24875, upload-time = "2025-11-28T15:56:47.847Z" }, +] + +[[package]] +name = "aiortc" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aioice" }, + { name = "av" }, + { name = "cryptography" }, + { name = "google-crc32c" }, + { name = "pyee" }, + { name = "pylibsrtp" }, + { name = "pyopenssl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/9c/4e027bfe0195de0442da301e2389329496745d40ae44d2d7c4571c4290ce/aiortc-1.14.0.tar.gz", hash = "sha256:adc8a67ace10a085721e588e06a00358ed8eaf5f6b62f0a95358ff45628dd762", size = 1180864, upload-time = "2025-10-13T21:40:37.905Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/ab/31646a49209568cde3b97eeade0d28bb78b400e6645c56422c101df68932/aiortc-1.14.0-py3-none-any.whl", hash = "sha256:4b244d7e482f4e1f67e685b3468269628eca1ec91fa5b329ab517738cfca086e", size = 93183, upload-time = "2025-10-13T21:40:36.59Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "ale-py" +version = "0.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/85/fffbe95501efc9ecf73e02fb62e7a99401e12dcb8217313016dd00b13cdc/ale_py-0.11.2.tar.gz", hash = "sha256:cc6fed9d994d796d76b0d042fbe0a7101834faa55b5e70b1e1f14367ed9e2a8a", size = 512258, upload-time = "2025-07-12T22:19:16.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/e71cf04cc71eb0131aeb31be51440ca2081bcdfe44af06a09dee5fab0431/ale_py-0.11.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:5ab4bfac7c17fdbd96e8068424f491a16c18584f7bbe2797cbb6c13cc4930e76", size = 2336652, upload-time = "2025-07-12T22:18:55.788Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/21de6504fd571ea827d71d436ca196d1253de7a6d2ba26b4e0c19c08d2b4/ale_py-0.11.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:db47d52e75ee0bc08899e32e3c2b05822c3a75f4e6f34e7896bd1133bec3dee7", size = 2469261, upload-time = "2025-07-12T22:18:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/42/de/942044cadbbba73a2c15f1e9a6603106afd53a8c1e6c91ec6eca0e8ca021/ale_py-0.11.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7c42fa8a76caf04dd435bd3fc8682a9d25128102d1df96c35b7971ee31f137d0", size = 5063866, upload-time = "2025-07-12T22:18:58.86Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ff/154caf6b05b7993dd438b78bcd2a8c7d97c6ecd4a3fee82a610d83711b84/ale_py-0.11.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:212927f98357390e651b830835e1d24690816416d0e0d2148ad2d4c679941e1c", size = 5051643, upload-time = "2025-07-12T22:19:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/4b/dc/55f404bf4c8a2c707ef05eba9a8986fc943d192e26d2b4bb6889de02abc9/ale_py-0.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:bb8c4d6d8b6cbecfff2915c9f1787101f033719b66f8149dbc4685a2ff22514a", size = 3471735, upload-time = "2025-07-12T22:19:01.895Z" }, + { url = "https://files.pythonhosted.org/packages/85/27/a75f6c231704af0a71d15fa3520d4f3444031fecb614ba96b2b567c4d873/ale_py-0.11.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:208c70a8a47d8ba5f0b6eb8bfd1c3adc6ec2718f66d7866646976f0853dbda4e", size = 2336736, upload-time = "2025-07-12T22:19:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/aa/02/ec9804b7f6ad80ecb9c4b7f76bad6f672fca3337ea3d7662bdb7239aa595/ale_py-0.11.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:f12a9ee789c3c851ea60afe91c6273e49b880dca510bae00496b0339c41cda81", size = 2469360, upload-time = "2025-07-12T22:19:04.042Z" }, + { url = "https://files.pythonhosted.org/packages/e5/77/e4b21f4befd96c949aa9addf543cf612504612df30039841a182831fb593/ale_py-0.11.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d12e62ac6d57a02745ad8cbf72fbf11ffedbe12d14b48d08e33f22f5625c8ec8", size = 5062617, upload-time = "2025-07-12T22:19:05.15Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1b/79bf78c840d520b6928480bee14b39c3617833edb9ccf81a965e9beec30a/ale_py-0.11.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e56970ae5f56f377d1c6d8d364d0c610f9c0bc4f88f7abce48174c83ea2882", size = 5052135, upload-time = "2025-07-12T22:19:06.297Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6e/365b95c82b214e6b193c6dbda94afaedc5b1673c77777c3b3e3cabd75761/ale_py-0.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:868019090c66fc8c2c24fb19dd8e956a5a4211e594b78097ce1db11a5736684e", size = 3471794, upload-time = "2025-07-12T22:19:08.038Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -149,7 +307,6 @@ name = "anyio" version = "4.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] @@ -159,111 +316,46 @@ wheels = [ ] [[package]] -name = "autotech" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "fastapi" }, - { name = "uvicorn" }, - { name = "websocket" }, - { name = "websockets" }, -] - -[package.optional-dependencies] -docs = [ - { name = "mkdocs" }, - { name = "mkdocs-git-authors-plugin" }, - { name = "mkdocs-git-revision-date-localized-plugin" }, - { name = "mkdocs-mermaid2-plugin" }, -] -rpi = [ - { name = "adafruit-blinka" }, - { name = "adafruit-circuitpython-vl53l0x" }, - { name = "adafruit-ssd1306" }, - { name = "gpiozero" }, - { name = "luma-core" }, - { name = "luma-oled" }, - { name = "matplotlib" }, - { name = "netifaces" }, - { name = "onnxruntime" }, - { name = "opencv-python" }, - { name = "pyps4controller" }, - { name = "rpi-gpio" }, - { name = "rpi-hardware-pwm" }, - { name = "scipy" }, - { name = "smbus" }, - { name = "spidev" }, - { name = "websockets" }, - { name = "zmq" }, -] -simu = [ - { name = "matplotlib" }, - { name = "numpy" }, - { name = "onnx" }, - { name = "onnxruntime" }, - { name = "psutil" }, - { name = "stable-baselines3" }, - { name = "torch" }, -] - -[package.metadata] -requires-dist = [ - { name = "adafruit-blinka", marker = "extra == 'rpi'", specifier = ">=8.0.0" }, - { name = "adafruit-circuitpython-vl53l0x", marker = "extra == 'rpi'", specifier = ">=3.6.0" }, - { name = "adafruit-ssd1306", marker = "extra == 'rpi'", specifier = ">=1.6.0" }, - { name = "fastapi", specifier = ">=0.128.0" }, - { name = "gpiozero", marker = "extra == 'rpi'", specifier = ">=1.6.0" }, - { name = "luma-core", marker = "extra == 'rpi'", specifier = ">=2.3.0" }, - { name = "luma-oled", marker = "extra == 'rpi'", specifier = ">=3.8.0" }, - { name = "matplotlib", marker = "extra == 'rpi'", specifier = ">=3.10.5" }, - { name = "matplotlib", marker = "extra == 'simu'", specifier = ">=3.4.0" }, - { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.4.0" }, - { name = "mkdocs-git-authors-plugin", marker = "extra == 'docs'", specifier = ">=0.6.0" }, - { name = "mkdocs-git-revision-date-localized-plugin", marker = "extra == 'docs'", specifier = ">=1.1.0" }, - { name = "mkdocs-mermaid2-plugin", marker = "extra == 'docs'", specifier = ">=0.6.0" }, - { name = "netifaces", marker = "extra == 'rpi'", specifier = ">=0.11.0" }, - { name = "numpy", marker = "extra == 'simu'", specifier = ">=1.21.0" }, - { name = "onnx", marker = "extra == 'simu'", specifier = ">=1.8.0" }, - { name = "onnxruntime", marker = "extra == 'rpi'", specifier = ">=1.8.0" }, - { name = "onnxruntime", marker = "extra == 'simu'", specifier = ">=1.8.0" }, - { name = "opencv-python", marker = "extra == 'rpi'", specifier = ">=4.12.0.88" }, - { name = "psutil", extras = ["simu"], marker = "extra == 'simu'", specifier = ">=7.1.3" }, - { name = "pyps4controller", marker = "extra == 'rpi'", specifier = ">=1.2.0" }, - { name = "rpi-gpio", marker = "extra == 'rpi'", specifier = ">=0.7.1" }, - { name = "rpi-hardware-pwm", marker = "extra == 'rpi'", specifier = ">=0.1.0" }, - { name = "scipy", marker = "extra == 'rpi'", specifier = ">=1.15.3" }, - { name = "smbus", marker = "extra == 'rpi'", specifier = ">=1.1.post2" }, - { name = "spidev", marker = "extra == 'rpi'", specifier = ">=3.5" }, - { name = "stable-baselines3", marker = "extra == 'simu'", specifier = ">=1.6.0" }, - { name = "torch", marker = "extra == 'simu'", specifier = ">=1.9.0" }, - { name = "uvicorn", specifier = ">=0.40.0" }, - { name = "websocket", specifier = ">=0.2.1" }, - { name = "websockets", specifier = ">=16.0" }, - { name = "websockets", marker = "extra == 'rpi'", specifier = ">=16.0" }, - { name = "zmq", marker = "extra == 'rpi'", specifier = ">=0.0.0" }, -] -provides-extras = ["docs", "simu", "rpi"] - -[[package]] -name = "babel" -version = "2.17.0" +name = "attrs" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] -name = "beautifulsoup4" -version = "4.14.3" +name = "autotech" +version = "0.1.0" +source = { virtual = "." } + +[[package]] +name = "av" +version = "16.1.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/cd/3a83ffbc3cc25b39721d174487fb0d51a76582f4a1703f98e46170ce83d4/av-16.1.0.tar.gz", hash = "sha256:a094b4fd87a3721dacf02794d3d2c82b8d712c85b9534437e82a8a978c175ffd", size = 4285203, upload-time = "2026-01-11T07:31:33.772Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + { url = "https://files.pythonhosted.org/packages/9c/84/2535f55edcd426cebec02eb37b811b1b0c163f26b8d3f53b059e2ec32665/av-16.1.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:640f57b93f927fba8689f6966c956737ee95388a91bd0b8c8b5e0481f73513d6", size = 26945785, upload-time = "2026-01-09T20:18:34.486Z" }, + { url = "https://files.pythonhosted.org/packages/b6/17/ffb940c9e490bf42e86db4db1ff426ee1559cd355a69609ec1efe4d3a9eb/av-16.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ae3fb658eec00852ebd7412fdc141f17f3ddce8afee2d2e1cf366263ad2a3b35", size = 21481147, upload-time = "2026-01-09T20:18:36.716Z" }, + { url = "https://files.pythonhosted.org/packages/15/c1/e0d58003d2d83c3921887d5c8c9b8f5f7de9b58dc2194356a2656a45cfdc/av-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ee558d9c02a142eebcbe55578a6d817fedfde42ff5676275504e16d07a7f86", size = 39517197, upload-time = "2026-01-11T09:57:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/77/787797b43475d1b90626af76f80bfb0c12cfec5e11eafcfc4151b8c80218/av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2", size = 41174337, upload-time = "2026-01-11T09:57:35.792Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/d90df7f1e3b97fc5554cf45076df5045f1e0a6adf13899e10121229b826c/av-16.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8cf065f9d438e1921dc31fc7aa045790b58aee71736897866420d80b5450f62a", size = 40817720, upload-time = "2026-01-11T09:57:39.039Z" }, + { url = "https://files.pythonhosted.org/packages/80/6f/13c3a35f9dbcebafd03fe0c4cbd075d71ac8968ec849a3cfce406c35a9d2/av-16.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a345877a9d3cc0f08e2bc4ec163ee83176864b92587afb9d08dff50f37a9a829", size = 42267396, upload-time = "2026-01-11T09:57:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/2a/63797a4dde34283dd8054219fcb29294ba1c25d68ba8c8c8a6ae53c62c45/av-16.1.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:ce2a1b3d8bf619f6c47a9f28cfa7518ff75ddd516c234a4ee351037b05e6a587", size = 26916715, upload-time = "2026-01-11T09:57:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c4/0b49cf730d0ae8cda925402f18ae814aef351f5772d14da72dd87ff66448/av-16.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:408dbe6a2573ca58a855eb8cd854112b33ea598651902c36709f5f84c991ed8e", size = 21452167, upload-time = "2026-01-11T09:57:50.606Z" }, + { url = "https://files.pythonhosted.org/packages/51/23/408806503e8d5d840975aad5699b153aaa21eb6de41ade75248a79b7a37f/av-16.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:57f657f86652a160a8a01887aaab82282f9e629abf94c780bbdbb01595d6f0f7", size = 39215659, upload-time = "2026-01-11T09:57:53.757Z" }, + { url = "https://files.pythonhosted.org/packages/c4/19/a8528d5bba592b3903f44c28dab9cc653c95fcf7393f382d2751a1d1523e/av-16.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:adbad2b355c2ee4552cac59762809d791bda90586d134a33c6f13727fb86cb3a", size = 40874970, upload-time = "2026-01-11T09:57:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/e8/24/2dbcdf0e929ad56b7df078e514e7bd4ca0d45cba798aff3c8caac097d2f7/av-16.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f42e1a68ec2aebd21f7eb6895be69efa6aa27eec1670536876399725bbda4b99", size = 40530345, upload-time = "2026-01-11T09:58:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/54/27/ae91b41207f34e99602d1c72ab6ffd9c51d7c67e3fbcd4e3a6c0e54f882c/av-16.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58fe47aeaef0f100c40ec8a5de9abbd37f118d3ca03829a1009cf288e9aef67c", size = 41972163, upload-time = "2026-01-11T09:58:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7a/22158fb923b2a9a00dfab0e96ef2e8a1763a94dd89e666a5858412383d46/av-16.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:565093ebc93b2f4b76782589564869dadfa83af5b852edebedd8fee746457d06", size = 31729230, upload-time = "2026-01-11T09:58:07.254Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f1/878f8687d801d6c4565d57ebec08449c46f75126ebca8e0fed6986599627/av-16.1.0-cp313-cp313t-macosx_11_0_x86_64.whl", hash = "sha256:574081a24edb98343fd9f473e21ae155bf61443d4ec9d7708987fa597d6b04b2", size = 27008769, upload-time = "2026-01-11T09:58:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/30/f1/bd4ce8c8b5cbf1d43e27048e436cbc9de628d48ede088a1d0a993768eb86/av-16.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:9ab00ea29c25ebf2ea1d1e928d7babb3532d562481c5d96c0829212b70756ad0", size = 21590588, upload-time = "2026-01-11T09:58:12.629Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/c81f6f9209201ff0b5d5bed6da6c6e641eef52d8fbc930d738c3f4f6f75d/av-16.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:a84a91188c1071f238a9523fd42dbe567fb2e2607b22b779851b2ce0eac1b560", size = 40638029, upload-time = "2026-01-11T09:58:15.399Z" }, + { url = "https://files.pythonhosted.org/packages/15/4d/07edff82b78d0459a6e807e01cd280d3180ce832efc1543de80d77676722/av-16.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c2cd0de4dd022a7225ff224fde8e7971496d700be41c50adaaa26c07bb50bf97", size = 41970776, upload-time = "2026-01-11T09:58:19.075Z" }, + { url = "https://files.pythonhosted.org/packages/da/9d/1f48b354b82fa135d388477cd1b11b81bdd4384bd6a42a60808e2ec2d66b/av-16.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0816143530624a5a93bc5494f8c6eeaf77549b9366709c2ac8566c1e9bff6df5", size = 41764751, upload-time = "2026-01-11T09:58:22.788Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c7/a509801e98db35ec552dd79da7bdbcff7104044bfeb4c7d196c1ce121593/av-16.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e3a28053af29644696d0c007e897d19b1197585834660a54773e12a40b16974c", size = 43034355, upload-time = "2026-01-11T09:58:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/e5f530d9e8f640da5f5c5f681a424c65f9dd171c871cd255d8a861785a6e/av-16.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e3e67144a202b95ed299d165232533989390a9ea3119d37eccec697dc6dbb0c", size = 31947047, upload-time = "2026-01-11T09:58:31.867Z" }, ] [[package]] @@ -301,15 +393,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d6/4f/101071f880b4da05771128c0b89f41e334cff044dee05fb013c8f4be661c/cbor2-5.8.0-py3-none-any.whl", hash = "sha256:3727d80f539567b03a7aa11890e57798c67092c38df9e6c23abb059e0f65069c", size = 24374, upload-time = "2025-12-30T18:44:21.476Z" }, ] -[[package]] -name = "certifi" -version = "2026.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, -] - [[package]] name = "cffi" version = "2.0.0" @@ -345,47 +428,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, ] -[[package]] -name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, -] - [[package]] name = "click" version = "8.3.1" @@ -485,33 +527,81 @@ wheels = [ ] [[package]] -name = "cycler" -version = "0.12.1" +name = "cryptography" +version = "46.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +] + +[[package]] +name = "cuda-bindings" +version = "12.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" }, ] [[package]] -name = "editorconfig" -version = "0.17.1" +name = "cuda-pathfinder" +version = "1.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/3a/a61d9a1f319a186b05d14df17daea42fcddea63c213bcd61a929fb3a6796/editorconfig-0.17.1.tar.gz", hash = "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745", size = 14695, upload-time = "2025-06-09T08:21:37.097Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/fd/a40c621ff207f3ce8e484aa0fc8ba4eb6e3ecf52e15b42ba764b457a9550/editorconfig-0.17.1-py3-none-any.whl", hash = "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", size = 16360, upload-time = "2025-06-09T08:21:35.654Z" }, + { url = "https://files.pythonhosted.org/packages/0b/02/4dbe7568a42e46582248942f54dc64ad094769532adbe21e525e4edf7bc4/cuda_pathfinder-1.3.3-py3-none-any.whl", hash = "sha256:9984b664e404f7c134954a771be8775dfd6180ea1e1aef4a5a37d4be05d9bbb1", size = 27154, upload-time = "2025-12-04T22:35:08.996Z" }, ] [[package]] -name = "exceptiongroup" -version = "1.3.1" +name = "cycler" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] [[package]] @@ -525,7 +615,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.128.0" +version = "0.128.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -533,9 +623,9 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/59/28bde150415783ff084334e3de106eb7461a57864cf69f343950ad5a5ddd/fastapi-0.128.1.tar.gz", hash = "sha256:ce5be4fa26d4ce6f54debcc873d1fb8e0e248f5c48d7502ba6c61457ab2dc766", size = 374260, upload-time = "2026-02-04T17:35:10.542Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/3953db1979ea131c68279b997c6465080118b407f0800445b843f8e164b3/fastapi-0.128.1-py3-none-any.whl", hash = "sha256:ee82146bbf91ea5bbf2bb8629e4c6e056c4fbd997ea6068501b11b15260b50fb", size = 103810, upload-time = "2026-02-04T17:35:08.02Z" }, ] [[package]] @@ -580,6 +670,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, ] +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + [[package]] name = "fsspec" version = "2026.1.0" @@ -590,91 +737,21 @@ wheels = [ ] [[package]] -name = "gevent" -version = "25.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, - { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, - { name = "zope-event" }, - { name = "zope-interface" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/48/b3ef2673ffb940f980966694e40d6d32560f3ffa284ecaeb5ea3a90a6d3f/gevent-25.9.1.tar.gz", hash = "sha256:adf9cd552de44a4e6754c51ff2e78d9193b7fa6eab123db9578a210e657235dd", size = 5059025, upload-time = "2025-09-17T16:15:34.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/c7/2c60fc4e5c9144f2b91e23af8d87c626870ad3183cfd09d2b3ba6d699178/gevent-25.9.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:856b990be5590e44c3a3dc6c8d48a40eaccbb42e99d2b791d11d1e7711a4297e", size = 1831980, upload-time = "2025-09-17T15:41:22.597Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ae/49bf0a01f95a1c92c001d7b3f482a2301626b8a0617f448c4cd14ca9b5d4/gevent-25.9.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fe1599d0b30e6093eb3213551751b24feeb43db79f07e89d98dd2f3330c9063e", size = 1918777, upload-time = "2025-09-17T15:48:57.223Z" }, - { url = "https://files.pythonhosted.org/packages/88/3f/266d2eb9f5d75c184a55a39e886b53a4ea7f42ff31f195220a363f0e3f9e/gevent-25.9.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:f0d8b64057b4bf1529b9ef9bd2259495747fba93d1f836c77bfeaacfec373fd0", size = 1869235, upload-time = "2025-09-17T15:49:18.255Z" }, - { url = "https://files.pythonhosted.org/packages/76/24/c0c7c7db70ca74c7b1918388ebda7c8c2a3c3bff0bbfbaa9280ed04b3340/gevent-25.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b56cbc820e3136ba52cd690bdf77e47a4c239964d5f80dc657c1068e0fe9521c", size = 2177334, upload-time = "2025-09-17T15:15:10.073Z" }, - { url = "https://files.pythonhosted.org/packages/4c/1e/de96bd033c03955f54c455b51a5127b1d540afcfc97838d1801fafce6d2e/gevent-25.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5fa9ce5122c085983e33e0dc058f81f5264cebe746de5c401654ab96dddfca8", size = 1847708, upload-time = "2025-09-17T15:52:38.475Z" }, - { url = "https://files.pythonhosted.org/packages/26/8b/6851e9cd3e4f322fa15c1d196cbf1a8a123da69788b078227dd13dd4208f/gevent-25.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:03c74fec58eda4b4edc043311fca8ba4f8744ad1632eb0a41d5ec25413581975", size = 2234274, upload-time = "2025-09-17T15:24:07.797Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d8/b1178b70538c91493bec283018b47c16eab4bac9ddf5a3d4b7dd905dab60/gevent-25.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a8ae9f895e8651d10b0a8328a61c9c53da11ea51b666388aa99b0ce90f9fdc27", size = 1695326, upload-time = "2025-09-17T20:10:25.455Z" }, - { url = "https://files.pythonhosted.org/packages/81/86/03f8db0704fed41b0fa830425845f1eb4e20c92efa3f18751ee17809e9c6/gevent-25.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5aff9e8342dc954adb9c9c524db56c2f3557999463445ba3d9cbe3dada7b7", size = 1792418, upload-time = "2025-09-17T15:41:24.384Z" }, - { url = "https://files.pythonhosted.org/packages/5f/35/f6b3a31f0849a62cfa2c64574bcc68a781d5499c3195e296e892a121a3cf/gevent-25.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1cdf6db28f050ee103441caa8b0448ace545364f775059d5e2de089da975c457", size = 1875700, upload-time = "2025-09-17T15:48:59.652Z" }, - { url = "https://files.pythonhosted.org/packages/66/1e/75055950aa9b48f553e061afa9e3728061b5ccecca358cef19166e4ab74a/gevent-25.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:812debe235a8295be3b2a63b136c2474241fa5c58af55e6a0f8cfc29d4936235", size = 1831365, upload-time = "2025-09-17T15:49:19.426Z" }, - { url = "https://files.pythonhosted.org/packages/31/e8/5c1f6968e5547e501cfa03dcb0239dff55e44c3660a37ec534e32a0c008f/gevent-25.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b28b61ff9216a3d73fe8f35669eefcafa957f143ac534faf77e8a19eb9e6883a", size = 2122087, upload-time = "2025-09-17T15:15:12.329Z" }, - { url = "https://files.pythonhosted.org/packages/c0/2c/ebc5d38a7542af9fb7657bfe10932a558bb98c8a94e4748e827d3823fced/gevent-25.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5e4b6278b37373306fc6b1e5f0f1cf56339a1377f67c35972775143d8d7776ff", size = 1808776, upload-time = "2025-09-17T15:52:40.16Z" }, - { url = "https://files.pythonhosted.org/packages/e6/26/e1d7d6c8ffbf76fe1fbb4e77bdb7f47d419206adc391ec40a8ace6ebbbf0/gevent-25.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d99f0cb2ce43c2e8305bf75bee61a8bde06619d21b9d0316ea190fc7a0620a56", size = 2179141, upload-time = "2025-09-17T15:24:09.895Z" }, - { url = "https://files.pythonhosted.org/packages/1d/6c/bb21fd9c095506aeeaa616579a356aa50935165cc0f1e250e1e0575620a7/gevent-25.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:72152517ecf548e2f838c61b4be76637d99279dbaa7e01b3924df040aa996586", size = 1677941, upload-time = "2025-09-17T19:59:50.185Z" }, - { url = "https://files.pythonhosted.org/packages/f7/49/e55930ba5259629eb28ac7ee1abbca971996a9165f902f0249b561602f24/gevent-25.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:46b188248c84ffdec18a686fcac5dbb32365d76912e14fda350db5dc0bfd4f86", size = 2955991, upload-time = "2025-09-17T14:52:30.568Z" }, - { url = "https://files.pythonhosted.org/packages/aa/88/63dc9e903980e1da1e16541ec5c70f2b224ec0a8e34088cb42794f1c7f52/gevent-25.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f2b54ea3ca6f0c763281cd3f96010ac7e98c2e267feb1221b5a26e2ca0b9a692", size = 1808503, upload-time = "2025-09-17T15:41:25.59Z" }, - { url = "https://files.pythonhosted.org/packages/7a/8d/7236c3a8f6ef7e94c22e658397009596fa90f24c7d19da11ad7ab3a9248e/gevent-25.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7a834804ac00ed8a92a69d3826342c677be651b1c3cd66cc35df8bc711057aa2", size = 1890001, upload-time = "2025-09-17T15:49:01.227Z" }, - { url = "https://files.pythonhosted.org/packages/4f/63/0d7f38c4a2085ecce26b50492fc6161aa67250d381e26d6a7322c309b00f/gevent-25.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:323a27192ec4da6b22a9e51c3d9d896ff20bc53fdc9e45e56eaab76d1c39dd74", size = 1855335, upload-time = "2025-09-17T15:49:20.582Z" }, - { url = "https://files.pythonhosted.org/packages/95/18/da5211dfc54c7a57e7432fd9a6ffeae1ce36fe5a313fa782b1c96529ea3d/gevent-25.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6ea78b39a2c51d47ff0f130f4c755a9a4bbb2dd9721149420ad4712743911a51", size = 2109046, upload-time = "2025-09-17T15:15:13.817Z" }, - { url = "https://files.pythonhosted.org/packages/a6/5a/7bb5ec8e43a2c6444853c4a9f955f3e72f479d7c24ea86c95fb264a2de65/gevent-25.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dc45cd3e1cc07514a419960af932a62eb8515552ed004e56755e4bf20bad30c5", size = 1827099, upload-time = "2025-09-17T15:52:41.384Z" }, - { url = "https://files.pythonhosted.org/packages/ca/d4/b63a0a60635470d7d986ef19897e893c15326dd69e8fb342c76a4f07fe9e/gevent-25.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34e01e50c71eaf67e92c186ee0196a039d6e4f4b35670396baed4a2d8f1b347f", size = 2172623, upload-time = "2025-09-17T15:24:12.03Z" }, - { url = "https://files.pythonhosted.org/packages/d5/98/caf06d5d22a7c129c1fb2fc1477306902a2c8ddfd399cd26bbbd4caf2141/gevent-25.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acd6bcd5feabf22c7c5174bd3b9535ee9f088d2bbce789f740ad8d6554b18f3", size = 1682837, upload-time = "2025-09-17T19:48:47.318Z" }, - { url = "https://files.pythonhosted.org/packages/5a/77/b97f086388f87f8ad3e01364f845004aef0123d4430241c7c9b1f9bde742/gevent-25.9.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:4f84591d13845ee31c13f44bdf6bd6c3dbf385b5af98b2f25ec328213775f2ed", size = 2973739, upload-time = "2025-09-17T14:53:30.279Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/9d5f204ead343e5b27bbb2fedaec7cd0009d50696b2266f590ae845d0331/gevent-25.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9cdbb24c276a2d0110ad5c978e49daf620b153719ac8a548ce1250a7eb1b9245", size = 1809165, upload-time = "2025-09-17T15:41:27.193Z" }, - { url = "https://files.pythonhosted.org/packages/10/3e/791d1bf1eb47748606d5f2c2aa66571f474d63e0176228b1f1fd7b77ab37/gevent-25.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:88b6c07169468af631dcf0fdd3658f9246d6822cc51461d43f7c44f28b0abb82", size = 1890638, upload-time = "2025-09-17T15:49:02.45Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5c/9ad0229b2b4d81249ca41e4f91dd8057deaa0da6d4fbe40bf13cdc5f7a47/gevent-25.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b7bb0e29a7b3e6ca9bed2394aa820244069982c36dc30b70eb1004dd67851a48", size = 1857118, upload-time = "2025-09-17T15:49:22.125Z" }, - { url = "https://files.pythonhosted.org/packages/49/2a/3010ed6c44179a3a5c5c152e6de43a30ff8bc2c8de3115ad8733533a018f/gevent-25.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2951bb070c0ee37b632ac9134e4fdaad70d2e660c931bb792983a0837fe5b7d7", size = 2111598, upload-time = "2025-09-17T15:15:15.226Z" }, - { url = "https://files.pythonhosted.org/packages/08/75/6bbe57c19a7aa4527cc0f9afcdf5a5f2aed2603b08aadbccb5bf7f607ff4/gevent-25.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4e17c2d57e9a42e25f2a73d297b22b60b2470a74be5a515b36c984e1a246d47", size = 1829059, upload-time = "2025-09-17T15:52:42.596Z" }, - { url = "https://files.pythonhosted.org/packages/06/6e/19a9bee9092be45679cb69e4dd2e0bf5f897b7140b4b39c57cc123d24829/gevent-25.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d94936f8f8b23d9de2251798fcb603b84f083fdf0d7f427183c1828fb64f117", size = 2173529, upload-time = "2025-09-17T15:24:13.897Z" }, - { url = "https://files.pythonhosted.org/packages/ca/4f/50de9afd879440e25737e63f5ba6ee764b75a3abe17376496ab57f432546/gevent-25.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb51c5f9537b07da673258b4832f6635014fee31690c3f0944d34741b69f92fa", size = 1681518, upload-time = "2025-09-17T19:39:47.488Z" }, - { url = "https://files.pythonhosted.org/packages/15/1a/948f8167b2cdce573cf01cec07afc64d0456dc134b07900b26ac7018b37e/gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1a3fe4ea1c312dbf6b375b416925036fe79a40054e6bf6248ee46526ea628be1", size = 2982934, upload-time = "2025-09-17T14:54:11.302Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ec/726b146d1d3aad82e03d2e1e1507048ab6072f906e83f97f40667866e582/gevent-25.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0adb937f13e5fb90cca2edf66d8d7e99d62a299687400ce2edee3f3504009356", size = 1813982, upload-time = "2025-09-17T15:41:28.506Z" }, - { url = "https://files.pythonhosted.org/packages/35/5d/5f83f17162301662bd1ce702f8a736a8a8cac7b7a35e1d8b9866938d1f9d/gevent-25.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:427f869a2050a4202d93cf7fd6ab5cffb06d3e9113c10c967b6e2a0d45237cb8", size = 1894902, upload-time = "2025-09-17T15:49:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/83/cd/cf5e74e353f60dab357829069ffc300a7bb414c761f52cf8c0c6e9728b8d/gevent-25.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c049880175e8c93124188f9d926af0a62826a3b81aa6d3074928345f8238279e", size = 1861792, upload-time = "2025-09-17T15:49:23.279Z" }, - { url = "https://files.pythonhosted.org/packages/dd/65/b9a4526d4a4edce26fe4b3b993914ec9dc64baabad625a3101e51adb17f3/gevent-25.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b5a67a0974ad9f24721034d1e008856111e0535f1541499f72a733a73d658d1c", size = 2113215, upload-time = "2025-09-17T15:15:16.34Z" }, - { url = "https://files.pythonhosted.org/packages/e5/be/7d35731dfaf8370795b606e515d964a0967e129db76ea7873f552045dd39/gevent-25.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d0f5d8d73f97e24ea8d24d8be0f51e0cf7c54b8021c1fddb580bf239474690f", size = 1833449, upload-time = "2025-09-17T15:52:43.75Z" }, - { url = "https://files.pythonhosted.org/packages/65/58/7bc52544ea5e63af88c4a26c90776feb42551b7555a1c89c20069c168a3f/gevent-25.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddd3ff26e5c4240d3fbf5516c2d9d5f2a998ef87cfb73e1429cfaeaaec860fa6", size = 2176034, upload-time = "2025-09-17T15:24:15.676Z" }, - { url = "https://files.pythonhosted.org/packages/c2/69/a7c4ba2ffbc7c7dbf6d8b4f5d0f0a421f7815d229f4909854266c445a3d4/gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7", size = 1703019, upload-time = "2025-09-17T19:30:55.272Z" }, -] - -[[package]] -name = "ghp-import" -version = "2.1.0" +name = "google-crc32c" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, -] - -[[package]] -name = "gitdb" -version = "4.0.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "smmap" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, -] - -[[package]] -name = "gitpython" -version = "3.1.46" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gitdb" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, ] [[package]] @@ -690,58 +767,34 @@ wheels = [ ] [[package]] -name = "greenlet" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/6a/33d1702184d94106d3cdd7bfb788e19723206fce152e303473ca3b946c7b/greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d", size = 273658, upload-time = "2025-12-04T14:23:37.494Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b7/2b5805bbf1907c26e434f4e448cd8b696a0b71725204fa21a211ff0c04a7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb", size = 574810, upload-time = "2025-12-04T14:50:04.154Z" }, - { url = "https://files.pythonhosted.org/packages/94/38/343242ec12eddf3d8458c73f555c084359883d4ddc674240d9e61ec51fd6/greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd", size = 586248, upload-time = "2025-12-04T14:57:39.35Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/0ae86792fb212e4384041e0ef8e7bc66f59a54912ce407d26a966ed2914d/greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b", size = 597403, upload-time = "2025-12-04T15:07:10.831Z" }, - { url = "https://files.pythonhosted.org/packages/b6/a8/15d0aa26c0036a15d2659175af00954aaaa5d0d66ba538345bd88013b4d7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5", size = 586910, upload-time = "2025-12-04T14:25:59.705Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9b/68d5e3b7ccaba3907e5532cf8b9bf16f9ef5056a008f195a367db0ff32db/greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9", size = 1547206, upload-time = "2025-12-04T15:04:21.027Z" }, - { url = "https://files.pythonhosted.org/packages/66/bd/e3086ccedc61e49f91e2cfb5ffad9d8d62e5dc85e512a6200f096875b60c/greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d", size = 1613359, upload-time = "2025-12-04T14:27:26.548Z" }, - { url = "https://files.pythonhosted.org/packages/f4/6b/d4e73f5dfa888364bbf02efa85616c6714ae7c631c201349782e5b428925/greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082", size = 300740, upload-time = "2025-12-04T14:47:52.773Z" }, - { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, - { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, - { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" }, - { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, - { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, - { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, - { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, - { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, - { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, - { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, - { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, - { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, - { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, - { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" }, - { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" }, - { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" }, - { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" }, - { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" }, - { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" }, +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, + { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, + { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, + { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, ] [[package]] @@ -768,6 +821,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "high-level" +version = "0.1.0" +source = { editable = "src/high_level" } +dependencies = [ + { name = "adafruit-blinka" }, + { name = "adafruit-circuitpython-vl53l0x" }, + { name = "adafruit-circuitpython-vl53l1x" }, + { name = "adafruit-ssd1306" }, + { name = "aiohttp" }, + { name = "aiortc" }, + { name = "fastapi" }, + { name = "gpiozero" }, + { name = "luma-core" }, + { name = "luma-oled" }, + { name = "matplotlib" }, + { name = "netifaces" }, + { name = "onnxruntime" }, + { name = "opencv-python" }, + { name = "picamera2" }, + { name = "pyps4controller" }, + { name = "rpi-gpio" }, + { name = "rpi-hardware-pwm" }, + { name = "scipy" }, + { name = "smbus" }, + { name = "spidev" }, + { name = "uvicorn" }, + { name = "websockets" }, + { name = "zmq" }, +] + +[package.metadata] +requires-dist = [ + { name = "adafruit-blinka", specifier = ">=8.0.0" }, + { name = "adafruit-circuitpython-vl53l0x", specifier = ">=3.6.0" }, + { name = "adafruit-circuitpython-vl53l1x", specifier = ">=1.2.7" }, + { name = "adafruit-ssd1306", specifier = ">=1.6.0" }, + { name = "aiohttp", specifier = ">=3.13.3" }, + { name = "aiortc", specifier = ">=1.14.0" }, + { name = "fastapi", specifier = ">=0.128.0" }, + { name = "gpiozero", specifier = ">=1.6.0" }, + { name = "luma-core", specifier = ">=2.3.0" }, + { name = "luma-oled", specifier = ">=3.8.0" }, + { name = "matplotlib", specifier = ">=3.10.5" }, + { name = "netifaces", specifier = ">=0.11.0" }, + { name = "onnxruntime", specifier = ">=1.8.0" }, + { name = "opencv-python", specifier = ">=4.12.0.88" }, + { name = "picamera2", specifier = ">=0.3.0" }, + { name = "pyps4controller", specifier = ">=1.2.0" }, + { name = "rpi-gpio", specifier = ">=0.7.1" }, + { name = "rpi-hardware-pwm", specifier = ">=0.1.0" }, + { name = "scipy", specifier = ">=1.15.3" }, + { name = "smbus", specifier = ">=1.1.post2" }, + { name = "spidev", specifier = ">=3.5" }, + { name = "uvicorn", specifier = ">=0.40.0" }, + { name = "websockets", specifier = ">=16.0" }, + { name = "zmq", specifier = ">=0.0.0" }, +] + [[package]] name = "humanfriendly" version = "10.0" @@ -789,6 +901,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "ifaddr" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4", size = 10485, upload-time = "2022-06-15T21:40:27.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -802,16 +923,30 @@ wheels = [ ] [[package]] -name = "jsbeautifier" -version = "1.15.4" +name = "jsonschema" +version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "editorconfig" }, - { name = "six" }, + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/98/d6cadf4d5a1c03b2136837a435682418c29fdeb66be137128544cecc5b7a/jsbeautifier-1.15.4.tar.gz", hash = "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592", size = 75257, upload-time = "2025-02-27T17:53:53.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/14/1c65fccf8413d5f5c6e8425f84675169654395098000d8bddc4e9d3390e1/jsbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528", size = 94707, upload-time = "2025-02-27T17:53:46.152Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] [[package]] @@ -860,6 +995,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, ] +[[package]] +name = "libarchive-c" +version = "5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/23/e72434d5457c24113e0c22605cbf7dd806a2561294a335047f5aa8ddc1ca/libarchive_c-5.3.tar.gz", hash = "sha256:5ddb42f1a245c927e7686545da77159859d5d4c6d00163c59daff4df314dae82", size = 54349, upload-time = "2025-05-22T08:08:04.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/3f/ff00c588ebd7eae46a9d6223389f5ae28a3af4b6d975c0f2a6d86b1342b9/libarchive_c-5.3-py3-none-any.whl", hash = "sha256:651550a6ec39266b78f81414140a1e04776c935e72dfc70f1d7c8e0a3672ffba", size = 17035, upload-time = "2025-05-22T08:08:03.045Z" }, +] + [[package]] name = "luma-core" version = "2.5.3" @@ -888,11 +1032,23 @@ wheels = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [[package]] @@ -977,94 +1133,12 @@ wheels = [ ] [[package]] -name = "mergedeep" -version = "1.3.4" +name = "mdurl" +version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, -] - -[[package]] -name = "mkdocs" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "ghp-import" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mergedeep" }, - { name = "mkdocs-get-deps" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "watchdog" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, -] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mergedeep" }, - { name = "platformdirs" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, -] - -[[package]] -name = "mkdocs-git-authors-plugin" -version = "0.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/f1/b784c631b812aab80030db80127a576b68a84caac5229836fb7fcc00e055/mkdocs_git_authors_plugin-0.10.0.tar.gz", hash = "sha256:29d1973b2835663d79986fb756e02f1f0ff3fe35c278e993206bd3c550c205e4", size = 23432, upload-time = "2025-06-10T05:42:40.94Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/bc/a4166201c2789657c4d370bfcd71a5107edec185ae245675c8b9a6719243/mkdocs_git_authors_plugin-0.10.0-py3-none-any.whl", hash = "sha256:28421a99c3e872a8e205674bb80ec48524838243e5f59eaf9bd97df103e38901", size = 21899, upload-time = "2025-06-10T05:42:39.244Z" }, -] - -[[package]] -name = "mkdocs-git-revision-date-localized-plugin" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "gitpython" }, - { name = "mkdocs" }, - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/c5/1d3c4e6ddae6230b89d09105cb79de711655e3ebd6745f7a92efea0f5160/mkdocs_git_revision_date_localized_plugin-1.5.0.tar.gz", hash = "sha256:17345ccfdf69a1905dc96fb1070dce82d03a1eb6b0d48f958081a7589ce3c248", size = 460697, upload-time = "2025-10-31T16:11:34.44Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/51/fe0e3fdb16f6eed65c9459d12bae6a4e1f0bb4e2228cb037e7907b002678/mkdocs_git_revision_date_localized_plugin-1.5.0-py3-none-any.whl", hash = "sha256:933f9e35a8c135b113f21bb57610d82e9b7bcc71dd34fb06a029053c97e99656", size = 26153, upload-time = "2025-10-31T16:11:32.987Z" }, -] - -[[package]] -name = "mkdocs-mermaid2-plugin" -version = "1.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "jsbeautifier" }, - { name = "mkdocs" }, - { name = "pymdown-extensions" }, - { name = "requests" }, - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/6d/308f443a558b6a97ce55782658174c0d07c414405cfc0a44d36ad37e36f9/mkdocs_mermaid2_plugin-1.2.3.tar.gz", hash = "sha256:fb6f901d53e5191e93db78f93f219cad926ccc4d51e176271ca5161b6cc5368c", size = 16220, upload-time = "2025-10-17T19:38:53.047Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/4b/6fd6dd632019b7f522f1b1f794ab6115cd79890330986614be56fd18f0eb/mkdocs_mermaid2_plugin-1.2.3-py3-none-any.whl", hash = "sha256:33f60c582be623ed53829a96e19284fc7f1b74a1dbae78d4d2e47fe00c3e190d", size = 17299, upload-time = "2025-10-17T19:38:51.874Z" }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] @@ -1102,6 +1176,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + [[package]] name = "netifaces" version = "0.11.0" @@ -1119,40 +1256,42 @@ wheels = [ [[package]] name = "numpy" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, - { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, - { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, - { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, - { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, - { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, - { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, ] [[package]] @@ -1192,7 +1331,7 @@ name = "nvidia-cudnn-cu12" version = "9.10.2.21" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cublas-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, @@ -1203,7 +1342,7 @@ name = "nvidia-cufft-cu12" version = "11.3.3.83" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, @@ -1230,9 +1369,9 @@ name = "nvidia-cusolver-cu12" version = "11.7.3.90" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-cublas-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cusparse-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, @@ -1243,7 +1382,7 @@ name = "nvidia-cusparse-cu12" version = "12.5.8.93" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, @@ -1275,10 +1414,10 @@ wheels = [ [[package]] name = "nvidia-nvshmem-cu12" -version = "3.3.20" +version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145, upload-time = "2025-08-04T20:25:19.995Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, ] [[package]] @@ -1314,6 +1453,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/7d/1bbe626ff6b192c844d3ad34356840cc60fca02e2dea0db95e01645758b1/onnx-1.20.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eb335d7bcf9abac82a0d6a0fda0363531ae0b22cfd0fc6304bff32ee29905def", size = 16348968, upload-time = "2026-01-10T01:40:00.491Z" }, ] +[[package]] +name = "onnx-ir" +version = "0.1.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/a4/930427e49ccae912f2e21da96b8f52dbb7fb2fdfbba8006dca15c678e96b/onnx_ir-0.1.15.tar.gz", hash = "sha256:edec4db6c502856835e8f46f2d9f5dd8079fbd930170e418eda4203c599fb74a", size = 128266, upload-time = "2026-01-21T17:42:37.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/bf/62da272e87fd1d6317fe7a4dd22a8339f5ce37f95e5f63ad1ee46a13cd35/onnx_ir-0.1.15-py3-none-any.whl", hash = "sha256:c6df0eabd732671e9272275cf7693797497658610c00688d5e05132cbb4e2495", size = 148733, upload-time = "2026-01-21T17:42:36.313Z" }, +] + [[package]] name = "onnxruntime" version = "1.23.2" @@ -1341,73 +1495,152 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/ca/862b1e7a639460f0ca25fd5b6135fb42cf9deea86d398a92e44dfda2279d/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2b9233c4947907fd1818d0e581c049c41ccc39b2856cc942ff6d26317cee145", size = 17394184, upload-time = "2025-10-22T03:47:08.127Z" }, ] +[[package]] +name = "onnxscript" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "onnx-ir" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/0e/56414e9523b27eb84bec31aeb6b7b6f63a000b2c432bdfb2286c38c0f4dc/onnxscript-0.6.0.tar.gz", hash = "sha256:6858e46d53dd508c617636824e8103f29513c18e4bd693e379927ece9b68772f", size = 590933, upload-time = "2026-01-29T19:08:57.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/28/96f946a46b1b5d9ac2cd74f40df17e47e3481aeb469c23d7152d7ba1283f/onnxscript-0.6.0-py3-none-any.whl", hash = "sha256:80ded699e4953b05134e79abf6b77969ad4d66587f532ca583bee382086d1d24", size = 689121, upload-time = "2026-01-29T19:08:59.296Z" }, +] + [[package]] name = "opencv-python" -version = "4.12.0.88" +version = "4.13.0.92" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/71/25c98e634b6bdeca4727c7f6d6927b056080668c5008ad3c8fc9e7f8f6ec/opencv-python-4.12.0.88.tar.gz", hash = "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d", size = 95373294, upload-time = "2025-07-07T09:20:52.389Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/68/3da40142e7c21e9b1d4e7ddd6c58738feb013203e6e4b803d62cdd9eb96b/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5", size = 37877727, upload-time = "2025-07-07T09:13:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/33/7c/042abe49f58d6ee7e1028eefc3334d98ca69b030e3b567fe245a2b28ea6f/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81", size = 57326471, upload-time = "2025-07-07T09:13:41.26Z" }, - { url = "https://files.pythonhosted.org/packages/62/3a/440bd64736cf8116f01f3b7f9f2e111afb2e02beb2ccc08a6458114a6b5d/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92", size = 45887139, upload-time = "2025-07-07T09:13:50.761Z" }, - { url = "https://files.pythonhosted.org/packages/68/1f/795e7f4aa2eacc59afa4fb61a2e35e510d06414dd5a802b51a012d691b37/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9", size = 67041680, upload-time = "2025-07-07T09:14:01.995Z" }, - { url = "https://files.pythonhosted.org/packages/02/96/213fea371d3cb2f1d537612a105792aa0a6659fb2665b22cad709a75bd94/opencv_python-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357", size = 30284131, upload-time = "2025-07-07T09:14:08.819Z" }, - { url = "https://files.pythonhosted.org/packages/fa/80/eb88edc2e2b11cd2dd2e56f1c80b5784d11d6e6b7f04a1145df64df40065/opencv_python-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2", size = 39000307, upload-time = "2025-07-07T09:14:16.641Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052, upload-time = "2026-02-05T07:01:25.046Z" }, + { url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781, upload-time = "2026-02-05T07:01:41.379Z" }, + { url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527, upload-time = "2026-02-05T06:59:11.24Z" }, + { url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872, upload-time = "2026-02-05T06:59:19.162Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208, upload-time = "2026-02-05T06:59:15.419Z" }, + { url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042, upload-time = "2026-02-05T06:59:23.389Z" }, + { url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638, upload-time = "2026-02-05T07:02:14.98Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062, upload-time = "2026-02-05T07:02:12.724Z" }, +] + +[[package]] +name = "openexr" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/9e/3111996e000859586d4cba01d9792b7081f2eab0d1d4afacd148534a6336/openexr-3.4.4.tar.gz", hash = "sha256:9bf1f559a3133bb9c5cbfdea74d867816a06a654189ecbc6b3b44c8276fa0441", size = 25334958, upload-time = "2025-11-19T22:04:07.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/32/5f702338fad7c617c81cabb9f6199093a504e3a2ebd63a4d4df9a0d206ee/openexr-3.4.4-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:0e561a9144c04f326b913652c6399c9eba09755f4ef9632ad2e179eca991d35e", size = 2158794, upload-time = "2025-11-19T22:03:19.549Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0d/10ffc123f0d962bb8fa5b0c018e94fd86e5d5116db938795b007f0dc7723/openexr-3.4.4-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:4d22fda8019c214269ec9fa378e43a43ca16dbe89228ea3768f9b889ff65f60d", size = 1147476, upload-time = "2025-11-19T22:03:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/a7/10/b9ef61c29020316352b23629a75d85325837778b626469eddf433bdf0d8e/openexr-3.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bfd6efdff851f06f5702b95115ae6f314972ec5fe0a1ab190e08304e9180e976", size = 1019535, upload-time = "2025-11-19T22:03:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/df/4f/5f00a6a6963aeced557e412054bacf6acbdfefc298e5fd1da37545a791e9/openexr-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b04549166bba49c6f65caa2d192e338bc3c7d0670a91f577de424cf4221cac38", size = 1157637, upload-time = "2025-11-19T22:03:23.519Z" }, + { url = "https://files.pythonhosted.org/packages/41/a2/23c50db65d0925aaca9d65706ebf97cd242beaeb1d39131f159cffe0f1cc/openexr-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1085ca20a6ae9870ec27d4f1e1f7cc0ee87a39a672f9971f036a80bd11d8c8e8", size = 1290589, upload-time = "2025-11-19T22:03:24.794Z" }, + { url = "https://files.pythonhosted.org/packages/ea/41/167cd85e5c173eb04996583cd347703a26c85c37e839e82587e06d22e92f/openexr-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3b5f5bc6dbfba398a8dcbcbf4d3b332da6fd134d47a288239dfb85457435cc8", size = 2152240, upload-time = "2025-11-19T22:03:26.036Z" }, + { url = "https://files.pythonhosted.org/packages/f7/fd/9376c52e08564a809edf0bcb8031a5e1342facff2719e7059582ee13e536/openexr-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5443b222b0bced2c8353befb62807cdc18e3f0ef82e086b30b9c3e755513e1dd", size = 2335920, upload-time = "2025-11-19T22:03:27.421Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2e/c00a9b310a67aeb198952edf84d44a24f067b31daeb6e9b4090cb682d6f4/openexr-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:12edaf40a5febb889ba97336b300f92a3dc308c060f577674156b911d8aa742f", size = 724008, upload-time = "2025-11-19T22:03:29.214Z" }, + { url = "https://files.pythonhosted.org/packages/85/af/6758e64f2e35774af6ca5679444b7ec6480d31b55864a34530829250ffc6/openexr-3.4.4-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:cca3bb1bd0e94b5654e1ead81cc5eccd7f8336a245d1a8f663a58e339fd178e8", size = 2159371, upload-time = "2025-11-19T22:03:30.693Z" }, + { url = "https://files.pythonhosted.org/packages/b6/17/35b87465ba422661536192f65840c134d4d4b7119666c3094e08bcbb0ce8/openexr-3.4.4-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9f09c29aa634fa438abd59ead775c3ca3fc3d234585f9437add249866cee7b0e", size = 1148062, upload-time = "2025-11-19T22:03:31.991Z" }, + { url = "https://files.pythonhosted.org/packages/fb/51/2759d7728dc38ecc50d22f2ef18ed0120491d9f8937cd332f2bacd487e14/openexr-3.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:01ba479d80a2fb784ab7afed460d922735f2aab8f45ad20cb1d70af5c46ab931", size = 1019630, upload-time = "2025-11-19T22:03:34.562Z" }, + { url = "https://files.pythonhosted.org/packages/ea/0f/7b507bb029510e99c0eb48a42972f70422b114aaf99d944693b4607cca9b/openexr-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:39b09d703a0c631d7b56c91bba7ace2ab7d174e296964dae4c7d186d051c6780", size = 1157649, upload-time = "2025-11-19T22:03:36.147Z" }, + { url = "https://files.pythonhosted.org/packages/69/b0/de87e8ed7c2bdd7914b2728ef2245ea60c806b241b98ad9c1c5819edc68a/openexr-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:292cc335ae44b1ac71c16282767b7cff4b7a6303e894107a53416cf51146d91c", size = 1290388, upload-time = "2025-11-19T22:03:37.592Z" }, + { url = "https://files.pythonhosted.org/packages/49/13/7d5484f652483dad9a0cd45e9557783014f56e50a328b5926f34b8c118d9/openexr-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6663b09c0ad01f17974c50fea192baa07235879ac4a3d7308f5ec5e624c51d9f", size = 2152188, upload-time = "2025-11-19T22:03:38.852Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/1e25f12a3d5f3a0405f087583676489aca84085fc33bd8f0e317f8d53fae/openexr-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c6059f9ab04dfa138ebc27d21a4849e3eacb6bff93479c95d626320694a35ac", size = 2335961, upload-time = "2025-11-19T22:03:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/ed9fc25ccd3f3056cbb32e16c58d042078b800245ebfbb256d18cbb68256/openexr-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:c6a80b0ad7120c01d870caffa680130a7b0d4d2e2396d2e79e14cfaa037245a9", size = 723997, upload-time = "2025-11-19T22:03:41.75Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pandas" -version = "2.3.3" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, -] - -[[package]] -name = "pathspec" -version = "1.0.3" + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/38/db33686f4b5fa64d7af40d96361f6a4615b8c6c8f1b3d334eee46ae6160e/pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd", size = 10334013, upload-time = "2026-01-21T15:50:34.771Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7b/9254310594e9774906bacdd4e732415e1f86ab7dbb4b377ef9ede58cd8ec/pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740", size = 9874154, upload-time = "2026-01-21T15:50:36.67Z" }, + { url = "https://files.pythonhosted.org/packages/63/d4/726c5a67a13bc66643e66d2e9ff115cead482a44fc56991d0c4014f15aaf/pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801", size = 10384433, upload-time = "2026-01-21T15:50:39.132Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/9211f09bedb04f9832122942de8b051804b31a39cfbad199a819bb88d9f3/pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a", size = 10864519, upload-time = "2026-01-21T15:50:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/50858522cdc46ac88b9afdc3015e298959a70a08cd21e008a44e9520180c/pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb", size = 11394124, upload-time = "2026-01-21T15:50:43.377Z" }, + { url = "https://files.pythonhosted.org/packages/86/3f/83b2577db02503cd93d8e95b0f794ad9d4be0ba7cb6c8bcdcac964a34a42/pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f", size = 11920444, upload-time = "2026-01-21T15:50:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/64/2d/4f8a2f192ed12c90a0aab47f5557ece0e56b0370c49de9454a09de7381b2/pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1", size = 9730970, upload-time = "2026-01-21T15:50:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/ff571be435cf1e643ca98d0945d76732c0b4e9c37191a89c8550b105eed1/pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0", size = 9041950, upload-time = "2026-01-21T15:50:50.422Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, + { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, + { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, + { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, +] + +[[package]] +name = "picamera2" +version = "0.3.34" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } +dependencies = [ + { name = "av" }, + { name = "jsonschema" }, + { name = "libarchive-c" }, + { name = "numpy" }, + { name = "openexr" }, + { name = "pidng" }, + { name = "piexif" }, + { name = "pillow" }, + { name = "python-prctl" }, + { name = "simplejpeg" }, + { name = "tqdm" }, + { name = "videodev2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/f3/4cb82c4d39b5573fd19257c6ff08b99b5506a0201d8bbb1cf8f1c67cd8c9/picamera2-0.3.34.tar.gz", hash = "sha256:fa923c2d25a124b1b591b332e10836a08690a1cf6bd233bb25862466d881229d", size = 108661, upload-time = "2026-02-02T13:13:37.752Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, + { url = "https://files.pythonhosted.org/packages/57/eb/ef4ec16258285093e90d26195deb58d5b473cd334f713c2d4691955eaafe/picamera2-0.3.34-py3-none-any.whl", hash = "sha256:08635c902b43cfa6349b2dec460005f92bf4d97655caf3cd57b06fc3bb8b19c5", size = 129389, upload-time = "2026-02-02T13:13:36.615Z" }, +] + +[[package]] +name = "pidng" +version = "4.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/65/2670c465c8a63a23eb3a5e5547262e247e1aa2d3889a0a6781da9109d5f7/pidng-4.0.9.tar.gz", hash = "sha256:560eb008086f8a715fd9e1ab998817a7d4c8500a7f161b9ce6af5ab27501f82c", size = 21907, upload-time = "2022-05-06T19:09:32.093Z" } + +[[package]] +name = "piexif" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/84/a3f25cec7d0922bf60be8000c9739d28d24b6896717f44cc4cfb843b1487/piexif-1.1.3.zip", hash = "sha256:83cb35c606bf3a1ea1a8f0a25cb42cf17e24353fd82e87ae3884e74a302a5f1b", size = 1011134, upload-time = "2019-07-01T15:29:23.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/d8/6f63147dd73373d051c5eb049ecd841207f898f50a5a1d4378594178f6cf/piexif-1.1.3-py2.py3-none-any.whl", hash = "sha256:3bc435d171720150b81b15d27e05e54b8abbde7b4242cddd81ef160d283108b6", size = 20691, upload-time = "2019-07-01T15:43:20.907Z" }, ] [[package]] @@ -1455,58 +1688,103 @@ wheels = [ ] [[package]] -name = "platformdirs" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] [[package]] name = "protobuf" -version = "6.33.4" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/b8/cda15d9d46d03d4aa3a67cb6bffe05173440ccf86a9541afaf7ac59a1b6b/protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91", size = 444346, upload-time = "2026-01-12T18:33:40.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/be/24ef9f3095bacdf95b458543334d0c4908ccdaee5130420bf064492c325f/protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d", size = 425612, upload-time = "2026-01-12T18:33:29.656Z" }, - { url = "https://files.pythonhosted.org/packages/31/ad/e5693e1974a28869e7cd244302911955c1cebc0161eb32dfa2b25b6e96f0/protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc", size = 436962, upload-time = "2026-01-12T18:33:31.345Z" }, - { url = "https://files.pythonhosted.org/packages/66/15/6ee23553b6bfd82670207ead921f4d8ef14c107e5e11443b04caeb5ab5ec/protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0", size = 427612, upload-time = "2026-01-12T18:33:32.646Z" }, - { url = "https://files.pythonhosted.org/packages/2b/48/d301907ce6d0db75f959ca74f44b475a9caa8fcba102d098d3c3dd0f2d3f/protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e", size = 324484, upload-time = "2026-01-12T18:33:33.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/1c/e53078d3f7fe710572ab2dcffd993e1e3b438ae71cfc031b71bae44fcb2d/protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6", size = 339256, upload-time = "2026-01-12T18:33:35.231Z" }, - { url = "https://files.pythonhosted.org/packages/e8/8e/971c0edd084914f7ee7c23aa70ba89e8903918adca179319ee94403701d5/protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9", size = 323311, upload-time = "2026-01-12T18:33:36.305Z" }, - { url = "https://files.pythonhosted.org/packages/75/b1/1dc83c2c661b4c62d56cc081706ee33a4fc2835bd90f965baa2663ef7676/protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc", size = 170532, upload-time = "2026-01-12T18:33:39.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] name = "psutil" -version = "7.2.1" +version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/8e/f0c242053a368c2aa89584ecd1b054a18683f13d6e5a318fc9ec36582c94/psutil-7.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d", size = 129624, upload-time = "2025-12-29T08:26:04.255Z" }, - { url = "https://files.pythonhosted.org/packages/26/97/a58a4968f8990617decee234258a2b4fc7cd9e35668387646c1963e69f26/psutil-7.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49", size = 130132, upload-time = "2025-12-29T08:26:06.228Z" }, - { url = "https://files.pythonhosted.org/packages/db/6d/ed44901e830739af5f72a85fa7ec5ff1edea7f81bfbf4875e409007149bd/psutil-7.2.1-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc", size = 180612, upload-time = "2025-12-29T08:26:08.276Z" }, - { url = "https://files.pythonhosted.org/packages/c7/65/b628f8459bca4efbfae50d4bf3feaab803de9a160b9d5f3bd9295a33f0c2/psutil-7.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf", size = 183201, upload-time = "2025-12-29T08:26:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/fb/23/851cadc9764edcc18f0effe7d0bf69f727d4cf2442deb4a9f78d4e4f30f2/psutil-7.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f", size = 139081, upload-time = "2025-12-29T08:26:12.483Z" }, - { url = "https://files.pythonhosted.org/packages/59/82/d63e8494ec5758029f31c6cb06d7d161175d8281e91d011a4a441c8a43b5/psutil-7.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672", size = 134767, upload-time = "2025-12-29T08:26:14.528Z" }, - { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, - { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, - { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, - { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, - { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -1533,33 +1811,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, @@ -1588,58 +1839,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pyee" +version = "13.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, ] [[package]] @@ -1655,25 +1870,78 @@ wheels = [ ] [[package]] -name = "pymdown-extensions" -version = "10.20" +name = "pygame" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/cc/08bba60f00541f62aaa252ce0cfbd60aebd04616c0b9574f755b583e45ae/pygame-2.6.1.tar.gz", hash = "sha256:56fb02ead529cee00d415c3e007f75e0780c655909aaa8e8bf616ee09c9feb1f", size = 14808125, upload-time = "2024-09-29T13:41:34.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/16/2c602c332f45ff9526d61f6bd764db5096ff9035433e2172e2d2cadae8db/pygame-2.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ee7f2771f588c966fa2fa8b829be26698c9b4836f82ede5e4edc1a68594942e", size = 13118279, upload-time = "2024-09-29T14:26:30.427Z" }, + { url = "https://files.pythonhosted.org/packages/cd/53/77ccbc384b251c6e34bfd2e734c638233922449a7844e3c7a11ef91cee39/pygame-2.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8040ea2ab18c6b255af706ec01355c8a6b08dc48d77fd4ee783f8fc46a843bf", size = 12384524, upload-time = "2024-09-29T14:26:49.996Z" }, + { url = "https://files.pythonhosted.org/packages/06/be/3ed337583f010696c3b3435e89a74fb29d0c74d0931e8f33c0a4246307a9/pygame-2.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47a6938de93fa610accd4969e638c2aebcb29b2fca518a84c3a39d91ab47116", size = 13587123, upload-time = "2024-09-29T11:10:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/b015586a450db59313535662991b34d24c1f0c0dc149cc5f496573900f4e/pygame-2.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33006f784e1c7d7e466fcb61d5489da59cc5f7eb098712f792a225df1d4e229d", size = 14275532, upload-time = "2024-09-29T11:39:59.356Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f2/d31e6ad42d657af07be2ffd779190353f759a07b51232b9e1d724f2cda46/pygame-2.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1206125f14cae22c44565c9d333607f1d9f59487b1f1432945dfc809aeaa3e88", size = 13952653, upload-time = "2024-09-29T11:40:01.781Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/8ea2a6979e6fa971702fece1747e862e2256d4a8558fe0da6364dd946c53/pygame-2.6.1-cp312-cp312-win32.whl", hash = "sha256:84fc4054e25262140d09d39e094f6880d730199710829902f0d8ceae0213379e", size = 10252421, upload-time = "2024-09-29T11:14:26.877Z" }, + { url = "https://files.pythonhosted.org/packages/5f/90/7d766d54bb95939725e9a9361f9c06b0cfbe3fe100aa35400f0a461a278a/pygame-2.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9e7396be0d9633831c3f8d5d82dd63ba373ad65599628294b7a4f8a5a01a65", size = 10624591, upload-time = "2024-09-29T11:52:54.489Z" }, + { url = "https://files.pythonhosted.org/packages/e1/91/718acf3e2a9d08a6ddcc96bd02a6f63c99ee7ba14afeaff2a51c987df0b9/pygame-2.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6039f3a55d800db80e8010f387557b528d34d534435e0871326804df2a62f2", size = 13090765, upload-time = "2024-09-29T14:27:02.377Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c6/9cb315de851a7682d9c7568a41ea042ee98d668cb8deadc1dafcab6116f0/pygame-2.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a3a1288e2e9b1e5834e425bedd5ba01a3cd4902b5c2bff8ed4a740ccfe98171", size = 12381704, upload-time = "2024-09-29T14:27:10.228Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8f/617a1196e31ae3b46be6949fbaa95b8c93ce15e0544266198c2266cc1b4d/pygame-2.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27eb17e3dc9640e4b4683074f1890e2e879827447770470c2aba9f125f74510b", size = 13581091, upload-time = "2024-09-29T11:30:27.653Z" }, + { url = "https://files.pythonhosted.org/packages/3b/87/2851a564e40a2dad353f1c6e143465d445dab18a95281f9ea458b94f3608/pygame-2.6.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c1623180e70a03c4a734deb9bac50fc9c82942ae84a3a220779062128e75f3b", size = 14273844, upload-time = "2024-09-29T11:40:04.138Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/aa23aa2e70bcba42c989c02e7228273c30f3b44b9b264abb93eaeff43ad7/pygame-2.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef07c0103d79492c21fced9ad68c11c32efa6801ca1920ebfd0f15fb46c78b1c", size = 13951197, upload-time = "2024-09-29T11:40:06.785Z" }, + { url = "https://files.pythonhosted.org/packages/a6/06/29e939b34d3f1354738c7d201c51c250ad7abefefaf6f8332d962ff67c4b/pygame-2.6.1-cp313-cp313-win32.whl", hash = "sha256:3acd8c009317190c2bfd81db681ecef47d5eb108c2151d09596d9c7ea9df5c0e", size = 10249309, upload-time = "2024-09-29T11:10:23.329Z" }, + { url = "https://files.pythonhosted.org/packages/7e/11/17f7f319ca91824b86557e9303e3b7a71991ef17fd45286bf47d7f0a38e6/pygame-2.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:813af4fba5d0b2cb8e58f5d95f7910295c34067dcc290d34f1be59c48bd1ea6a", size = 10620084, upload-time = "2024-09-29T11:48:51.587Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pylibsrtp" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown" }, - { name = "pyyaml" }, + { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/a6/6e532bec974aaecbf9fe4e12538489fb1c28456e65088a50f305aeab9f89/pylibsrtp-1.0.0.tar.gz", hash = "sha256:b39dff075b263a8ded5377f2490c60d2af452c9f06c4d061c7a2b640612b34d4", size = 10858, upload-time = "2025-10-13T16:12:31.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" }, + { url = "https://files.pythonhosted.org/packages/aa/af/89e61a62fa3567f1b7883feb4d19e19564066c2fcd41c37e08d317b51881/pylibsrtp-1.0.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:822c30ea9e759b333dc1f56ceac778707c51546e97eb874de98d7d378c000122", size = 1865017, upload-time = "2025-10-13T16:12:15.62Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0e/8d215484a9877adcf2459a8b28165fc89668b034565277fd55d666edd247/pylibsrtp-1.0.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:aaad74e5c8cbc1c32056c3767fea494c1e62b3aea2c908eda2a1051389fdad76", size = 2182739, upload-time = "2025-10-13T16:12:17.121Z" }, + { url = "https://files.pythonhosted.org/packages/57/3f/76a841978877ae13eac0d4af412c13bbd5d83b3df2c1f5f2175f2e0f68e5/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9209b86e662ebbd17c8a9e8549ba57eca92a3e87fb5ba8c0e27b8c43cd08a767", size = 2732922, upload-time = "2025-10-13T16:12:18.348Z" }, + { url = "https://files.pythonhosted.org/packages/0e/14/cf5d2a98a66fdfe258f6b036cda570f704a644fa861d7883a34bc359501e/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:293c9f2ac21a2bd689c477603a1aa235d85cf252160e6715f0101e42a43cbedc", size = 2434534, upload-time = "2025-10-13T16:12:20.074Z" }, + { url = "https://files.pythonhosted.org/packages/bd/08/a3f6e86c04562f7dce6717cd2206a0f84ca85c5e38121d998e0e330194c3/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_28_i686.whl", hash = "sha256:81fb8879c2e522021a7cbd3f4bda1b37c192e1af939dfda3ff95b4723b329663", size = 2345818, upload-time = "2025-10-13T16:12:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d5/130c2b5b4b51df5631684069c6f0a6761c59d096a33d21503ac207cf0e47/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4ddb562e443cf2e557ea2dfaeef0d7e6b90e96dd38eb079b4ab2c8e34a79f50b", size = 2774490, upload-time = "2025-10-13T16:12:22.659Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/715a453bfee3bea92a243888ad359094a7727cc6d393f21281320fe7798c/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:f02e616c9dfab2b03b32d8cc7b748f9d91814c0211086f987629a60f05f6e2cc", size = 2372603, upload-time = "2025-10-13T16:12:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/e3/56/52fa74294254e1f53a4ff170ee2006e57886cf4bb3db46a02b4f09e1d99f/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c134fa09e7b80a5b7fed626230c5bc257fd771bd6978e754343e7a61d96bc7e6", size = 2451269, upload-time = "2025-10-13T16:12:25.475Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/2e9b34f484cbdd3bac999bf1f48b696d7389433e900639089e8fc4e0da0d/pylibsrtp-1.0.0-cp310-abi3-win32.whl", hash = "sha256:bae377c3b402b17b9bbfbfe2534c2edba17aa13bea4c64ce440caacbe0858b55", size = 1247503, upload-time = "2025-10-13T16:12:27.39Z" }, + { url = "https://files.pythonhosted.org/packages/c3/70/43db21af194580aba2d9a6d4c7bd8c1a6e887fa52cd810b88f89096ecad2/pylibsrtp-1.0.0-cp310-abi3-win_amd64.whl", hash = "sha256:8d6527c4a78a39a8d397f8862a8b7cdad4701ee866faf9de4ab8c70be61fd34d", size = 1601659, upload-time = "2025-10-13T16:12:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ec/6e02b2561d056ea5b33046e3cad21238e6a9097b97d6ccc0fbe52b50c858/pylibsrtp-1.0.0-cp310-abi3-win_arm64.whl", hash = "sha256:2696bdb2180d53ac55d0eb7b58048a2aa30cd4836dd2ca683669889137a94d2a", size = 1159246, upload-time = "2025-10-13T16:12:30.285Z" }, +] + +[[package]] +name = "pyopenssl" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, ] [[package]] name = "pyparsing" -version = "3.3.1" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -1716,13 +1984,10 @@ wheels = [ ] [[package]] -name = "pytz" -version = "2025.2" +name = "python-prctl" +version = "1.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] +sdist = { url = "https://files.pythonhosted.org/packages/c0/99/be5393cfe9c16376b4f515d90a68b11f1840143ac1890e9008bc176cf6a6/python-prctl-1.8.1.tar.gz", hash = "sha256:b4ca9a25a7d4f1ace4fffd1f3a2e64ef5208fe05f929f3edd5e27081ca7e67ce", size = 28033, upload-time = "2020-11-02T19:30:25.257Z" } [[package]] name = "pyusb" @@ -1733,46 +1998,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl", hash = "sha256:bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430", size = 58465, upload-time = "2025-01-08T23:45:00.029Z" }, ] -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, -] - -[[package]] -name = "pyyaml-env-tag" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, -] - [[package]] name = "pyzmq" version = "27.1.0" @@ -1807,18 +2032,82 @@ wheels = [ ] [[package]] -name = "requests" -version = "2.32.5" +name = "referencing" +version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "rich" +version = "14.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, ] [[package]] @@ -1879,11 +2168,65 @@ wheels = [ [[package]] name = "setuptools" -version = "80.9.0" +version = "80.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, +] + +[[package]] +name = "simplejpeg" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/64/da60f0ba80570f9a36c9b6e055f4364bda2c547715296d5773d2ea6d5a60/simplejpeg-1.9.0.tar.gz", hash = "sha256:5ac7d9489eeb812c2e7ea5c283994a29d9fefdfe5ed7b86c09d485e0dd366689", size = 3965764, upload-time = "2025-10-10T10:58:08.197Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/a932dc6a89cdfd8cdfbd300340d87164eb3daaaf6a1b86b09bf0b87e0c2a/simplejpeg-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f218b4810f0dcb573bf323dae73177961c235c79588657927d7893a714636ca2", size = 424657, upload-time = "2025-10-10T10:57:36.676Z" }, + { url = "https://files.pythonhosted.org/packages/44/73/53f7d2e0ce86c9b850301c1c9165dedbac9ac88a6045aa1cb8ad37176c17/simplejpeg-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f987b5783e0d649457acf136a4544a75f6d40f15cba89b6c5a4583ccf5577957", size = 401461, upload-time = "2025-10-10T10:57:37.963Z" }, + { url = "https://files.pythonhosted.org/packages/75/c1/0cbf167e3efa32adfbb0674a3504eb118cc5bdc372a44ee937c30324188e/simplejpeg-1.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08ab337ca3b26d7562f5ad686ab8f3966fb206fced607d248e693cbc57fc53b3", size = 448908, upload-time = "2025-10-10T10:57:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/03/80/44514f83a09500d1eb8ebba8cadd9aa16f7a60690c19dbd98a570ca2c0ec/simplejpeg-1.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5be1c8932f43f99b6cc52f8ac4c28e3ac19a1a830351efdb159715fd683e2053", size = 407547, upload-time = "2025-10-10T10:57:40.867Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d7/115be2e87257c1e148c0f911c020c6442eafb8d164cbd642327d21f22179/simplejpeg-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:808b6840f1c6d4de20ae7a086cf9bf49eccac6ef6658df34b4948e071cbe9680", size = 293810, upload-time = "2025-10-10T10:57:42.498Z" }, + { url = "https://files.pythonhosted.org/packages/49/21/6a4c1589fbcde51a349ef7a629af5867701011bced774389a4f6782ef6cd/simplejpeg-1.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:b65fdde80097cb1fad9c6dad6a12767215c311704f7fad321fbd8501219fad06", size = 253182, upload-time = "2025-10-10T10:57:44.051Z" }, + { url = "https://files.pythonhosted.org/packages/e3/32/c2d5baa4af82551feae9082d1800c7c7e96586f67292dad4e1442298ad34/simplejpeg-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b4e8e0d68caa3e0962415daff12df2911df36a697e53a75878a45e9e34e9ad", size = 423518, upload-time = "2025-10-10T10:57:45.291Z" }, + { url = "https://files.pythonhosted.org/packages/84/97/6a4018d4c1c980d9f4c48c29d3d6bfaeb18444dd8e82997246c9950fb79a/simplejpeg-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:475d1932f50264d63dbc752678b5a6629ed8c6b0f5edfbe4e9cd7881d5f8a1f1", size = 400574, upload-time = "2025-10-10T10:57:46.475Z" }, + { url = "https://files.pythonhosted.org/packages/88/8b/d8ca384f1362371d61690d7460d3ae4cec4a5a25d9eb06cd15623de3725a/simplejpeg-1.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0c375130f73bb08229a3ded392d84ee2d916b3e87e7ec5d2ac4e47b7144346a", size = 448142, upload-time = "2025-10-10T10:57:47.894Z" }, + { url = "https://files.pythonhosted.org/packages/cf/0a/58d6d8e997ee01486cfcfd4406a74638f2f63bb65122694b10411dadf1d5/simplejpeg-1.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d00feb1cc0348aba0a41db6dbda4db468db92099b1b3d473159e6f68aa990795", size = 406252, upload-time = "2025-10-10T10:57:49.158Z" }, + { url = "https://files.pythonhosted.org/packages/ae/12/c95aef82037bd2082e9a35b949352e9d8477afec540fefe48c7502114bca/simplejpeg-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b58f81133040ff7103dee90bb4f949e34456084f86347fb388505f3a0a42895", size = 293831, upload-time = "2025-10-10T10:57:50.576Z" }, + { url = "https://files.pythonhosted.org/packages/84/cd/41e96d4b82a20d2d448a55a21831c1e57c920f7da485850717da7cf5036a/simplejpeg-1.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:acf6acd6c41a4a42fd9d89cf4d3f3d6a072d0eb5dbc231c1620e165f79a8cad5", size = 253131, upload-time = "2025-10-10T10:57:51.754Z" }, +] + +[[package]] +name = "simulation" +version = "0.1.0" +source = { editable = "src/simulation" } +dependencies = [ + { name = "gymnasium" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "onnxruntime" }, + { name = "onnxscript" }, + { name = "psutil" }, + { name = "stable-baselines3", extra = ["extra"] }, + { name = "torch" }, + { name = "torchvision" }, +] + +[package.metadata] +requires-dist = [ + { name = "gymnasium", specifier = ">=1.2.3" }, + { name = "matplotlib", specifier = ">=3.10.8" }, + { name = "numpy", specifier = ">=2.4.1" }, + { name = "onnx", specifier = ">=1.20.1" }, + { name = "onnxruntime", specifier = ">=1.23.2" }, + { name = "onnxscript", specifier = ">=0.5.7" }, + { name = "psutil", specifier = ">=7.2.1" }, + { name = "stable-baselines3", extras = ["extra"], specifier = ">=2.7.1" }, + { name = "torch", specifier = ">=2.10.0" }, + { name = "torchvision", specifier = ">=0.25.0" }, ] [[package]] @@ -1910,24 +2253,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a5/cf/2e1d6805da6f9c9b3a4358076ff2e072d828ba7fed124edc1b729e210c55/smbus2-0.6.0-py2.py3-none-any.whl", hash = "sha256:03d83d2a9a4afc5ddca0698ccabf101cb3de52bc5aefd7b76778ffb27ff654e0", size = 11849, upload-time = "2025-12-20T09:02:51.219Z" }, ] -[[package]] -name = "smmap" -version = "5.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, -] - -[[package]] -name = "soupsieve" -version = "2.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" }, -] - [[package]] name = "spidev" version = "3.8" @@ -1951,6 +2276,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/cc/a3038d3833f329dcd03b2dce8b778e4b41044caff88b48429473b8629623/stable_baselines3-2.7.1-py3-none-any.whl", hash = "sha256:b017e76dfe5ca0ce6eabb29e79c42e8c7e125d5862bfcd43ce04ec19732348d0", size = 188039, upload-time = "2025-12-05T11:22:00.819Z" }, ] +[package.optional-dependencies] +extra = [ + { name = "ale-py" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "psutil" }, + { name = "pygame" }, + { name = "rich" }, + { name = "tensorboard" }, + { name = "tqdm" }, +] + [[package]] name = "starlette" version = "0.50.0" @@ -1992,11 +2329,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/21/0127cb9ecbc281c5b5a79d4be7a61e2d35442f72baaa1594e089dbe9206a/sysv_ipc-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fc541299c3af8351abff804e287a0c203338c140f70ee70855f46a1710cc0ff7", size = 71575, upload-time = "2026-01-09T14:04:32.011Z" }, ] +[[package]] +name = "tensorboard" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "grpcio" }, + { name = "markdown" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "setuptools" }, + { name = "tensorboard-data-server" }, + { name = "werkzeug" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, +] + [[package]] name = "torch" -version = "2.9.1" +version = "2.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, @@ -2022,28 +2390,66 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/27/07c645c7673e73e53ded71705045d6cb5bae94c4b021b03aa8d03eee90ab/torch-2.9.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:da5f6f4d7f4940a173e5572791af238cb0b9e21b1aab592bd8b26da4c99f1cd6", size = 104126592, upload-time = "2025-11-12T15:20:41.62Z" }, - { url = "https://files.pythonhosted.org/packages/19/17/e377a460603132b00760511299fceba4102bd95db1a0ee788da21298ccff/torch-2.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:27331cd902fb4322252657f3902adf1c4f6acad9dcad81d8df3ae14c7c4f07c4", size = 899742281, upload-time = "2025-11-12T15:22:17.602Z" }, - { url = "https://files.pythonhosted.org/packages/b1/1a/64f5769025db846a82567fa5b7d21dba4558a7234ee631712ee4771c436c/torch-2.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:81a285002d7b8cfd3fdf1b98aa8df138d41f1a8334fd9ea37511517cedf43083", size = 110940568, upload-time = "2025-11-12T15:21:18.689Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ab/07739fd776618e5882661d04c43f5b5586323e2f6a2d7d84aac20d8f20bd/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:c0d25d1d8e531b8343bea0ed811d5d528958f1dcbd37e7245bc686273177ad7e", size = 74479191, upload-time = "2025-11-12T15:21:25.816Z" }, - { url = "https://files.pythonhosted.org/packages/20/60/8fc5e828d050bddfab469b3fe78e5ab9a7e53dda9c3bdc6a43d17ce99e63/torch-2.9.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c29455d2b910b98738131990394da3e50eea8291dfeb4b12de71ecf1fdeb21cb", size = 104135743, upload-time = "2025-11-12T15:21:34.936Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b7/6d3f80e6918213babddb2a37b46dbb14c15b14c5f473e347869a51f40e1f/torch-2.9.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:524de44cd13931208ba2c4bde9ec7741fd4ae6bfd06409a604fc32f6520c2bc9", size = 899749493, upload-time = "2025-11-12T15:24:36.356Z" }, - { url = "https://files.pythonhosted.org/packages/a6/47/c7843d69d6de8938c1cbb1eba426b1d48ddf375f101473d3e31a5fc52b74/torch-2.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:545844cc16b3f91e08ce3b40e9c2d77012dd33a48d505aed34b7740ed627a1b2", size = 110944162, upload-time = "2025-11-12T15:21:53.151Z" }, - { url = "https://files.pythonhosted.org/packages/28/0e/2a37247957e72c12151b33a01e4df651d9d155dd74d8cfcbfad15a79b44a/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5be4bf7496f1e3ffb1dd44b672adb1ac3f081f204c5ca81eba6442f5f634df8e", size = 74830751, upload-time = "2025-11-12T15:21:43.792Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f7/7a18745edcd7b9ca2381aa03353647bca8aace91683c4975f19ac233809d/torch-2.9.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:30a3e170a84894f3652434b56d59a64a2c11366b0ed5776fab33c2439396bf9a", size = 104142929, upload-time = "2025-11-12T15:21:48.319Z" }, - { url = "https://files.pythonhosted.org/packages/f4/dd/f1c0d879f2863ef209e18823a988dc7a1bf40470750e3ebe927efdb9407f/torch-2.9.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8301a7b431e51764629208d0edaa4f9e4c33e6df0f2f90b90e261d623df6a4e2", size = 899748978, upload-time = "2025-11-12T15:23:04.568Z" }, - { url = "https://files.pythonhosted.org/packages/1f/9f/6986b83a53b4d043e36f3f898b798ab51f7f20fdf1a9b01a2720f445043d/torch-2.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2e1c42c0ae92bf803a4b2409fdfed85e30f9027a66887f5e7dcdbc014c7531db", size = 111176995, upload-time = "2025-11-12T15:22:01.618Z" }, - { url = "https://files.pythonhosted.org/packages/40/60/71c698b466dd01e65d0e9514b5405faae200c52a76901baf6906856f17e4/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:2c14b3da5df416cf9cb5efab83aa3056f5b8cd8620b8fde81b4987ecab730587", size = 74480347, upload-time = "2025-11-12T15:21:57.648Z" }, + { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, + { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, + { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, + { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5c/dee910b87c4d5c0fcb41b50839ae04df87c1cfc663cf1b5fca7ea565eeaa/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294", size = 79498198, upload-time = "2026-01-21T16:24:34.704Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6f/f2e91e34e3fcba2e3fc8d8f74e7d6c22e74e480bbd1db7bc8900fdf3e95c/torch-2.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5c4d217b14741e40776dd7074d9006fd28b8a97ef5654db959d8635b2fe5f29b", size = 146004247, upload-time = "2026-01-21T16:24:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/98/fb/5160261aeb5e1ee12ee95fe599d0541f7c976c3701d607d8fc29e623229f/torch-2.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6b71486353fce0f9714ca0c9ef1c850a2ae766b409808acd58e9678a3edb7738", size = 915716445, upload-time = "2026-01-21T16:22:45.353Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/502fb1b41e6d868e8deb5b0e3ae926bbb36dab8ceb0d1b769b266ad7b0c3/torch-2.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2ee399c644dc92ef7bc0d4f7e74b5360c37cdbe7c5ba11318dda49ffac2bc57", size = 113757050, upload-time = "2026-01-21T16:24:19.204Z" }, + { url = "https://files.pythonhosted.org/packages/1a/0b/39929b148f4824bc3ad6f9f72a29d4ad865bcf7ebfc2fa67584773e083d2/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:3202429f58309b9fa96a614885eace4b7995729f44beb54d3e4a47773649d382", size = 79851305, upload-time = "2026-01-21T16:24:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d8/14/21fbce63bc452381ba5f74a2c0a959fdf5ad5803ccc0c654e752e0dbe91a/torch-2.10.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:aae1b29cd68e50a9397f5ee897b9c24742e9e306f88a807a27d617f07adb3bd8", size = 146005472, upload-time = "2026-01-21T16:22:29.022Z" }, + { url = "https://files.pythonhosted.org/packages/54/fd/b207d1c525cb570ef47f3e9f836b154685011fce11a2f444ba8a4084d042/torch-2.10.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f", size = 915612644, upload-time = "2026-01-21T16:21:47.019Z" }, + { url = "https://files.pythonhosted.org/packages/36/53/0197f868c75f1050b199fe58f9bf3bf3aecac9b4e85cc9c964383d745403/torch-2.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8", size = 113997015, upload-time = "2026-01-21T16:23:00.767Z" }, + { url = "https://files.pythonhosted.org/packages/0e/13/e76b4d9c160e89fff48bf16b449ea324bda84745d2ab30294c37c2434c0d/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f", size = 79498248, upload-time = "2026-01-21T16:23:09.315Z" }, +] + +[[package]] +name = "torchvision" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/3a/6ea0d73f49a9bef38a1b3a92e8dd455cea58470985d25635beab93841748/torchvision-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b", size = 1874920, upload-time = "2026-01-21T16:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/51/f8/c0e1ef27c66e15406fece94930e7d6feee4cb6374bbc02d945a630d6426e/torchvision-0.25.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b75deafa2dfea3e2c2a525559b04783515e3463f6e830cb71de0fb7ea36fe233", size = 2344556, upload-time = "2026-01-21T16:27:40.125Z" }, + { url = "https://files.pythonhosted.org/packages/68/2f/f24b039169db474e8688f649377de082a965fbf85daf4e46c44412f1d15a/torchvision-0.25.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f25aa9e380865b11ea6e9d99d84df86b9cc959f1a007cd966fc6f1ab2ed0e248", size = 8072351, upload-time = "2026-01-21T16:27:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/16/8f650c2e288977cf0f8f85184b90ee56ed170a4919347fc74ee99286ed6f/torchvision-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3", size = 4303059, upload-time = "2026-01-21T16:27:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5b/1562a04a6a5a4cf8cf40016a0cdeda91ede75d6962cff7f809a85ae966a5/torchvision-0.25.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:24e11199e4d84ba9c5ee7825ebdf1cd37ce8deec225117f10243cae984ced3ec", size = 1874918, upload-time = "2026-01-21T16:27:39.02Z" }, + { url = "https://files.pythonhosted.org/packages/36/b1/3d6c42f62c272ce34fcce609bb8939bdf873dab5f1b798fd4e880255f129/torchvision-0.25.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f271136d2d2c0b7a24c5671795c6e4fd8da4e0ea98aeb1041f62bc04c4370ef", size = 2309106, upload-time = "2026-01-21T16:27:30.624Z" }, + { url = "https://files.pythonhosted.org/packages/c7/60/59bb9c8b67cce356daeed4cb96a717caa4f69c9822f72e223a0eae7a9bd9/torchvision-0.25.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:855c0dc6d37f462482da7531c6788518baedca1e0847f3df42a911713acdfe52", size = 8071522, upload-time = "2026-01-21T16:27:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/32/a5/9a9b1de0720f884ea50dbf9acb22cbe5312e51d7b8c4ac6ba9b51efd9bba/torchvision-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:cef0196be31be421f6f462d1e9da1101be7332d91984caa6f8022e6c78a5877f", size = 4321911, upload-time = "2026-01-21T16:27:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/52/99/dca81ed21ebaeff2b67cc9f815a20fdaa418b69f5f9ea4c6ed71721470db/torchvision-0.25.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a8f8061284395ce31bcd460f2169013382ccf411148ceb2ee38e718e9860f5a7", size = 1896209, upload-time = "2026-01-21T16:27:32.159Z" }, + { url = "https://files.pythonhosted.org/packages/28/cc/2103149761fdb4eaed58a53e8437b2d716d48f05174fab1d9fcf1e2a2244/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:146d02c9876858420adf41f3189fe90e3d6a409cbfa65454c09f25fb33bf7266", size = 2310735, upload-time = "2026-01-21T16:27:22.327Z" }, + { url = "https://files.pythonhosted.org/packages/76/ad/f4c985ad52ddd3b22711c588501be1b330adaeaf6850317f66751711b78c/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c4d395cb2c4a2712f6eb93a34476cdf7aae74bb6ea2ea1917f858e96344b00aa", size = 8089557, upload-time = "2026-01-21T16:27:27.666Z" }, + { url = "https://files.pythonhosted.org/packages/63/cc/0ea68b5802e5e3c31f44b307e74947bad5a38cc655231d845534ed50ddb8/torchvision-0.25.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5e6b449e9fa7d642142c0e27c41e5a43b508d57ed8e79b7c0a0c28652da8678c", size = 4344260, upload-time = "2026-01-21T16:27:17.018Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] name = "triton" -version = "3.5.1" +version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/50/9a8358d3ef58162c0a415d173cfb45b67de60176e1024f71fbc4d24c0b6d/triton-3.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2c6b915a03888ab931a9fd3e55ba36785e1fe70cbea0b40c6ef93b20fc85232", size = 170470207, upload-time = "2025-11-11T17:41:00.253Z" }, - { url = "https://files.pythonhosted.org/packages/27/46/8c3bbb5b0a19313f50edcaa363b599e5a1a5ac9683ead82b9b80fe497c8d/triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4346b6ebbd4fad18773f5ba839114f4826037c9f2f34e0148894cd5dd3dba", size = 170470410, upload-time = "2025-11-11T17:41:06.319Z" }, - { url = "https://files.pythonhosted.org/packages/37/92/e97fcc6b2c27cdb87ce5ee063d77f8f26f19f06916aa680464c8104ef0f6/triton-3.5.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d2c70127fca6a23e247f9348b8adde979d2e7a20391bfbabaac6aebc7e6a8", size = 170579924, upload-time = "2025-11-11T17:41:12.455Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, ] [[package]] @@ -2076,15 +2482,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] - [[package]] name = "uvicorn" version = "0.40.0" @@ -2092,7 +2489,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } wheels = [ @@ -2100,63 +2496,20 @@ wheels = [ ] [[package]] -name = "watchdog" -version = "6.0.0" +name = "videodev2" +version = "0.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/82/ffdba8838b1f24b83268863a8f66fe9334d7f28a5b9c368f9c48f7516e69/videodev2-0.0.4.tar.gz", hash = "sha256:c34ba70491d148c23a08cbacd8efabeb413cff5baa943a7548ac4abd1eb19e2a", size = 50108, upload-time = "2025-07-23T10:18:51.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, + { url = "https://files.pythonhosted.org/packages/68/30/4982441a03860ab8f656702d8a2c13d0cf6f56d65bfb78fe288028dcb473/videodev2-0.0.4-py3-none-any.whl", hash = "sha256:d35f7ab39ddb06d50fec96a99bfc8d5b8b525bc7ea03788259d386393f1a64ba", size = 49923, upload-time = "2025-07-23T10:18:50.378Z" }, ] -[[package]] -name = "websocket" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gevent" }, - { name = "greenlet" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/6d/a60d620ea575c885510c574909d2e3ed62129b121fa2df00ca1c81024c87/websocket-0.2.1.tar.gz", hash = "sha256:42b506fae914ac5ed654e23ba9742e6a342b1a1c3eb92632b6166c65256469a4", size = 195339, upload-time = "2010-12-03T11:51:30.867Z" } - [[package]] name = "websockets" version = "16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, - { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, - { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, - { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, - { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, - { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, - { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, - { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, - { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, @@ -2175,32 +2528,83 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, - { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, - { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, - { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, - { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, - { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, - { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] +[[package]] +name = "werkzeug" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + [[package]] name = "zmq" version = "0.0.0" @@ -2209,50 +2613,3 @@ dependencies = [ { name = "pyzmq" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6e/78/833b2808793c1619835edb1a4e17a023d5d625f4f97ff25ffff986d1f472/zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9", size = 966, upload-time = "2015-05-21T17:34:26.603Z" } - -[[package]] -name = "zope-event" -version = "6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/33/d3eeac228fc14de76615612ee208be2d8a5b5b0fada36bf9b62d6b40600c/zope_event-6.1.tar.gz", hash = "sha256:6052a3e0cb8565d3d4ef1a3a7809336ac519bc4fe38398cb8d466db09adef4f0", size = 18739, upload-time = "2025-11-07T08:05:49.934Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/b0/956902e5e1302f8c5d124e219c6bf214e2649f92ad5fce85b05c039a04c9/zope_event-6.1-py3-none-any.whl", hash = "sha256:0ca78b6391b694272b23ec1335c0294cc471065ed10f7f606858fc54566c25a0", size = 6414, upload-time = "2025-11-07T08:05:48.874Z" }, -] - -[[package]] -name = "zope-interface" -version = "8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/a4/77daa5ba398996d16bb43fc721599d27d03eae68fe3c799de1963c72e228/zope_interface-8.2.tar.gz", hash = "sha256:afb20c371a601d261b4f6edb53c3c418c249db1a9717b0baafc9a9bb39ba1224", size = 254019, upload-time = "2026-01-09T07:51:07.253Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/fa/6d9eb3a33998a3019d7eb4fa1802d01d6602fad90e0aea443e6e0fe8e49a/zope_interface-8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:788c293f3165964ec6527b2d861072c68eef53425213f36d3893ebee89a89623", size = 207541, upload-time = "2026-01-09T08:04:55.378Z" }, - { url = "https://files.pythonhosted.org/packages/19/8c/ad23c96fdee84cb1f768f6695dac187cc26e9038e01c69713ba0f7dc46ab/zope_interface-8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9a4e785097e741a1c953b3970ce28f2823bd63c00adc5d276f2981dd66c96c15", size = 208075, upload-time = "2026-01-09T08:04:57.118Z" }, - { url = "https://files.pythonhosted.org/packages/dd/35/1bfd5fec31a307f0cf4065ee74ade63858ded3e2a71e248f1508118fcc95/zope_interface-8.2-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:16c69da19a06566664ddd4785f37cad5693a51d48df1515d264c20d005d322e2", size = 249528, upload-time = "2026-01-09T08:04:59.074Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3a/5d50b5fdb0f8226a2edff6adb7efdd3762ec95dff827dbab1761cb9a9e85/zope_interface-8.2-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c31acfa3d7cde48bec45701b0e1f4698daffc378f559bfb296837d8c834732f6", size = 254646, upload-time = "2026-01-09T08:05:00.964Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2a/ee7d675e151578eaf77828b8faac2b7ed9a69fead350bf5cf0e4afe7c73d/zope_interface-8.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0723507127f8269b8f3f22663168f717e9c9742107d1b6c9f419df561b71aa6d", size = 255083, upload-time = "2026-01-09T08:05:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/5d/07/99e2342f976c3700e142eddc01524e375a9e9078869a6885d9c72f3a3659/zope_interface-8.2-cp310-cp310-win_amd64.whl", hash = "sha256:3bf73a910bb27344def2d301a03329c559a79b308e1e584686b74171d736be4e", size = 211924, upload-time = "2026-01-09T08:05:04.702Z" }, - { url = "https://files.pythonhosted.org/packages/98/97/9c2aa8caae79915ed64eb114e18816f178984c917aa9adf2a18345e4f2e5/zope_interface-8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c65ade7ea85516e428651048489f5e689e695c79188761de8c622594d1e13322", size = 208081, upload-time = "2026-01-09T08:05:06.623Z" }, - { url = "https://files.pythonhosted.org/packages/34/86/4e2fcb01a8f6780ac84923748e450af0805531f47c0956b83065c99ab543/zope_interface-8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1ef4b43659e1348f35f38e7d1a6bbc1682efde239761f335ffc7e31e798b65b", size = 208522, upload-time = "2026-01-09T08:05:07.986Z" }, - { url = "https://files.pythonhosted.org/packages/f6/eb/08e277da32ddcd4014922854096cf6dcb7081fad415892c2da1bedefbf02/zope_interface-8.2-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:dfc4f44e8de2ff4eba20af4f0a3ca42d3c43ab24a08e49ccd8558b7a4185b466", size = 255198, upload-time = "2026-01-09T08:05:09.532Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a1/b32484f3281a5dc83bc713ad61eca52c543735cdf204543172087a074a74/zope_interface-8.2-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8f094bfb49179ec5dc9981cb769af1275702bd64720ef94874d9e34da1390d4c", size = 259970, upload-time = "2026-01-09T08:05:11.477Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/bca0e8ae1e487d4093a8a7cfed2118aa2d4758c8cfd66e59d2af09d71f1c/zope_interface-8.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d2bb8e7364e18f083bf6744ccf30433b2a5f236c39c95df8514e3c13007098ce", size = 261153, upload-time = "2026-01-09T08:05:13.402Z" }, - { url = "https://files.pythonhosted.org/packages/40/1e/e3ff2a708011e56b10b271b038d4cb650a8ad5b7d24352fe2edf6d6b187a/zope_interface-8.2-cp311-cp311-win_amd64.whl", hash = "sha256:6f4b4dfcfdfaa9177a600bb31cebf711fdb8c8e9ed84f14c61c420c6aa398489", size = 212330, upload-time = "2026-01-09T08:05:15.267Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a0/1e1fabbd2e9c53ef92b69df6d14f4adc94ec25583b1380336905dc37e9a0/zope_interface-8.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:624b6787fc7c3e45fa401984f6add2c736b70a7506518c3b537ffaacc4b29d4c", size = 208785, upload-time = "2026-01-09T08:05:17.348Z" }, - { url = "https://files.pythonhosted.org/packages/c3/2a/88d098a06975c722a192ef1fb7d623d1b57c6a6997cf01a7aabb45ab1970/zope_interface-8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc9ded9e97a0ed17731d479596ed1071e53b18e6fdb2fc33af1e43f5fd2d3aaa", size = 208976, upload-time = "2026-01-09T08:05:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e8/757398549fdfd2f8c89f32c82ae4d2f0537ae2a5d2f21f4a2f711f5a059f/zope_interface-8.2-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:532367553e4420c80c0fc0cabcc2c74080d495573706f66723edee6eae53361d", size = 259411, upload-time = "2026-01-09T08:05:20.567Z" }, - { url = "https://files.pythonhosted.org/packages/91/af/502601f0395ce84dff622f63cab47488657a04d0065547df42bee3a680ff/zope_interface-8.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2bf9cf275468bafa3c72688aad8cfcbe3d28ee792baf0b228a1b2d93bd1d541a", size = 264859, upload-time = "2026-01-09T08:05:22.234Z" }, - { url = "https://files.pythonhosted.org/packages/89/0c/d2f765b9b4814a368a7c1b0ac23b68823c6789a732112668072fe596945d/zope_interface-8.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0009d2d3c02ea783045d7804da4fd016245e5c5de31a86cebba66dd6914d59a2", size = 264398, upload-time = "2026-01-09T08:05:23.853Z" }, - { url = "https://files.pythonhosted.org/packages/4a/81/2f171fbc4222066957e6b9220c4fb9146792540102c37e6d94e5d14aad97/zope_interface-8.2-cp312-cp312-win_amd64.whl", hash = "sha256:845d14e580220ae4544bd4d7eb800f0b6034fe5585fc2536806e0a26c2ee6640", size = 212444, upload-time = "2026-01-09T08:05:25.148Z" }, - { url = "https://files.pythonhosted.org/packages/66/47/45188fb101fa060b20e6090e500682398ab415e516a0c228fbb22bc7def2/zope_interface-8.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:6068322004a0158c80dfd4708dfb103a899635408c67c3b10e9acec4dbacefec", size = 209170, upload-time = "2026-01-09T08:05:26.616Z" }, - { url = "https://files.pythonhosted.org/packages/09/03/f6b9336c03c2b48403c4eb73a1ec961d94dc2fb5354c583dfb5fa05fd41f/zope_interface-8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2499de92e8275d0dd68f84425b3e19e9268cd1fa8507997900fa4175f157733c", size = 209229, upload-time = "2026-01-09T08:05:28.521Z" }, - { url = "https://files.pythonhosted.org/packages/07/b1/65fe1dca708569f302ade02e6cdca309eab6752bc9f80105514f5b708651/zope_interface-8.2-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f777e68c76208503609c83ca021a6864902b646530a1a39abb9ed310d1100664", size = 259393, upload-time = "2026-01-09T08:05:29.897Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a5/97b49cfceb6ed53d3dcfb3f3ebf24d83b5553194f0337fbbb3a9fec6cf78/zope_interface-8.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b05a919fdb0ed6ea942e5a7800e09a8b6cdae6f98fee1bef1c9d1a3fc43aaa0", size = 264863, upload-time = "2026-01-09T08:05:31.501Z" }, - { url = "https://files.pythonhosted.org/packages/cb/02/0b7a77292810efe3a0586a505b077ebafd5114e10c6e6e659f0c8e387e1f/zope_interface-8.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ccc62b5712dd7bd64cfba3ee63089fb11e840f5914b990033beeae3b2180b6cb", size = 264369, upload-time = "2026-01-09T08:05:32.941Z" }, - { url = "https://files.pythonhosted.org/packages/fb/1d/0d1ff3846302ed1b5bbf659316d8084b30106770a5f346b7ff4e9f540f80/zope_interface-8.2-cp313-cp313-win_amd64.whl", hash = "sha256:34f877d1d3bb7565c494ed93828fa6417641ca26faf6e8f044e0d0d500807028", size = 212447, upload-time = "2026-01-09T08:05:35.064Z" }, - { url = "https://files.pythonhosted.org/packages/1a/da/3c89de3917751446728b8898b4d53318bc2f8f6bf8196e150a063c59905e/zope_interface-8.2-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:46c7e4e8cbc698398a67e56ca985d19cb92365b4aafbeb6a712e8c101090f4cb", size = 209223, upload-time = "2026-01-09T08:05:36.449Z" }, - { url = "https://files.pythonhosted.org/packages/00/7f/62d00ec53f0a6e5df0c984781e6f3999ed265129c4c3413df8128d1e0207/zope_interface-8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a87fc7517f825a97ff4a4ca4c8a950593c59e0f8e7bfe1b6f898a38d5ba9f9cf", size = 209366, upload-time = "2026-01-09T08:05:38.197Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a2/f241986315174be8e00aabecfc2153cf8029c1327cab8ed53a9d979d7e08/zope_interface-8.2-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:ccf52f7d44d669203c2096c1a0c2c15d52e36b2e7a9413df50f48392c7d4d080", size = 261037, upload-time = "2026-01-09T08:05:39.568Z" }, - { url = "https://files.pythonhosted.org/packages/02/cc/b321c51d6936ede296a1b8860cf173bee2928357fe1fff7f97234899173f/zope_interface-8.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aae807efc7bd26302eb2fea05cd6de7d59269ed6ae23a6de1ee47add6de99b8c", size = 264219, upload-time = "2026-01-09T08:05:41.624Z" }, - { url = "https://files.pythonhosted.org/packages/ab/fb/5f5e7b40a2f4efd873fe173624795ca47eaa22e29051270c981361b45209/zope_interface-8.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05a0e42d6d830f547e114de2e7cd15750dc6c0c78f8138e6c5035e51ddfff37c", size = 264390, upload-time = "2026-01-09T08:05:42.936Z" }, - { url = "https://files.pythonhosted.org/packages/f9/82/3f2bc594370bc3abd58e5f9085d263bf682a222f059ed46275cde0570810/zope_interface-8.2-cp314-cp314-win_amd64.whl", hash = "sha256:561ce42390bee90bae51cf1c012902a8033b2aaefbd0deed81e877562a116d48", size = 212585, upload-time = "2026-01-09T08:05:44.419Z" }, -]