├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── linak_controller ├── __init__.py ├── config.py ├── desk.py ├── example │ └── config.yaml ├── gatt.py ├── main.py └── util.py ├── poetry.lock ├── pyproject.toml ├── recipes ├── RECIPES.md ├── docker │ ├── .gitignore │ ├── Dockerfile │ ├── Dockerfile.dockerignore │ ├── docker-compose.yml │ └── docker_entrypoint.sh └── images │ └── macos-system-preferences-security-bluetooth.png ├── reference ├── advertised-services.md └── desk-internals.md └── scripts ├── build └── publish /.gitignore: -------------------------------------------------------------------------------- 1 | reference/com.linak.deskcontrol/ 2 | venv/ 3 | .vscode 4 | desk.pickle 5 | __pycache__/ 6 | *.egg-info 7 | dist/ 8 | build/ 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.3.0] - 2025-xx-xx 6 | 7 | ### Breaking 8 | 9 | If using the http or tcp server the JSON payload should now be a `command` object and not a `config` object: 10 | 11 | ```bash 12 | # HTTP 13 | curl -X POST http://127.0.0.1:9123 --data '{"key": "move_to", "value": 640}' 14 | 15 | # TCP 16 | echo '{"key": "move_to", "value": 640}' | nc -w 1 127.0.0.1 9123 17 | ``` 18 | 19 | ### Changed 20 | 21 | - Refactor global config and command to fix [100](https://github.com/rhyst/linak-controller/issues/100) and [85](https://github.com/rhyst/linak-controller/issues/85) 22 | 23 | ### Fixed 24 | 25 | - Server port argument [99](https://github.com/rhyst/linak-controller/pull/99) by @gomi-source 26 | - Allow using FIFO as config file [101](https://github.com/rhyst/linak-controller/pull/101) by @jayrhynas 27 | 28 | 29 | ## [1.2.0] - 2024-12-03 30 | 31 | ### Added 32 | 33 | - Added http endpoint in addition to web socket endpoint in server mode 34 | 35 | ### Changes 36 | 37 | - Added support for Python 3.13 38 | - Update bleak dependency 39 | 40 | ## [1.1.1] - 2023-12-01 41 | 42 | ### Changed 43 | 44 | - Removed redundant move to method 45 | 46 | ### Fixed 47 | 48 | - Configurable move command period should allow float [76](https://github.com/rhyst/linak-controller/pull/76) by @tomsb 49 | - Fixed "ValueError: Characteristic notifications already started" [77](https://github.com/rhyst/linak-controller/issues/77) 50 | 51 | ## [1.1.0] - 2023-11-03 52 | 53 | ### Added 54 | 55 | - Configurable move command period (relating to [48](https://github.com/rhyst/linak-controller/issues/48)) 56 | 57 | ### Changed 58 | 59 | - Removed redundant move timeout config 60 | 61 | ## [1.0.3] - 2023-11-01 62 | 63 | ### Added 64 | 65 | - Docker recipe by @voruti 66 | 67 | ### Fixed 68 | 69 | - Fixed stuttering issues [48](https://github.com/rhyst/linak-controller/issues/48) by @tomsb 70 | 71 | ## [1.0.2] - 2023-10-31 72 | 73 | ### Changed 74 | 75 | - Copy config from old config location if it exists and new one does not 76 | 77 | ### Fixed 78 | 79 | - Swapped `|` operator for `Union` in `gatt.py` to fix python 3.8 compatibility 80 | 81 | ## [1.0.1] - 2023-10-31 82 | 83 | ### Changed 84 | 85 | - Added `idasen-controller` as a script to maintain some backwards compatibility 86 | 87 | ## [1.0.0] - 2023-10-31 88 | 89 | Rename `idasen-controller` to `linak-controller`. 90 | 91 | --- 92 | 93 | This package was previously named `idasen-controller` 94 | 95 | ## [2.2.0] - 2023-10-31 96 | 97 | Final release of `idasen-controller` to point at renamed packaged `linak-controller`. 98 | 99 | ## [2.1.0] - 2023-10-31 100 | 101 | ### Added 102 | 103 | - Add setting a user ID on first connection to support DPG1C and DPG1M 104 | - Allow fetching base height from desk by setting config value to null 105 | 106 | ### Changed 107 | 108 | - Python version changed to >=3.8 <3.13 to satisfy dependencies 109 | - Bleak updated to 0.21.1 110 | - Refactor into seperate files and helper classes 111 | - Removed `print-exceptions` option 112 | - Removed max height option as it was unused 113 | - Removed movement range option as it was unused 114 | 115 | ## [2.0.2] - 2023-03-13 116 | 117 | ### Changed 118 | 119 | - Update aiohttp dependency to 3.8.4 120 | - Update bleak dependency to 0.19.5 121 | - Use poetry for dependency management 122 | - Removed old options from RECIPES.md 123 | 124 | 125 | ## [2.0.1] - 2022-07-27 126 | 127 | ### Fixed 128 | 129 | - Removed unnecessary unsubscribe command that was leftover from previous refactor and causing a `bleak.exc.BleakDBusError: [org.bluez.Error.Failed] No notify session started` error. 130 | 131 | ## [2.0.0] - 2022-03-08 132 | 133 | ### Changed 134 | 135 | - Replaced `--sit/--stand` commands with a more configurable list of favourites that can be actived with `--move-to ` 136 | 137 | ## [1.1.0] - 2022-03-08 138 | 139 | ### Added 140 | 141 | - Client/server mode now prints heights on the client as well as the server 142 | 143 | ### Changed 144 | 145 | - Updated to use the REFERENCE_INPUT characteristic which allows you to specify a height to move to. Makes the movement accurate to the millimetre and greatly simplifies code. Inspired by this project: https://github.com/pfilipp/idasen-controller 146 | 147 | ## [1.0.8] - 2022-01-07 148 | 149 | ### Added 150 | 151 | - Add options allowing configuring base height and movement range (via PR from subraizada3) 152 | 153 | ### Changed 154 | 155 | - Remove redundant scanning code when connecting which should improve time to connection 156 | - Remove pickling code as that was an attempt to speed up the redundant scanning process 157 | 158 | ### Fixed 159 | 160 | - More compatible shebang (via PR from subraizada3) 161 | 162 | ## [1.0.7] - 2022-01-07 163 | 164 | ### Changed 165 | 166 | - Remove unneeded depenedencies which will hopefully make windows installs a bit smoother. 167 | 168 | ## [1.0.6] - 2021-03-30 169 | 170 | ### Changed 171 | 172 | - Changed check for pickling connection to `IS_LINUX` after confirmation that you cannot pickle OSX bluetooth connection objects. 173 | 174 | ## [1.0.5] - 2021-02-11 175 | 176 | ### Changed 177 | 178 | - Fix `--scan-timeout` flag. 179 | 180 | ## [1.0.4] - 2021-02-11 181 | 182 | ### Changed 183 | 184 | - Ensure mac addresses are upper case because apparently this matters on some devices. 185 | 186 | ## [1.0.3] - 2021-02-11 187 | 188 | ### Changed 189 | 190 | - Fix to prevent the script from hiding connection errors on subsequent failed attempts. 191 | 192 | ## [1.0.2] - 2021-01-27 193 | 194 | No changes, just a reupload to fix bad Pypi upload of 1.0.1. 195 | 196 | ## [1.0.1] - 2021-01-27 197 | 198 | ### Added 199 | 200 | - This changelog. 201 | 202 | ### Changed 203 | 204 | - Script will retry connection if it fails with a pickled connection. This will help in situations where the adapter name changes for example. 205 | 206 | ## [1.0.0] - 2021-01-27 207 | 208 | ### Added 209 | 210 | - Initial pip release. 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linak-controller 2 | 3 | (Previously `idasen-controller`) 4 | 5 | Linak make motorised standing desks. They can be controlled by a physical switch on the desks or via bluetooth using an phone app. This is a script to control Linak desks via bluetooth from any other device. 6 | 7 | Note: This script may not work with all Linak desks - see below for comaptible models. 8 | 9 | ## Set up 10 | 11 | ### Prerequisites 12 | 13 | - Windows / Linux / Mac 14 | - The device should have Python 3 15 | - The desk should be paired to the device. 16 | 17 | ### Working Desks 18 | 19 | - Ikea Idasen - works (my desk!) 20 | - iMovr Lander - reported working [43](https://github.com/rhyst/linak-controller/issues/43) 21 | - Linak DPG1C - reported working [32](https://github.com/rhyst/linak-controller/issues/32) 22 | - Linak DPG1M - reported working [32](https://github.com/rhyst/linak-controller/issues/32) 23 | 24 | If you find another desk model that works please make an issue to report it! 25 | 26 | ### Install 27 | 28 | Install using pip: 29 | 30 | ``` 31 | pip3 install linak-controller 32 | ``` 33 | 34 | ### Configuration 35 | 36 | Configuration can be provided with a file, or via command line arguments. Use `--help` to see the command line arguments help. Edit `/config.yaml` if you prefer your config to be in a file. `` is normally: 37 | 38 | - `~/.config/linak-controller` on Linux 39 | - `C:\Users\\AppData\Local\linak-controller\linak-controller` on Windows 40 | - `~/Library/Application Support/linak-controller` on MacOS 41 | 42 | Config options: 43 | 44 | | Option | Description | Default | 45 | | --------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------- | 46 | | `mac_address` | The MAC address (or UUID on MacOS) of the desk. This is required. | | 47 | | `base_height` | The lowest possible height (mm) of the desk top from the floor By default this is read from the desk. | `null`. | 48 | | `adapter_name` | The adapter name for the bluetooth adapter to use for the connection (Linux only). | `hci0` | 49 | | `scan_timeout` | Timeout to scan for the device (seconds). | `5` | 50 | | `connection_timeout` | Timeout to obtain connection (seconds). | `10` | 51 | | `move_command_period` | Time between move commands when using `move-to` (seconds). | `0.4` | 52 | | `server_address` | The address the server should run at (if running server). | `127.0.0.1` | 53 | | `server_port` | The port the server should run on (if running server). | `9123` | 54 | | `favourites` | Favourite heights object where the key is the name and the value is the height | `{ sit: 683, stand: 1040 }` | 55 | 56 | All of these options (except `favourites`) can be set on the command line, just replace any `_` with `-` e.g. `mac_address` becomes `--mac-address`. 57 | 58 | #### Device MAC addresses 59 | 60 | - On Linux, device MAC addresses can be found using `bluetoothctl` and bluetooth adapter names can be found with `hcitool dev` 61 | - On Windows you can use [Bluetooth LE Explorer](https://www.microsoft.com/en-us/p/bluetooth-le-explorer/9n0ztkf1qd98?activetab=pivot:overviewtab). 62 | - On MacOS you can pair the device with [Bluetility](https://github.com/jnross/Bluetility), but you must use the UUID instead of the Mac Address. 63 | 64 | ## Usage 65 | 66 | The script accepts a number of commands: 67 | 68 | | Command | Description | 69 | | ---------------------------- | ------------------------------------------------------------------------------------------------- | 70 | | | Running without any command will print the current desk height | 71 | | `--watch` | Watch desk and print changes to height (and speed) | 72 | | `--move-to ` | Move the desk to a certain height (mm) above the floor | 73 | | `--scan` | List available bluetooth devices (using the configured `adapter_name`) | 74 | | `--server` | Run the script as a server, which will maintain the connection and provide quicker response times | 75 | | `--tcp-server` | Run the script as a simpler tcp only server | 76 | | `--forward ` | Send commands to a server | 77 | | `--config ` | Specify a path to a config file | 78 | 79 | ### Moving the desk 80 | 81 | To move to a particular height you can run: 82 | 83 | ``` 84 | linak-controller --move-to 800 85 | ``` 86 | 87 | If you have configured favourite values in the `config.yaml` like this: 88 | 89 | ``` 90 | favourites: 91 | sit: 683 92 | stand: 1040 93 | ``` 94 | 95 | Then you can also pass the favourite name to the `--move-to` command: 96 | 97 | ``` 98 | linak-controller --move-to sit 99 | ``` 100 | 101 | ### Using the Server 102 | 103 | You can run the script in a server mode. This will maintain a persistent connection to the desk and then listen on the specified port for commands. This has a number of uses, one of which is making the response time a lot quicker. Both the server and client will print the current height and speed of the desk as it moves. 104 | 105 | Remember to ensure that the ports and IPs are configured in both the server and client `config.yaml` files (or provide them as command line arguments). 106 | 107 | You can start the server like this: 108 | 109 | ``` 110 | linak-controller --server 111 | ``` 112 | 113 | And then on the same or different device: 114 | 115 | ``` 116 | linak-controller --forward --move-to 800 117 | ``` 118 | 119 | You can also use any of the favourites that are configured on the server: 120 | 121 | ``` 122 | linak-controller --forward --move-to stand 123 | ``` 124 | 125 | You can also directly post a JSON object to the server: 126 | 127 | ``` 128 | curl -X POST http://127.0.0.1:9123 --data '{"key": "move_to", "value": 640}' 129 | ``` 130 | 131 | There is also a simpler TCP server mode which you can with: 132 | 133 | ``` 134 | linak-controller --tcp-server 135 | ``` 136 | 137 | And then use any tool you like to send commands. For example you could use `nc` on linux: 138 | 139 | ``` 140 | echo '{"key": "move_to", "value": 640}' | nc -w 1 127.0.0.1 9123 141 | ``` 142 | 143 | If you use the `linak-controller` command to send commands to the server then you will receive live logging back from the server, which you will not receive if you post JSON or use the TCP server. 144 | 145 | ## Troubleshooting 146 | 147 | ### Connection failed 148 | 149 | The initial connection can fail for a variety of reasons, here are some things to try if it happens repeatedly: 150 | 151 | - Try ensuring that the desk is paired but _not_ connected before using the script. 152 | - Try increasing the `scan-timeout` and `connection-timeout`. 153 | 154 | ### Connection / commands are slow 155 | 156 | - Try reducing the `connection-timeout`. I have found that it can work well set to just `1` second. You may find that a low connection timeout results in failed connections sometimes though. 157 | - Use the server mode. Run the script once with `--server` which will start a persistent server and maintain a connection to the desk. Then when sending commands (like `--move-to sit` or `--move-to 800`) just add the additional argument `--forward` to forward the command to the server. The server should already have a connection so the desk should respond much quicker. 158 | 159 | ### Error message "abort" on MacOS 160 | 161 | On MacOS the process may quit with a vague message like `abort`. This could be because the application running the process doesn't have access to Bluetooth. To provide access, open `System Preferences -> Security & Privacy -> Privacy -> Bluetooth` and drag the application running the process into the list (eg. Terminal or iTerm2). [More info at the `bleak` issue](https://github.com/hbldh/bleak/issues/438#issuecomment-787125189) 162 | 163 | ### Scanning and connection issues on MacOS 12 (Monterey) 164 | 165 | There was a bug with MacOS 12 that prevents connecting to bluetooth devices with this script [see this issue](https://github.com/rhyst/linak-controller/issues/33) or [this bleak issue for more info](https://github.com/hbldh/bleak/issues/635#issuecomment-988054876). 166 | 167 | You should update to MacOS 12.3 which fixes this issue. 168 | 169 | ### Desk movement stuttering 170 | 171 | Try lowering the `move_command_period` config value. 172 | 173 | ## Recipes 174 | 175 | There is a page with a few examples of different ways to use the script: [RECIPES](recipes/RECIPES.md) 176 | 177 | ## Development 178 | 179 | Install the dependencies with poetry: 180 | 181 | ``` 182 | poetry install 183 | ``` 184 | 185 | Then you can run the script with: 186 | 187 | ``` 188 | poetry run python -m linak_controller.main 189 | ``` 190 | 191 | You can also install the project in editable/development mode with: 192 | 193 | ``` 194 | pip install -e . 195 | ``` 196 | 197 | To build the project for publishing run: 198 | 199 | ``` 200 | ./scripts/build 201 | ``` 202 | 203 | And to publish the project to PyPi run: 204 | 205 | ``` 206 | ./scripts/publish 207 | ``` 208 | 209 | ## Projects using this project 210 | 211 | Other useful projects that make use of this one: 212 | 213 | - [Home Assistant Integration](https://github.com/j5lien/esphome-idasen-desk-controller) by @j5lien 214 | 215 | ## Attribution 216 | 217 | Some ideas stolen from: 218 | 219 | - [idasen-controller](https://github.com/pfilipp/idasen-controller) by @pfilipp for working out the functionality of the REFERENCE_INPUT characteristic which allows more accurate movement. 220 | - [linak_bt_desk](https://github.com/anetczuk/linak_bt_desk) by @anetczuk (forked from @zewelor) for general information (particularly the initialisation though) 221 | -------------------------------------------------------------------------------- /linak_controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhyst/linak-controller/28b3f663f09a8f97589cf185697c61eae111298e/linak_controller/__init__.py -------------------------------------------------------------------------------- /linak_controller/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config parsing for this script. Parses command line arguments and config.yaml. 3 | """ 4 | 5 | import os 6 | import sys 7 | import shutil 8 | import argparse 9 | import yaml 10 | from appdirs import user_config_dir 11 | from typing import Optional, TypedDict 12 | from enum import Enum 13 | 14 | 15 | class CommandAction(argparse.Action): 16 | def __call__(self, parser, namespace, values, option_string=None): 17 | setattr(namespace, "command", self.const) 18 | setattr(namespace, self.dest, values) 19 | 20 | 21 | class Commands(str, Enum): 22 | watch = "watch" 23 | move_to = "move_to" 24 | scan_adapter = "scan_adapter" 25 | server = "server" 26 | tcp_server = "tcp_server" 27 | 28 | 29 | Config = TypedDict( 30 | "Config", 31 | mac_address=Optional[str], 32 | base_height=Optional[int], 33 | adapter_name=str, 34 | scan_timeout=int, 35 | connection_timeout=int, 36 | server_address=str, 37 | server_port=int, 38 | favourites=dict, 39 | forward=bool, 40 | move_command_period=float, 41 | ) 42 | 43 | default_config = Config( 44 | { 45 | "mac_address": None, 46 | "base_height": None, 47 | "adapter_name": "hci0", 48 | "scan_timeout": 5, 49 | "connection_timeout": 10, 50 | "server_address": "127.0.0.1", 51 | "server_port": 9123, 52 | "favourites": {}, 53 | "forward": False, 54 | "move_command_period": 0.4, 55 | } 56 | ) 57 | 58 | Command = TypedDict( 59 | "Command", 60 | key=Optional[Commands], 61 | value=Optional[str], 62 | ) 63 | 64 | 65 | def get_config() -> tuple[Config, Command]: 66 | config = default_config.copy() 67 | 68 | OLD_CONFIG_DIR = user_config_dir("idasen-controller") 69 | OLD_CONFIG_PATH = os.path.join(OLD_CONFIG_DIR, "config.yaml") 70 | 71 | DEFAULT_CONFIG_DIR = user_config_dir("linak-controller") 72 | DEFAULT_CONFIG_PATH = os.path.join(DEFAULT_CONFIG_DIR, "config.yaml") 73 | 74 | # Default config 75 | if not os.path.isfile(DEFAULT_CONFIG_PATH): 76 | os.makedirs(os.path.dirname(DEFAULT_CONFIG_PATH), exist_ok=True) 77 | if os.path.isfile(OLD_CONFIG_PATH): 78 | shutil.copyfile(OLD_CONFIG_PATH, DEFAULT_CONFIG_PATH) 79 | else: 80 | shutil.copyfile( 81 | os.path.join(os.path.dirname(__file__), "example", "config.yaml"), 82 | DEFAULT_CONFIG_PATH, 83 | ) 84 | 85 | parser = argparse.ArgumentParser(description="") 86 | 87 | # Config via command line options 88 | 89 | parser.add_argument( 90 | "--mac-address", 91 | dest="mac_address", 92 | type=str, 93 | help="Mac address of the Linak desk", 94 | ) 95 | parser.add_argument( 96 | "--base-height", 97 | dest="base_height", 98 | type=int, 99 | help="The height of tabletop above ground at lowest position (mm)", 100 | ) 101 | parser.add_argument( 102 | "--adapter", 103 | dest="adapter_name", 104 | type=str, 105 | help="The bluetooth adapter device name", 106 | ) 107 | parser.add_argument( 108 | "--scan-timeout", 109 | dest="scan_timeout", 110 | type=int, 111 | help="The timeout for bluetooth scan (seconds)", 112 | ) 113 | parser.add_argument( 114 | "--connection-timeout", 115 | dest="connection_timeout", 116 | type=int, 117 | help="The timeout for bluetooth connection (seconds)", 118 | ) 119 | parser.add_argument( 120 | "--move-command-period", 121 | dest="move_command_period", 122 | type=float, 123 | help="The period between each move command (seconds)", 124 | ) 125 | parser.add_argument( 126 | "--forward", 127 | dest="forward", 128 | action="store_true", 129 | help="Forward any commands to a server", 130 | ) 131 | parser.add_argument( 132 | "--server-address", 133 | dest="server_address", 134 | type=str, 135 | help="The address the server should run at", 136 | ) 137 | parser.add_argument( 138 | "--server-port", 139 | dest="server_port", 140 | type=int, 141 | help="The port the server should run on", 142 | ) 143 | parser.add_argument( 144 | "--config", 145 | dest="config", 146 | type=str, 147 | help="File path to the config file (Default: {})".format(DEFAULT_CONFIG_PATH), 148 | default=DEFAULT_CONFIG_PATH, 149 | ) 150 | 151 | # Command to run 152 | 153 | cmd = parser.add_mutually_exclusive_group() 154 | cmd.add_argument( 155 | "--watch", 156 | dest="command", 157 | action="store_const", 158 | const=Commands.watch, 159 | help="Watch for changes to desk height and speed and print them", 160 | ) 161 | cmd.add_argument( 162 | "--move-to", 163 | dest="move_to", 164 | action=CommandAction, 165 | type=str, 166 | const=Commands.move_to, 167 | help="Move desk to specified height (mm) or to a favourite position", 168 | ) 169 | cmd.add_argument( 170 | "--scan", 171 | dest="command", 172 | action="store_const", 173 | const=Commands.scan_adapter, 174 | help="Scan for devices using the configured adapter", 175 | ) 176 | cmd.add_argument( 177 | "--server", 178 | dest="command", 179 | action="store_const", 180 | const=Commands.server, 181 | help="Run as a server to accept forwarded commands", 182 | ) 183 | cmd.add_argument( 184 | "--tcp-server", 185 | dest="command", 186 | action="store_const", 187 | const=Commands.tcp_server, 188 | help="Run as a simple TCP server to accept forwarded commands", 189 | ) 190 | 191 | args = {k: v for k, v in vars(parser.parse_args()).items() if v is not None} 192 | 193 | if args.get("move_to"): 194 | args["command"] = Commands.move_to 195 | 196 | # Overwrite config from config.yaml 197 | config_file = {} 198 | config_file_path = os.path.join(args["config"]) 199 | if ( 200 | config_file_path 201 | and os.path.exists(config_file_path) 202 | and not os.path.isdir(config_file_path) 203 | ): 204 | with open(config_file_path, "r") as stream: 205 | try: 206 | config_file = yaml.safe_load(stream) 207 | except yaml.YAMLError as exc: 208 | print("Reading config.yaml failed") 209 | exit(1) 210 | else: 211 | print("No config file found") 212 | 213 | for key in config: 214 | if key in config_file: 215 | config[key] = config_file[key] 216 | 217 | # Overwrite config from command line args 218 | for key in config: 219 | if key in args: 220 | config[key] = args[key] 221 | 222 | if not config["mac_address"]: 223 | parser.error("Mac address must be provided") 224 | 225 | config["mac_address"] = config["mac_address"].upper() 226 | 227 | IS_WINDOWS = sys.platform == "win32" 228 | 229 | if IS_WINDOWS: 230 | # Windows doesn't use this parameter so rename it so it looks nice for the logs 231 | config["adapter_name"] = "default adapter" 232 | 233 | # Parse command 234 | command = Command( 235 | { 236 | "key": args.get("command"), 237 | "value": args.get("move_to"), 238 | } 239 | ) 240 | 241 | return config, command 242 | -------------------------------------------------------------------------------- /linak_controller/desk.py: -------------------------------------------------------------------------------- 1 | """ 2 | High level helper class to organise methods for performing actions with a Linak Desk. 3 | """ 4 | 5 | import asyncio 6 | from bleak import BleakClient 7 | from bleak.exc import BleakDBusError 8 | from typing import Tuple 9 | from .gatt import ( 10 | DPGService, 11 | ControlService, 12 | ReferenceInputService, 13 | ReferenceOutputService, 14 | ) 15 | from .config import Config 16 | from .util import logger, bytes_to_hex, Height, Speed 17 | import struct 18 | 19 | 20 | class Desk: 21 | client: BleakClient = None 22 | config: Config = None 23 | disconnecting = False 24 | 25 | def __init__(self, config: Config, client: BleakClient): 26 | self.client = client 27 | self.config = config 28 | 29 | @classmethod 30 | async def initialise(cls, config: Config, client: BleakClient) -> None: 31 | desk = cls(config, client) 32 | 33 | # Read capabilities 34 | capabilities = desk.decode_capabilities( 35 | await DPGService.dpg_command(client, DPGService.DPG.CMD_GET_CAPABILITIES) 36 | ) 37 | logger.log("Capabilities: {}".format(capabilities)) 38 | 39 | # Read the user id 40 | user_id = await DPGService.dpg_command(client, DPGService.DPG.CMD_USER_ID) 41 | logger.log("User ID: {}".format(bytes_to_hex(user_id))) 42 | if user_id and user_id[0] != 1: 43 | # For DPG1C it is important that the first byte is set to 1 44 | # The other bytes do not seem to matter 45 | user_id[0] = 1 46 | logger.log("Setting user ID to {}".format(bytes_to_hex(user_id))) 47 | await DPGService.dpg_command(client, DPGService.DPG.CMD_USER_ID, user_id) 48 | 49 | # Check if base height should be taken from controller 50 | if config["base_height"] == None: 51 | resp = await DPGService.dpg_command(client, DPGService.DPG.CMD_BASE_OFFSET) 52 | if resp: 53 | base_height = struct.unpack(" None: 62 | await ControlService.COMMAND.write_command( 63 | self.client, ControlService.COMMAND.CMD_WAKEUP 64 | ) 65 | 66 | async def move_to(self, target: Height) -> None: 67 | initial_height, speed = await ReferenceOutputService.get_height_speed(self.client) 68 | initial_height.base_height = self.config["base_height"] 69 | if initial_height.value == target.value: 70 | return 71 | 72 | await self.wakeup() 73 | await self.stop() 74 | 75 | data = ReferenceInputService.encode_height(target.value) 76 | 77 | while True: 78 | await ReferenceInputService.ONE.write(self.client, data) 79 | await asyncio.sleep(self.config["move_command_period"]) 80 | height, speed = await ReferenceOutputService.get_height_speed(self.client) 81 | height.base_height = self.config["base_height"] 82 | if speed.value == 0: 83 | break 84 | logger.log( 85 | "Height:{:4.0f}mm Speed: {:2.0f}mm/s".format(height.human, speed.human) 86 | ) 87 | 88 | async def get_height_speed(self) -> Tuple[Height, Speed]: 89 | height, speed = await ReferenceOutputService.get_height_speed(self.client) 90 | height.base_height = self.config["base_height"] 91 | return height, speed 92 | 93 | async def watch_height_speed(self) -> None: 94 | """Listen for height changes""" 95 | 96 | def callback(sender, data): 97 | height, speed = ReferenceOutputService.decode_height_speed(data) 98 | height.base_height = self.config["base_height"] 99 | logger.log( 100 | "Height:{:4.0f}mm Speed: {:2.0f}mm/s".format(height.human, speed.human) 101 | ) 102 | 103 | await ReferenceOutputService.ONE.subscribe(self.client, callback) 104 | await asyncio.Future() 105 | 106 | async def stop(self) -> None: 107 | try: 108 | await ControlService.COMMAND.write_command( 109 | self.client, ControlService.COMMAND.CMD_STOP 110 | ) 111 | except BleakDBusError as e: 112 | # Harmless exception that happens on Raspberry Pis 113 | # bleak.exc.BleakDBusError: [org.bluez.Error.NotPermitted] Write acquired 114 | pass 115 | 116 | @classmethod 117 | def decode_capabilities(self, caps: bytearray) -> dict: 118 | if len(caps) < 2: 119 | return {} 120 | capByte = caps[0] 121 | refByte = caps[1] 122 | return { 123 | "memSize": capByte & 7, 124 | "autoUp": (capByte & 8) != 0, 125 | "autoDown": (capByte & 16) != 0, 126 | "bleAllow": (capByte & 32) != 0, 127 | "hasDisplay": (capByte & 64) != 0, 128 | "hasLight": (capByte & 128) != 0, 129 | } 130 | -------------------------------------------------------------------------------- /linak_controller/example/config.yaml: -------------------------------------------------------------------------------- 1 | mac_address: AA:AA:AA:AA:AA:AA 2 | scan_timeout: 5 3 | connection_timeout: 10 4 | adapter_name: hci0 5 | server_address: "127.0.0.1" 6 | server_port: 9123 7 | favourites: 8 | sit: 683 9 | stand: 1040 10 | -------------------------------------------------------------------------------- /linak_controller/gatt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Low level helper classes to organise methods for interacting with the GATT services/characteristics provided by Linak Desks. 3 | """ 4 | 5 | import struct 6 | from bleak import BleakClient 7 | from typing import Optional, Tuple, Union 8 | from .util import Height, Speed, make_iter 9 | 10 | 11 | class Characteristic: 12 | uuid = None 13 | 14 | @classmethod 15 | async def read(cls, client: BleakClient) -> bytearray: 16 | return await client.read_gatt_char(cls.uuid) 17 | 18 | @classmethod 19 | async def write(cls, client: BleakClient, value: bytearray) -> None: 20 | return await client.write_gatt_char(cls.uuid, value) 21 | 22 | @classmethod 23 | async def subscribe(cls, client: BleakClient, callback) -> None: 24 | """Listen for notifications on a characteristic""" 25 | await client.start_notify(cls.uuid, callback) 26 | 27 | @classmethod 28 | async def unsubscribe(cls, client: BleakClient) -> None: 29 | """Stop listenening for notifications on a characteristic""" 30 | await client.stop_notify(cls.uuid) 31 | 32 | 33 | class Service: 34 | uuid = None 35 | 36 | 37 | # Generic Access 38 | 39 | 40 | class GenericAccessDeviceNameCharacteristic(Characteristic): 41 | uuid = "00002A00-0000-1000-8000-00805F9B34FB" 42 | 43 | 44 | class GenericAccessServiceChangedCharacteristic(Characteristic): 45 | uuid = "00002A05-0000-1000-8000-00805F9B34FB" 46 | 47 | 48 | class GenericAccessManufacturerCharacteristic(Characteristic): 49 | uuid = "00002A29-0000-1000-8000-00805F9B34FB" 50 | 51 | 52 | class GenericAccessModelNumberCharacteristic(Characteristic): 53 | uuid = "00002A24-0000-1000-8000-00805F9B34FB" 54 | 55 | 56 | class GenericAccessService(Service): 57 | uuid = "00001800-0000-1000-8000-00805F9B34FB" 58 | 59 | DEVICE_NAME = GenericAccessDeviceNameCharacteristic 60 | SERVICE_CHANGED = GenericAccessServiceChangedCharacteristic 61 | MANUFACTURER = GenericAccessManufacturerCharacteristic 62 | MODEL_NUMBER = GenericAccessModelNumberCharacteristic 63 | 64 | 65 | # Reference Input 66 | 67 | 68 | class ReferenceInputOneCharacteristic(Characteristic): 69 | uuid = "99fa0031-338a-1024-8a49-009c0215f78a" 70 | 71 | 72 | class ReferenceInputService(Service): 73 | uuid = "99fa0030-338a-1024-8a49-009c0215f78a" 74 | 75 | ONE = ReferenceInputOneCharacteristic 76 | 77 | @classmethod 78 | def encode_height(cls, height: Union[int, str]) -> bytearray: 79 | try: 80 | return bytearray(struct.pack(" Tuple[Height, Speed]: 99 | height, speed = struct.unpack(" Tuple[Height, Speed]: 104 | data = await cls.ONE.read(client) 105 | return cls.decode_height_speed(data) 106 | 107 | 108 | # Control 109 | 110 | 111 | class ControlCommandCharacteristic(Characteristic): 112 | uuid = "99fa0002-338a-1024-8a49-009c0215f78a" 113 | 114 | CMD_MOVE_DOWN = 70 115 | CMD_MOVE_UP = 71 116 | CMD_WAKEUP = 254 117 | CMD_STOP = 255 118 | 119 | @classmethod 120 | async def write_command(cls, client: BleakClient, command: int) -> None: 121 | await client.write_gatt_char(cls.uuid, bytearray(struct.pack("BB", command, 0))) 122 | 123 | 124 | class ControlErrorCharacteristic(Characteristic): 125 | uuid = "99fa0003-338a-1024-8a49-009c0215f78a" 126 | 127 | 128 | class ControlService(Service): 129 | uuid = "99fa0001-338a-1024-8a49-009c0215f78a" 130 | 131 | COMMAND = ControlCommandCharacteristic 132 | ERROR = ControlErrorCharacteristic 133 | 134 | 135 | # DPG 136 | 137 | 138 | class DPGDPGCharacteristic(Characteristic): 139 | uuid = "99fa0011-338a-1024-8a49-009c0215f78a" 140 | 141 | CMD_GET_CAPABILITIES = 128 142 | CMD_BASE_OFFSET = 129 143 | CMD_USER_ID = 134 144 | 145 | @classmethod 146 | async def read_command(cls, client: BleakClient, command: int) -> bytearray: 147 | await cls.write(client, bytearray(struct.pack("BBB", 127, command, 0))) 148 | return await client.read_gatt_char(cls.uuid) 149 | 150 | @classmethod 151 | async def write_command( 152 | cls, client: BleakClient, command: int, data: bytearray 153 | ) -> None: 154 | header = struct.pack("BBB", 127, command, 128) 155 | buffer = bytes() 156 | for val in data: 157 | buffer += struct.pack("B", val) 158 | buffer = header + buffer 159 | await cls.write(client, buffer) 160 | 161 | 162 | class DPGService(Service): 163 | uuid = "99fa0010-338a-1024-8a49-009c0215f78a" 164 | 165 | DPG = DPGDPGCharacteristic 166 | 167 | @classmethod 168 | def is_valid_response(self, response: bytearray) -> bool: 169 | return response[0] == 0x1 170 | 171 | @classmethod 172 | def is_valid_data(self, data: bytearray) -> bool: 173 | return data[1] > 0x1 174 | 175 | @classmethod 176 | async def dpg_command( 177 | cls, client: BleakClient, command: int, data: Optional[bytearray] = None 178 | ) -> bytearray: 179 | iter, callback = make_iter() 180 | await cls.DPG.subscribe(client, callback) 181 | if data: 182 | await cls.DPG.write_command(client, command, data) 183 | else: 184 | await cls.DPG.read_command(client, command) 185 | async for sender, data in iter: 186 | # Return the first response from the callback 187 | await cls.DPG.unsubscribe(client) 188 | if data[0] == 1: 189 | return data[2:] 190 | else: 191 | return None 192 | -------------------------------------------------------------------------------- /linak_controller/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import traceback 4 | import asyncio 5 | import aiohttp 6 | from aiohttp import web 7 | from bleak import BleakClient, BleakError, BleakScanner 8 | import json 9 | from functools import partial 10 | from .config import get_config, Config, Command, Commands 11 | from .util import Height, logger 12 | from .desk import Desk 13 | 14 | 15 | async def scan(config: Config): 16 | """Scan for a bluetooth device with the configured address and return it or return all devices if no address specified""" 17 | logger.log("Scanning\r", end="") 18 | devices = await BleakScanner().discover( 19 | device=config["adapter_name"], timeout=config["scan_timeout"] 20 | ) 21 | logger.log("Found {} devices using {}".format(len(devices), config["adapter_name"])) 22 | for device in devices: 23 | logger.log(device) 24 | return devices 25 | 26 | desk_for_disconnect = None 27 | 28 | def disconnect_callback(client: BleakClient, _=None): 29 | global desk_for_disconnect 30 | if not desk_for_disconnect.disconnecting: 31 | logger.log("Lost connection with {}".format(client.address)) 32 | asyncio.create_task(connect(desk_for_disconnect.config, desk_for_disconnect)) 33 | 34 | 35 | async def connect(config: Config, desk=None, attempt=0): 36 | """Attempt to connect to the desk""" 37 | try: 38 | logger.log("Connecting\r", end="") 39 | if not desk: 40 | client = BleakClient( 41 | config["mac_address"], 42 | device=config["adapter_name"], 43 | disconnected_callback=disconnect_callback, 44 | ) 45 | await client.connect(timeout=config["connection_timeout"]) 46 | logger.log("Connected: {}".format(config["mac_address"])) 47 | desk = await Desk.initialise(config, client) 48 | global desk_for_disconnect 49 | desk_for_disconnect = desk 50 | else: 51 | await desk.client.connect(timeout=config["connection_timeout"]) 52 | logger.log("Reconnected: {}".format(config["mac_address"])) 53 | return desk 54 | except BleakError as e: 55 | logger.log("Connecting failed") 56 | if "was not found" in str(e): 57 | logger.log(e) 58 | else: 59 | logger.log(traceback.format_exc()) 60 | os._exit(1) 61 | except asyncio.exceptions.TimeoutError as e: 62 | logger.log("Connecting failed - timed out") 63 | os._exit(1) 64 | except OSError as e: 65 | logger.log(e) 66 | os._exit(1) 67 | 68 | 69 | async def disconnect(desk: Desk): 70 | """Attempt to disconnect cleanly""" 71 | if desk.client.is_connected: 72 | desk.disconnecting = True 73 | await desk.client.disconnect() 74 | 75 | 76 | async def run_command(desk: Desk, command: Command): 77 | """Begin the action specified by command line arguments and config""" 78 | # Always print current height 79 | initial_height, _ = await desk.get_height_speed() 80 | logger.log("Height: {:4.0f}mm".format(initial_height.human)) 81 | target = None 82 | 83 | if command["key"] == Commands.watch: 84 | # Print changes to height data 85 | logger.log("Watching for changes to desk height and speed") 86 | await desk.watch_height_speed() 87 | elif command["key"] == Commands.move_to: 88 | # Move to custom height 89 | if command["value"] in desk.config["favourites"]: 90 | target = Height( 91 | desk.config["favourites"].get(command["value"]), desk.base_height, True 92 | ) 93 | logger.log( 94 | f"""Moving to favourite height: {command["value"]} ({target.human} mm)""" 95 | ) 96 | elif str(command["value"]).isnumeric(): 97 | target = Height(int(command["value"]), desk.config["base_height"], True) 98 | logger.log(f"""Moving to height: {command["value"]}""") 99 | else: 100 | logger.log( 101 | f"""Not a valid height or favourite position: {command["value"]}""" 102 | ) 103 | return 104 | if target.value == initial_height.value: 105 | logger.log(f"Nothing to do - already at specified height") 106 | return 107 | await desk.move_to(target) 108 | if target: 109 | final_height, _ = await desk.get_height_speed() 110 | # If we were moving to a target height, wait, then print the actual final height 111 | logger.log( 112 | "Final height: {:4.0f}mm (Target: {:4.0f}mm)".format( 113 | final_height.human, target.human 114 | ) 115 | ) 116 | 117 | 118 | async def run_tcp_server(desk: Desk): 119 | """Start a simple tcp server to listen for commands""" 120 | 121 | server = await asyncio.start_server( 122 | partial(run_tcp_forwarded_command, desk), 123 | desk.config["server_address"], 124 | desk.config["server_port"], 125 | ) 126 | logger.log("TCP Server listening") 127 | await server.serve_forever() 128 | 129 | 130 | async def run_tcp_forwarded_command(desk: Desk, reader, writer): 131 | """Run commands received by the tcp server""" 132 | logger.log("Received command") 133 | request = (await reader.read()).decode("utf8") 134 | command = json.loads(str(request)) 135 | await run_command(desk, command) 136 | writer.close() 137 | 138 | 139 | async def run_http_server(desk: Desk): 140 | """Start a server to listen for commands via websocket connection""" 141 | app = web.Application() 142 | app.router.add_post("/", partial(run_forwarded_http_command, desk)) 143 | app.router.add_get("/ws", partial(run_forwarded_ws_command, desk)) 144 | runner = web.AppRunner(app) 145 | await runner.setup() 146 | site = web.TCPSite(runner, desk.config["server_address"], desk.config["server_port"]) 147 | await site.start() 148 | logger.log("Server listening") 149 | await asyncio.Future() 150 | 151 | 152 | async def run_forwarded_http_command(desk: Desk, request): 153 | """Run commands received by the server""" 154 | logger.log("Received command") 155 | command = await request.json() 156 | await run_command(desk, command) 157 | return web.Response(text="OK") 158 | 159 | 160 | async def run_forwarded_ws_command(desk: Desk, request): 161 | """ 162 | Run commands received by the server via websocket connection 163 | This allows live streaming of the logs back to the client 164 | """ 165 | logger.log("Received ws command") 166 | ws = web.WebSocketResponse() 167 | 168 | def log(message, end="\n"): 169 | logger.log(message, end=end) 170 | asyncio.create_task(ws.send_str(str(message))) 171 | 172 | logger.log = log 173 | 174 | await ws.prepare(request) 175 | async for msg in ws: 176 | if msg.type == aiohttp.WSMsgType.TEXT: 177 | command = json.loads(msg.data) 178 | await run_command(desk, command) 179 | break 180 | await asyncio.sleep(1) # Allows final messages to send on web socket 181 | await ws.close() 182 | return ws 183 | 184 | 185 | async def forward_command(config: Config, command: Command): 186 | """Send commands to a server instance of this script""" 187 | allowed_commands = [None, Commands.move_to] 188 | if command["key"] not in allowed_commands: 189 | logger.log(f"Command must be one of {allowed_commands}") 190 | return 191 | session = aiohttp.ClientSession() 192 | ws = await session.ws_connect( 193 | f"""http://{config["server_address"]}:{config["server_port"]}/ws""" 194 | ) 195 | await ws.send_str(json.dumps(command)) 196 | while True: 197 | msg = await ws.receive() 198 | if msg.type == aiohttp.WSMsgType.text: 199 | logger.log(msg.data) 200 | elif msg.type in [aiohttp.WSMsgType.closed, aiohttp.WSMsgType.error]: 201 | break 202 | await ws.close() 203 | await session.close() 204 | 205 | 206 | async def main(): 207 | """Set up the async event loop and signal handlers""" 208 | desk = None 209 | try: 210 | config, command = get_config() 211 | # Forward and scan don't require a connection so run them and exit 212 | if config["forward"]: 213 | await forward_command(config, command) 214 | elif command["key"] == Commands.scan_adapter: 215 | await scan(config) 216 | else: 217 | # Server and other commands do require a connection so set one up 218 | desk = await connect(config) 219 | if command["key"] == Commands.server: 220 | await run_http_server(desk) 221 | elif command["key"] == Commands.tcp_server: 222 | await run_tcp_server(desk) 223 | else: 224 | await run_command(desk, command) 225 | except Exception as e: 226 | logger.log("\nSomething unexpected went wrong:") 227 | logger.log(traceback.format_exc()) 228 | finally: 229 | if desk: 230 | logger.log("\rDisconnecting\r", end="") 231 | await desk.stop() 232 | await disconnect(desk) 233 | logger.log("Disconnected ") 234 | 235 | 236 | def init(): 237 | try: 238 | asyncio.run(main()) 239 | except KeyboardInterrupt: 240 | pass 241 | 242 | 243 | if __name__ == "__main__": 244 | init() 245 | -------------------------------------------------------------------------------- /linak_controller/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Random helpers and util. 3 | """ 4 | 5 | import asyncio 6 | 7 | class Logger: 8 | def log(self, message, end="\n"): 9 | print(message, end=end) 10 | 11 | logger = Logger() 12 | 13 | def bytes_to_hex(bytes: bytearray) -> str: 14 | return bytes.hex(" ") 15 | 16 | 17 | def hex_to_bytes(hex: str) -> bytearray: 18 | return bytearray.fromhex(hex) 19 | 20 | 21 | def bytes_to_int(bytes: bytearray) -> int: 22 | return int.from_bytes(bytes, byteorder="little") 23 | 24 | 25 | def bytes_to_utf8(bytes: bytearray) -> str: 26 | return bytes.decode("utf-8") 27 | 28 | 29 | def make_iter(): 30 | loop = asyncio.get_event_loop() 31 | queue = asyncio.Queue() 32 | 33 | def put(*args): 34 | loop.call_soon_threadsafe(queue.put_nowait, args) 35 | 36 | async def get(): 37 | while True: 38 | yield await queue.get() 39 | 40 | return get(), put 41 | 42 | 43 | class Height: 44 | value: int # internal height in 10ths of a mm 45 | base_height: int = 0 # height of the desk at the lowest position in mm 46 | 47 | def __init__(self, height: int, base_height: int = 0, convertFromHuman: bool = False): 48 | self.base_height = base_height 49 | if convertFromHuman: 50 | self.value = self.height_to_internal_height(height) 51 | else: 52 | self.value = height 53 | 54 | def height_to_internal_height(self, height: int): 55 | return (height - self.base_height) * 10 56 | 57 | def internal_height_to_height(self, height: int): 58 | return (height / 10) + self.base_height 59 | 60 | @property 61 | def human(self) -> int: 62 | return self.internal_height_to_height(self.value) 63 | 64 | 65 | class Speed: 66 | value: int # internal speed in 100ths of a mm/s 67 | 68 | def __init__(self, speed: int, convert: bool = False): 69 | if convert: 70 | self.value = self.speed_to_internal_speed(speed) 71 | else: 72 | self.value = speed # Speed in 100ths of a mm/s 73 | 74 | def speed_to_internal_speed(self, speed: int): 75 | return speed * 100 76 | 77 | def internal_speed_to_speed(self, speed: int): 78 | return speed / 100 79 | 80 | @property 81 | def human(self) -> int: 82 | return self.internal_speed_to_speed(self.value) 83 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "aiohappyeyeballs" 5 | version = "2.4.4" 6 | description = "Happy Eyeballs for asyncio" 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.8" 10 | files = [ 11 | {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, 12 | {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, 13 | ] 14 | 15 | [[package]] 16 | name = "aiohttp" 17 | version = "3.10.11" 18 | description = "Async http client/server framework (asyncio)" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.8" 22 | files = [ 23 | {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, 24 | {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, 25 | {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, 26 | {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, 27 | {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, 28 | {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, 29 | {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, 30 | {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, 31 | {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, 32 | {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, 33 | {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, 34 | {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, 35 | {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, 36 | {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, 37 | {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, 38 | {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, 39 | {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, 40 | {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, 41 | {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, 42 | {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, 43 | {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, 44 | {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, 45 | {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, 46 | {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, 47 | {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, 48 | {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, 49 | {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, 50 | {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, 51 | {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, 52 | {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, 53 | {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, 54 | {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, 55 | {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, 56 | {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, 57 | {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, 58 | {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, 59 | {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, 60 | {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, 61 | {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, 62 | {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, 63 | {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, 64 | {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, 65 | {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, 66 | {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, 67 | {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, 68 | {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, 69 | {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, 70 | {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, 71 | {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, 72 | {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, 73 | {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, 74 | {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, 75 | {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, 76 | {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, 77 | {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, 78 | {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, 79 | {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, 80 | {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, 81 | {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, 82 | {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, 83 | {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, 84 | {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, 85 | {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, 86 | {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, 87 | {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, 88 | {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, 89 | {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, 90 | {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, 91 | {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, 92 | {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, 93 | {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, 94 | {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, 95 | {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, 96 | {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, 97 | {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, 98 | {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, 99 | {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, 100 | {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, 101 | {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, 102 | {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, 103 | {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, 104 | {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, 105 | {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, 106 | {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, 107 | {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, 108 | {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, 109 | {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, 110 | {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, 111 | {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, 112 | {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, 113 | {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, 114 | ] 115 | 116 | [package.dependencies] 117 | aiohappyeyeballs = ">=2.3.0" 118 | aiosignal = ">=1.1.2" 119 | async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} 120 | attrs = ">=17.3.0" 121 | frozenlist = ">=1.1.1" 122 | multidict = ">=4.5,<7.0" 123 | yarl = ">=1.12.0,<2.0" 124 | 125 | [package.extras] 126 | speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] 127 | 128 | [[package]] 129 | name = "aiosignal" 130 | version = "1.3.1" 131 | description = "aiosignal: a list of registered asynchronous callbacks" 132 | category = "main" 133 | optional = false 134 | python-versions = ">=3.7" 135 | files = [ 136 | {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, 137 | {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, 138 | ] 139 | 140 | [package.dependencies] 141 | frozenlist = ">=1.1.0" 142 | 143 | [[package]] 144 | name = "appdirs" 145 | version = "1.4.4" 146 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 147 | category = "main" 148 | optional = false 149 | python-versions = "*" 150 | files = [ 151 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 152 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 153 | ] 154 | 155 | [[package]] 156 | name = "async-timeout" 157 | version = "4.0.3" 158 | description = "Timeout context manager for asyncio programs" 159 | category = "main" 160 | optional = false 161 | python-versions = ">=3.7" 162 | files = [ 163 | {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, 164 | {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, 165 | ] 166 | 167 | [[package]] 168 | name = "attrs" 169 | version = "24.2.0" 170 | description = "Classes Without Boilerplate" 171 | category = "main" 172 | optional = false 173 | python-versions = ">=3.7" 174 | files = [ 175 | {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, 176 | {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, 177 | ] 178 | 179 | [package.extras] 180 | benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 181 | cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 182 | dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 183 | docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] 184 | tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 185 | tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] 186 | 187 | [[package]] 188 | name = "black" 189 | version = "23.12.1" 190 | description = "The uncompromising code formatter." 191 | category = "dev" 192 | optional = false 193 | python-versions = ">=3.8" 194 | files = [ 195 | {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, 196 | {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, 197 | {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, 198 | {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, 199 | {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, 200 | {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, 201 | {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, 202 | {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, 203 | {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, 204 | {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, 205 | {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, 206 | {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, 207 | {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, 208 | {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, 209 | {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, 210 | {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, 211 | {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, 212 | {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, 213 | {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, 214 | {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, 215 | {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, 216 | {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, 217 | ] 218 | 219 | [package.dependencies] 220 | click = ">=8.0.0" 221 | mypy-extensions = ">=0.4.3" 222 | packaging = ">=22.0" 223 | pathspec = ">=0.9.0" 224 | platformdirs = ">=2" 225 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 226 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 227 | 228 | [package.extras] 229 | colorama = ["colorama (>=0.4.3)"] 230 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] 231 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 232 | uvloop = ["uvloop (>=0.15.2)"] 233 | 234 | [[package]] 235 | name = "bleak" 236 | version = "0.22.3" 237 | description = "Bluetooth Low Energy platform Agnostic Klient" 238 | category = "main" 239 | optional = false 240 | python-versions = "<3.14,>=3.8" 241 | files = [ 242 | {file = "bleak-0.22.3-py3-none-any.whl", hash = "sha256:1e62a9f5e0c184826e6c906e341d8aca53793e4596eeaf4e0b191e7aca5c461c"}, 243 | {file = "bleak-0.22.3.tar.gz", hash = "sha256:3149c3c19657e457727aa53d9d6aeb89658495822cd240afd8aeca4dd09c045c"}, 244 | ] 245 | 246 | [package.dependencies] 247 | async-timeout = {version = ">=3.0.0,<5", markers = "python_version < \"3.11\""} 248 | bleak-winrt = {version = ">=1.2.0,<2.0.0", markers = "platform_system == \"Windows\" and python_version < \"3.12\""} 249 | dbus-fast = {version = ">=1.83.0,<3", markers = "platform_system == \"Linux\""} 250 | pyobjc-core = {version = ">=10.3,<11.0", markers = "platform_system == \"Darwin\""} 251 | pyobjc-framework-CoreBluetooth = {version = ">=10.3,<11.0", markers = "platform_system == \"Darwin\""} 252 | pyobjc-framework-libdispatch = {version = ">=10.3,<11.0", markers = "platform_system == \"Darwin\""} 253 | typing-extensions = {version = ">=4.7.0", markers = "python_version < \"3.12\""} 254 | winrt-runtime = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 255 | "winrt-Windows.Devices.Bluetooth" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 256 | "winrt-Windows.Devices.Bluetooth.Advertisement" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 257 | "winrt-Windows.Devices.Bluetooth.GenericAttributeProfile" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 258 | "winrt-Windows.Devices.Enumeration" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 259 | "winrt-Windows.Foundation" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 260 | "winrt-Windows.Foundation.Collections" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 261 | "winrt-Windows.Storage.Streams" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} 262 | 263 | [[package]] 264 | name = "bleak-winrt" 265 | version = "1.2.0" 266 | description = "Python WinRT bindings for Bleak" 267 | category = "main" 268 | optional = false 269 | python-versions = "*" 270 | files = [ 271 | {file = "bleak-winrt-1.2.0.tar.gz", hash = "sha256:0577d070251b9354fc6c45ffac57e39341ebb08ead014b1bdbd43e211d2ce1d6"}, 272 | {file = "bleak_winrt-1.2.0-cp310-cp310-win32.whl", hash = "sha256:a2ae3054d6843ae0cfd3b94c83293a1dfd5804393977dd69bde91cb5099fc47c"}, 273 | {file = "bleak_winrt-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:677df51dc825c6657b3ae94f00bd09b8ab88422b40d6a7bdbf7972a63bc44e9a"}, 274 | {file = "bleak_winrt-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9449cdb942f22c9892bc1ada99e2ccce9bea8a8af1493e81fefb6de2cb3a7b80"}, 275 | {file = "bleak_winrt-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:98c1b5a6a6c431ac7f76aa4285b752fe14a1c626bd8a1dfa56f66173ff120bee"}, 276 | {file = "bleak_winrt-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:623ac511696e1f58d83cb9c431e32f613395f2199b3db7f125a3d872cab968a4"}, 277 | {file = "bleak_winrt-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:13ab06dec55469cf51a2c187be7b630a7a2922e1ea9ac1998135974a7239b1e3"}, 278 | {file = "bleak_winrt-1.2.0-cp38-cp38-win32.whl", hash = "sha256:5a36ff8cd53068c01a795a75d2c13054ddc5f99ce6de62c1a97cd343fc4d0727"}, 279 | {file = "bleak_winrt-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:810c00726653a962256b7acd8edf81ab9e4a3c66e936a342ce4aec7dbd3a7263"}, 280 | {file = "bleak_winrt-1.2.0-cp39-cp39-win32.whl", hash = "sha256:dd740047a08925bde54bec357391fcee595d7b8ca0c74c87170a5cbc3f97aa0a"}, 281 | {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, 282 | ] 283 | 284 | [[package]] 285 | name = "click" 286 | version = "8.1.7" 287 | description = "Composable command line interface toolkit" 288 | category = "dev" 289 | optional = false 290 | python-versions = ">=3.7" 291 | files = [ 292 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 293 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 294 | ] 295 | 296 | [package.dependencies] 297 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 298 | 299 | [[package]] 300 | name = "colorama" 301 | version = "0.4.6" 302 | description = "Cross-platform colored terminal text." 303 | category = "dev" 304 | optional = false 305 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 306 | files = [ 307 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 308 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 309 | ] 310 | 311 | [[package]] 312 | name = "dbus-fast" 313 | version = "2.24.4" 314 | description = "A faster version of dbus-next" 315 | category = "main" 316 | optional = false 317 | python-versions = "<4.0,>=3.8" 318 | files = [ 319 | {file = "dbus_fast-2.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4640feb97e3b992052eb075a5dd606e0ba54ae3ce702d6d15d90b479da561547"}, 320 | {file = "dbus_fast-2.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0fd863108be7494cab3570b76aac68fbd54290d7edea9063afa33815d76015"}, 321 | {file = "dbus_fast-2.24.4-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:8bf8037e190071f02e01b2133effb1715b884bbbf5bd5e6dcf0998a6f7972d23"}, 322 | {file = "dbus_fast-2.24.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:590f2767e3b8a9e66c7fb0500d439fe95793933682e525e3518f414d83a454bf"}, 323 | {file = "dbus_fast-2.24.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf9aba8ed59ef8c0026b321710442b8ccc876a37c883490fb2900bc009d7bd70"}, 324 | {file = "dbus_fast-2.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d97063b1000d8a28e76f80f016ec794637df507fbc26a0211053045c2a14958"}, 325 | {file = "dbus_fast-2.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09eb824358c9e23405320e4430e6384eb750fd7c3aafe9fe1ed76341de50c276"}, 326 | {file = "dbus_fast-2.24.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8f0726f01de87dc5db543c4f2cfa6334f2ec159465ba891c538e2f63ed3ac265"}, 327 | {file = "dbus_fast-2.24.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7d8177f35a504651788f4a03bb81e92d90f26eaa3e5384085631a521a6d8a146"}, 328 | {file = "dbus_fast-2.24.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f02b734948b9d70c943e694a0fca5ab323a516dc2d453365c70fbe4d5e0a731"}, 329 | {file = "dbus_fast-2.24.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a1d215d9a62964a0df56ddb27f09f315903e5756920832fccb5b7990894ceb8"}, 330 | {file = "dbus_fast-2.24.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99f98f15543063806350c12b0304616660c34ad6e7d252cb3b8f74dd6a7ebc52"}, 331 | {file = "dbus_fast-2.24.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ef4c70d965787215717e150d961a30e8414e0822d9c070baf5d4f166fa4996ad"}, 332 | {file = "dbus_fast-2.24.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c74cf8283678ccbcc73c136eddbd60187775283c75372bcdfa62affdc787bc11"}, 333 | {file = "dbus_fast-2.24.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bf5d2ccc43b1072493f5b916c7f55aad9e773438c0ef1fdba563f6c8c0f281"}, 334 | {file = "dbus_fast-2.24.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800066f870bf980939b14fa0a6eb262bf00d46f2436a47180686ea945900418e"}, 335 | {file = "dbus_fast-2.24.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b94f0421451196e769bbd4c32c88b575bf6d639733311870d7698d142961d7b"}, 336 | {file = "dbus_fast-2.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:556c6d5378aa990d935eba24160b1af09e79f3382ba5aea484cac348d318d62c"}, 337 | {file = "dbus_fast-2.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17d91b75d7ad6dea9c81f3f006ba64232d71080a20832c8dd55f22cd72f07fc"}, 338 | {file = "dbus_fast-2.24.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6eb0266c95f7d7d58d2cbaaa87be881ec431eab027a14376ceabfe190c4c63f3"}, 339 | {file = "dbus_fast-2.24.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ee00b91fdb7ff439ac90aa8944c2bf781d4406d9d96d79d4e4aa211d165b4cad"}, 340 | {file = "dbus_fast-2.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fad2bfd7a7f9370cbc30fc91d82e7978a337d51de22c17bed4afa425c60cf0dc"}, 341 | {file = "dbus_fast-2.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fa6e61c1b1c7059928af1d0fab864cb34d463a07c1f7df3b20c8a7a94e9d45"}, 342 | {file = "dbus_fast-2.24.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:64b901364fe5351033784a87e6d4fbdc6684656e89e701bbd01be76fc8e852a6"}, 343 | {file = "dbus_fast-2.24.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0150b93244fc36f97ce166f0d671251e657fbd12e0c5e179507958f1845ba232"}, 344 | {file = "dbus_fast-2.24.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:837b1cd3fb445454f812f33f61e4657568a57d0ebaabe196f61484aff865a457"}, 345 | {file = "dbus_fast-2.24.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3e2338e8d06488ed9ec764c53a25c041322dd94ee6cd519fc028c8880666909"}, 346 | {file = "dbus_fast-2.24.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb67a94f9a9c27e18bb7dffd7e6cf6e16bce80a8850ca2d172e9ccb5d79f941"}, 347 | {file = "dbus_fast-2.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c5361ee5726237f3308c57a4f09eaea242a3b9cb3125b0481f9e922a000fe5e"}, 348 | {file = "dbus_fast-2.24.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980c6214e3fdf9402bc3ac81af21b3808de29e41a65256ad4e36a590d5e47b6"}, 349 | {file = "dbus_fast-2.24.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6b54971d1a02c753e62bc78431f59ee5db2f2049e9262880be92117cb7419fc"}, 350 | {file = "dbus_fast-2.24.4.tar.gz", hash = "sha256:58f97e8342d6cd11ebb2c8ac959c5bb342eb83e29180528690b323a5a5def41c"}, 351 | ] 352 | 353 | [[package]] 354 | name = "frozenlist" 355 | version = "1.5.0" 356 | description = "A list-like structure which implements collections.abc.MutableSequence" 357 | category = "main" 358 | optional = false 359 | python-versions = ">=3.8" 360 | files = [ 361 | {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, 362 | {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, 363 | {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, 364 | {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, 365 | {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, 366 | {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, 367 | {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, 368 | {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, 369 | {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, 370 | {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, 371 | {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, 372 | {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, 373 | {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, 374 | {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, 375 | {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, 376 | {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, 377 | {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, 378 | {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, 379 | {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, 380 | {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, 381 | {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, 382 | {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, 383 | {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, 384 | {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, 385 | {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, 386 | {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, 387 | {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, 388 | {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, 389 | {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, 390 | {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, 391 | {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, 392 | {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, 393 | {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, 394 | {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, 395 | {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, 396 | {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, 397 | {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, 398 | {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, 399 | {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, 400 | {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, 401 | {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, 402 | {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, 403 | {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, 404 | {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, 405 | {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, 406 | {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, 407 | {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, 408 | {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, 409 | {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, 410 | {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, 411 | {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, 412 | {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, 413 | {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, 414 | {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, 415 | {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, 416 | {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, 417 | {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, 418 | {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, 419 | {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, 420 | {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, 421 | {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, 422 | {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, 423 | {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, 424 | {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, 425 | {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, 426 | {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, 427 | {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, 428 | {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, 429 | {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, 430 | {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, 431 | {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, 432 | {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, 433 | {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, 434 | {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, 435 | {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, 436 | {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, 437 | {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, 438 | {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, 439 | {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, 440 | {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, 441 | {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, 442 | {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, 443 | {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, 444 | {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, 445 | {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, 446 | {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, 447 | {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, 448 | {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, 449 | {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, 450 | {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, 451 | {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, 452 | {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, 453 | ] 454 | 455 | [[package]] 456 | name = "idna" 457 | version = "3.10" 458 | description = "Internationalized Domain Names in Applications (IDNA)" 459 | category = "main" 460 | optional = false 461 | python-versions = ">=3.6" 462 | files = [ 463 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 464 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 465 | ] 466 | 467 | [package.extras] 468 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 469 | 470 | [[package]] 471 | name = "multidict" 472 | version = "6.1.0" 473 | description = "multidict implementation" 474 | category = "main" 475 | optional = false 476 | python-versions = ">=3.8" 477 | files = [ 478 | {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, 479 | {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, 480 | {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, 481 | {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, 482 | {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, 483 | {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, 484 | {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, 485 | {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, 486 | {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, 487 | {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, 488 | {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, 489 | {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, 490 | {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, 491 | {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, 492 | {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, 493 | {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, 494 | {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, 495 | {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, 496 | {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, 497 | {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, 498 | {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, 499 | {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, 500 | {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, 501 | {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, 502 | {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, 503 | {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, 504 | {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, 505 | {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, 506 | {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, 507 | {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, 508 | {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, 509 | {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, 510 | {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, 511 | {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, 512 | {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, 513 | {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, 514 | {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, 515 | {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, 516 | {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, 517 | {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, 518 | {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, 519 | {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, 520 | {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, 521 | {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, 522 | {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, 523 | {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, 524 | {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, 525 | {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, 526 | {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, 527 | {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, 528 | {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, 529 | {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, 530 | {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, 531 | {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, 532 | {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, 533 | {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, 534 | {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, 535 | {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, 536 | {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, 537 | {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, 538 | {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, 539 | {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, 540 | {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, 541 | {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, 542 | {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, 543 | {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, 544 | {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, 545 | {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, 546 | {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, 547 | {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, 548 | {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, 549 | {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, 550 | {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, 551 | {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, 552 | {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, 553 | {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, 554 | {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, 555 | {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, 556 | {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, 557 | {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, 558 | {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, 559 | {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, 560 | {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, 561 | {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, 562 | {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, 563 | {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, 564 | {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, 565 | {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, 566 | {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, 567 | {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, 568 | {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, 569 | {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, 570 | ] 571 | 572 | [package.dependencies] 573 | typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} 574 | 575 | [[package]] 576 | name = "mypy-extensions" 577 | version = "1.0.0" 578 | description = "Type system extensions for programs checked with the mypy type checker." 579 | category = "dev" 580 | optional = false 581 | python-versions = ">=3.5" 582 | files = [ 583 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 584 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 585 | ] 586 | 587 | [[package]] 588 | name = "packaging" 589 | version = "24.2" 590 | description = "Core utilities for Python packages" 591 | category = "dev" 592 | optional = false 593 | python-versions = ">=3.8" 594 | files = [ 595 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 596 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 597 | ] 598 | 599 | [[package]] 600 | name = "pathspec" 601 | version = "0.12.1" 602 | description = "Utility library for gitignore style pattern matching of file paths." 603 | category = "dev" 604 | optional = false 605 | python-versions = ">=3.8" 606 | files = [ 607 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 608 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 609 | ] 610 | 611 | [[package]] 612 | name = "platformdirs" 613 | version = "4.3.6" 614 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 615 | category = "dev" 616 | optional = false 617 | python-versions = ">=3.8" 618 | files = [ 619 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 620 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 621 | ] 622 | 623 | [package.extras] 624 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 625 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 626 | type = ["mypy (>=1.11.2)"] 627 | 628 | [[package]] 629 | name = "propcache" 630 | version = "0.2.0" 631 | description = "Accelerated property cache" 632 | category = "main" 633 | optional = false 634 | python-versions = ">=3.8" 635 | files = [ 636 | {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, 637 | {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, 638 | {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, 639 | {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, 640 | {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, 641 | {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, 642 | {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, 643 | {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, 644 | {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, 645 | {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, 646 | {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, 647 | {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, 648 | {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, 649 | {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, 650 | {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, 651 | {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, 652 | {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, 653 | {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, 654 | {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, 655 | {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, 656 | {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, 657 | {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, 658 | {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, 659 | {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, 660 | {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, 661 | {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, 662 | {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, 663 | {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, 664 | {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, 665 | {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, 666 | {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, 667 | {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, 668 | {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, 669 | {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, 670 | {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, 671 | {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, 672 | {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, 673 | {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, 674 | {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, 675 | {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, 676 | {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, 677 | {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, 678 | {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, 679 | {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, 680 | {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, 681 | {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, 682 | {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, 683 | {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, 684 | {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, 685 | {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, 686 | {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, 687 | {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, 688 | {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, 689 | {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, 690 | {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, 691 | {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, 692 | {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, 693 | {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, 694 | {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, 695 | {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, 696 | {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, 697 | {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, 698 | {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, 699 | {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, 700 | {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, 701 | {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, 702 | {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, 703 | {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, 704 | {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, 705 | {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, 706 | {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, 707 | {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, 708 | {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, 709 | {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, 710 | {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, 711 | {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, 712 | {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, 713 | {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, 714 | {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, 715 | {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, 716 | {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, 717 | {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, 718 | {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, 719 | {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, 720 | {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, 721 | {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, 722 | {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, 723 | {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, 724 | {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, 725 | {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, 726 | {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, 727 | {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, 728 | {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, 729 | {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, 730 | {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, 731 | {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, 732 | {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, 733 | {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, 734 | ] 735 | 736 | [[package]] 737 | name = "pyobjc-core" 738 | version = "10.3.2" 739 | description = "Python<->ObjC Interoperability Module" 740 | category = "main" 741 | optional = false 742 | python-versions = ">=3.8" 743 | files = [ 744 | {file = "pyobjc_core-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb40672d682851a5c7fd84e5041c4d069b62076168d72591abb5fcc871bb039"}, 745 | {file = "pyobjc_core-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cea5e77659619ad93c782ca07644b6efe7d7ec6f59e46128843a0a87c1af511a"}, 746 | {file = "pyobjc_core-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:16644a92fb9661de841ba6115e5354db06a1d193a5e239046e840013c7b3874d"}, 747 | {file = "pyobjc_core-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:76b8b911d94501dac89821df349b1860bb770dce102a1a293f524b5b09dd9462"}, 748 | {file = "pyobjc_core-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8c6288fdb210b64115760a4504efbc4daffdc390d309e9318eb0e3e3b78d2828"}, 749 | {file = "pyobjc_core-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:87901e9f7032f33eb4fa884e407bf2744d5a0791b379bfca783982a02be3f7fb"}, 750 | {file = "pyobjc_core-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:636971ab48a4198ca129e149fe58ccf85a7b4a9b93d27f5ae920d88eb2655431"}, 751 | {file = "pyobjc_core-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:48e9ac3af42b2340dae709a8b894f5ef7e5132d8546adcd1797cffcc449dabdc"}, 752 | {file = "pyobjc_core-10.3.2.tar.gz", hash = "sha256:dbf1475d864ce594288ce03e94e3a98dc7f0e4639971eb1e312bdf6661c21e0e"}, 753 | ] 754 | 755 | [[package]] 756 | name = "pyobjc-framework-cocoa" 757 | version = "10.3.2" 758 | description = "Wrappers for the Cocoa frameworks on macOS" 759 | category = "main" 760 | optional = false 761 | python-versions = ">=3.8" 762 | files = [ 763 | {file = "pyobjc_framework_Cocoa-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61f44c2adab28fdf3aa3d593c9497a2d9ceb9583ed9814adb48828c385d83ff4"}, 764 | {file = "pyobjc_framework_Cocoa-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7caaf8b260e81b27b7b787332846f644b9423bfc1536f6ec24edbde59ab77a87"}, 765 | {file = "pyobjc_framework_Cocoa-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c49e99fc4b9e613fb308651b99d52a8a9ae9916c8ef27aa2f5d585b6678a59bf"}, 766 | {file = "pyobjc_framework_Cocoa-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1161b5713f9b9934c12649d73a6749617172e240f9431eff9e22175262fdfda"}, 767 | {file = "pyobjc_framework_Cocoa-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:08e48b9ee4eb393447b2b781d16663b954bd10a26927df74f92e924c05568d89"}, 768 | {file = "pyobjc_framework_Cocoa-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7faa448d2038ae0e0287a326d390002e744bb6470e45995e2dbd16c892e4495a"}, 769 | {file = "pyobjc_framework_Cocoa-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:fcd53fee2be9708576617994b107aedc2c40824b648cd51e780e8399c0a447b6"}, 770 | {file = "pyobjc_framework_Cocoa-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:838fcf0d10674bde9ff64a3f20c0e188f2dc5e804476d80509b81c4ac1dabc59"}, 771 | {file = "pyobjc_framework_cocoa-10.3.2.tar.gz", hash = "sha256:673968e5435845bef969bfe374f31a1a6dc660c98608d2b84d5cae6eafa5c39d"}, 772 | ] 773 | 774 | [package.dependencies] 775 | pyobjc-core = ">=10.3.2" 776 | 777 | [[package]] 778 | name = "pyobjc-framework-corebluetooth" 779 | version = "10.3.2" 780 | description = "Wrappers for the framework CoreBluetooth on macOS" 781 | category = "main" 782 | optional = false 783 | python-versions = ">=3.8" 784 | files = [ 785 | {file = "pyobjc_framework_CoreBluetooth-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:af3e2f935a6a7e5b009b4cf63c64899592a7b46c3ddcbc8f2e28848842ef65f4"}, 786 | {file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:973b78f47c7e2209a475e60bcc7d1b4a87be6645d39b4e8290ee82640e1cc364"}, 787 | {file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4bafdf1be15eae48a4878dbbf1bf19877ce28cbbba5baa0267a9564719ee736e"}, 788 | {file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4d7dc7494de66c850bda7b173579df7481dc97046fa229d480fe9bf90b2b9651"}, 789 | {file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:62e09e730f4d98384f1b6d44718812195602b3c82d5c78e09f60e8a934e7b266"}, 790 | {file = "pyobjc_framework_corebluetooth-10.3.2.tar.gz", hash = "sha256:c0a077bc3a2466271efa382c1e024630bc43cc6f9ab8f3f97431ad08b1ad52bb"}, 791 | ] 792 | 793 | [package.dependencies] 794 | pyobjc-core = ">=10.3.2" 795 | pyobjc-framework-Cocoa = ">=10.3.2" 796 | 797 | [[package]] 798 | name = "pyobjc-framework-libdispatch" 799 | version = "10.3.2" 800 | description = "Wrappers for libdispatch on macOS" 801 | category = "main" 802 | optional = false 803 | python-versions = ">=3.8" 804 | files = [ 805 | {file = "pyobjc_framework_libdispatch-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35233a8b1135567c7696087f924e398799467c7f129200b559e8e4fa777af860"}, 806 | {file = "pyobjc_framework_libdispatch-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:061f6aa0f88d11d993e6546ec734303cb8979f40ae0f5cd23541236a6b426abd"}, 807 | {file = "pyobjc_framework_libdispatch-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6bb528f34538f35e1b79d839dbfc398dd426990e190d9301fe2d811fddc3da62"}, 808 | {file = "pyobjc_framework_libdispatch-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1357729d5fded08fbf746834ebeef27bee07d6acb991f3b8366e8f4319d882c4"}, 809 | {file = "pyobjc_framework_libdispatch-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:210398f9e1815ceeff49b578bf51c2d6a4a30d4c33f573da322f3d7da1add121"}, 810 | {file = "pyobjc_framework_libdispatch-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e7ae5988ac0b369ad40ce5497af71864fac45c289fa52671009b427f03d6871f"}, 811 | {file = "pyobjc_framework_libdispatch-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:f9d51d52dff453a4b19c096171a6cd31dd5e665371c00c1d72d480e1c22cd3d4"}, 812 | {file = "pyobjc_framework_libdispatch-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ef755bcabff2ea8db45603a8294818e0eeae85bf0b7b9d59e42f5947a26e33b9"}, 813 | {file = "pyobjc_framework_libdispatch-10.3.2.tar.gz", hash = "sha256:e9f4311fbf8df602852557a98d2a64f37a9d363acf4d75634120251bbc7b7304"}, 814 | ] 815 | 816 | [package.dependencies] 817 | pyobjc-core = ">=10.3.2" 818 | pyobjc-framework-Cocoa = ">=10.3.2" 819 | 820 | [[package]] 821 | name = "pyyaml" 822 | version = "6.0.2" 823 | description = "YAML parser and emitter for Python" 824 | category = "main" 825 | optional = false 826 | python-versions = ">=3.8" 827 | files = [ 828 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 829 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 830 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 831 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 832 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 833 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 834 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 835 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 836 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 837 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 838 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 839 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 840 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 841 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 842 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 843 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 844 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 845 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 846 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 847 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 848 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 849 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 850 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 851 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 852 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 853 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 854 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 855 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 856 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 857 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 858 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 859 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 860 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 861 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 862 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 863 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 864 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 865 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 866 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 867 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 868 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 869 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 870 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 871 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 872 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 873 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 874 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 875 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 876 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 877 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 878 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 879 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 880 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 881 | ] 882 | 883 | [[package]] 884 | name = "tomli" 885 | version = "2.2.1" 886 | description = "A lil' TOML parser" 887 | category = "dev" 888 | optional = false 889 | python-versions = ">=3.8" 890 | files = [ 891 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 892 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 893 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 894 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 895 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 896 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 897 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 898 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 899 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 900 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 901 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 902 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 903 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 904 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 905 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 906 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 907 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 908 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 909 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 910 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 911 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 912 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 913 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 914 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 915 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 916 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 917 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 918 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 919 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 920 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 921 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 922 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 923 | ] 924 | 925 | [[package]] 926 | name = "typing-extensions" 927 | version = "4.12.2" 928 | description = "Backported and Experimental Type Hints for Python 3.8+" 929 | category = "main" 930 | optional = false 931 | python-versions = ">=3.8" 932 | files = [ 933 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 934 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 935 | ] 936 | 937 | [[package]] 938 | name = "winrt-runtime" 939 | version = "2.3.0" 940 | description = "Python projection of Windows Runtime (WinRT) APIs" 941 | category = "main" 942 | optional = false 943 | python-versions = "<3.14,>=3.9" 944 | files = [ 945 | {file = "winrt_runtime-2.3.0-cp310-cp310-win32.whl", hash = "sha256:5c22ed339b420a6026134e28281b25078a9e6755eceb494dce5d42ee5814e3fd"}, 946 | {file = "winrt_runtime-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f3ef0d6b281a8d4155ea14a0f917faf82a004d4996d07beb2b3d2af191503fb1"}, 947 | {file = "winrt_runtime-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:93ce23df52396ed89dfe659ee0e1a968928e526b9c577942d4a54ad55b333644"}, 948 | {file = "winrt_runtime-2.3.0-cp311-cp311-win32.whl", hash = "sha256:352d70864846fd7ec89703845b82a35cef73f42d178a02a4635a38df5a61c0f8"}, 949 | {file = "winrt_runtime-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:286e6036af4903dd830398103c3edd110a46432347e8a52ba416d937c0e1f5f9"}, 950 | {file = "winrt_runtime-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:44d0f0f48f2f10c02b885989e8bbac41d7bf9c03550b20ddf562100356fca7a9"}, 951 | {file = "winrt_runtime-2.3.0-cp312-cp312-win32.whl", hash = "sha256:03d3e4aedc65832e57c0dbf210ec2a9d7fb2819c74d420ba889b323e9fa5cf28"}, 952 | {file = "winrt_runtime-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0dc636aec2f4ee6c3849fa59dae10c128f4a908f0ce452e91af65d812ea66dcb"}, 953 | {file = "winrt_runtime-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:d9f140c71e4f3bf7bf7d6853b246eab2e1632c72f218ff163aa41a74b576736f"}, 954 | {file = "winrt_runtime-2.3.0-cp313-cp313-win32.whl", hash = "sha256:77f06df6b7a6cb536913ae455e30c1733d31d88dafe2c3cd8c3d0e2bcf7e2a20"}, 955 | {file = "winrt_runtime-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:7388774b74ea2f4510ab3a98c95af296665ebe69d9d7e2fd7ee2c3fc5856099e"}, 956 | {file = "winrt_runtime-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:0d3a4ac7661cad492d51653054e63328b940a6083c1ee1dd977f90069cb8afaa"}, 957 | {file = "winrt_runtime-2.3.0-cp39-cp39-win32.whl", hash = "sha256:cd7bce2c7703054e7f64d11be665e9728e15d9dae0d952a51228fe830e0c4b55"}, 958 | {file = "winrt_runtime-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:2da01af378ab9374a3a933da97543f471a676a3b844318316869bffeff811e8a"}, 959 | {file = "winrt_runtime-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:1c6bbfcc7cbe1c8159ed5d776b30b7f1cbc2c6990803292823b0788c22d75636"}, 960 | {file = "winrt_runtime-2.3.0.tar.gz", hash = "sha256:bb895a2b8c74b375781302215e2661914369c625aa1f8df84f8d37691b22db77"}, 961 | ] 962 | 963 | [[package]] 964 | name = "winrt-windows-devices-bluetooth" 965 | version = "2.3.0" 966 | description = "Python projection of Windows Runtime (WinRT) APIs" 967 | category = "main" 968 | optional = false 969 | python-versions = "<3.14,>=3.9" 970 | files = [ 971 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp310-cp310-win32.whl", hash = "sha256:554aa6d0ca4bebc22a45f19fa60db1183a2b5643468f3c95cf0ebc33fbc1b0d0"}, 972 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:cec2682e10431f027c1823647772671fb09bebc1e8a00021a3651120b846d36f"}, 973 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:b4d42faef99845de2aded4c75c906f03cc3ba3df51fb4435e4cc88a19168cf99"}, 974 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp311-cp311-win32.whl", hash = "sha256:64e0992175d4d5a1160179a8c586c2202a0edbd47a5b6da4efdbc8bb601f2f99"}, 975 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:0830111c077508b599062fbe2d817203e4efa3605bd209cf4a3e03388ec39dda"}, 976 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:3943d538cb7b6bde3fd8741591eb6e23487ee9ee6284f05428b205e7d10b6d92"}, 977 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp312-cp312-win32.whl", hash = "sha256:544ed169039e6d5e250323cc18c87967cfeb4d3d09ce354fd7c5fd2283f3bb98"}, 978 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:f7becf095bf9bc999629fcb6401a88b879c3531b3c55c820e63259c955ddc06c"}, 979 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:a6a2980409c855b4e5dab0be9bde9f30236292ac1fc994df959fa5a518cd6690"}, 980 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp313-cp313-win32.whl", hash = "sha256:82f443be43379d4762e72633047c82843c873b6f26428a18855ca7b53e1958d7"}, 981 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:8b407da87ab52315c2d562a75d824dcafcae6e1628031cdb971072a47eb78ff0"}, 982 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:e36d0b487bc5b64662b8470085edf8bfa5a220d7afc4f2e8d7faa3e3ac2bae80"}, 983 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp39-cp39-win32.whl", hash = "sha256:6553023433edf5a75767e8962bf492d0623036975c7d8373d5bbccc633a77bbc"}, 984 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:77bdeadb043190c40ebbad462cd06e38b6461bc976bc67daf587e9395c387aae"}, 985 | {file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:c588ab79b534fedecce48f7082b419315e8d797d0120556166492e603e90d932"}, 986 | {file = "winrt_windows_devices_bluetooth-2.3.0.tar.gz", hash = "sha256:a1204b71c369a0399ec15d9a7b7c67990dd74504e486b839bf81825bd381a837"}, 987 | ] 988 | 989 | [package.dependencies] 990 | winrt-runtime = "2.3.0" 991 | 992 | [package.extras] 993 | all = ["winrt-Windows.Devices.Bluetooth.GenericAttributeProfile[all] (==2.3.0)", "winrt-Windows.Devices.Bluetooth.Rfcomm[all] (==2.3.0)", "winrt-Windows.Devices.Enumeration[all] (==2.3.0)", "winrt-Windows.Devices.Radios[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Networking[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)"] 994 | 995 | [[package]] 996 | name = "winrt-windows-devices-bluetooth-advertisement" 997 | version = "2.3.0" 998 | description = "Python projection of Windows Runtime (WinRT) APIs" 999 | category = "main" 1000 | optional = false 1001 | python-versions = "<3.14,>=3.9" 1002 | files = [ 1003 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp310-cp310-win32.whl", hash = "sha256:4386498e7794ed383542ea868f0aa2dd8fb5f09f12bdffde024d12bd9f5a3756"}, 1004 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6fa25b2541d2898ae17982e86e0977a639b04f75119612cb46e1719474513fd"}, 1005 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:b200ff5acd181353f61f5b6446176faf78a61867d8c1d21e77a15e239d2cdf6b"}, 1006 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp311-cp311-win32.whl", hash = "sha256:e56ad277813b48e35a3074f286c55a7a25884676e23ef9c3fc12349a42cb8fa4"}, 1007 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d6533fef6a5914dc8d519b83b1841becf6fd2f37163d6e07df318a6a6118f194"}, 1008 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:8f4369cb0108f8ee0cace559f9870b00a4dde3fc1abd52f84adba08bc733825c"}, 1009 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp312-cp312-win32.whl", hash = "sha256:d729d989acd7c1d703e2088299b6e219089a415db4a7b80cd52fdc507ec3ce95"}, 1010 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d3d258d4388a2b46f2e46f2fbdede1bf327eaa9c2dd4605f8a7fe454077c49e"}, 1011 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:d8c12457b00a79f8f1058d7a51bd8e7f177fb66e31389469e75b1104f6358921"}, 1012 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp313-cp313-win32.whl", hash = "sha256:ac1e55a350881f82cb51e162cb7a4b5d9359e9e5fbde922de802404a951d64ec"}, 1013 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0fc339340fb8be21c1c829816a49dc31b986c6d602d113d4a49ee8ffaf0e2396"}, 1014 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:da63d9c56edcb3b2d5135e65cc8c9c4658344dd480a8a2daf45beb2106f17874"}, 1015 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp39-cp39-win32.whl", hash = "sha256:e98c6ae4b0afd3e4f3ab4fa06e84d6017ff9242146a64e3bad73f7f34183a076"}, 1016 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdc485f4143fbbb3ae0c9c9ad03b1021a5cb233c6df65bf56ac14f8e22c918c3"}, 1017 | {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:7af519cc895be84d6974e9f70d102545a5e8db05e065903b0fd84521218e60a9"}, 1018 | {file = "winrt_windows_devices_bluetooth_advertisement-2.3.0.tar.gz", hash = "sha256:c8adbec690b765ca70337c35efec9910b0937a40a0a242184ea295367137f81c"}, 1019 | ] 1020 | 1021 | [package.dependencies] 1022 | winrt-runtime = "2.3.0" 1023 | 1024 | [package.extras] 1025 | all = ["winrt-Windows.Devices.Bluetooth[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)"] 1026 | 1027 | [[package]] 1028 | name = "winrt-windows-devices-bluetooth-genericattributeprofile" 1029 | version = "2.3.0" 1030 | description = "Python projection of Windows Runtime (WinRT) APIs" 1031 | category = "main" 1032 | optional = false 1033 | python-versions = "<3.14,>=3.9" 1034 | files = [ 1035 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp310-cp310-win32.whl", hash = "sha256:1ec75b107370827874d8435a47852d0459cb66d5694e02a833e0a75c4748e847"}, 1036 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:0a178aa936abbc56ae1cc54a222dee4a34ce6c09506a5b592d4f7d04dbe76b95"}, 1037 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:b7067b8578e19ad17b28694090d5b000fee57db5b219462155961b685d71fba5"}, 1038 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp311-cp311-win32.whl", hash = "sha256:e0aeba201e20b6c4bc18a4336b5b07d653d4ab4c9c17a301613db680a346cd5e"}, 1039 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:f87b3995de18b98075ec2b02afc7252873fa75e7c840eb770d7bfafb4fda5c12"}, 1040 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:7dccce04ec076666001efca8e2484d0ec444b2302ae150ef184aa253b8cfba09"}, 1041 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp312-cp312-win32.whl", hash = "sha256:1b97ef2ab9c9f5bae984989a47565d0d19c84969d74982a2664a4a3485cb8274"}, 1042 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:5fac2c7b301fa70e105785d7504176c76e4d824fc3823afed4d1ab6a7682272c"}, 1043 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:353fdccf2398b2a12e0835834cff8143a7efd9ba877fb5820fdcce531732b500"}, 1044 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp313-cp313-win32.whl", hash = "sha256:f414f793767ccc56d055b1c74830efb51fa4cbdc9163847b1a38b1ee35778f49"}, 1045 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ef35d9cda5bbdcc55aa7eaf143ab873227d6ee467aaf28edbd2428f229e7c94"}, 1046 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:6a9e7308ba264175c2a9ee31f6cf1d647cb35ee9a1da7350793d8fe033a6b9b8"}, 1047 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp39-cp39-win32.whl", hash = "sha256:aea58f7e484cf3480ab9472a3e99b61c157b8a47baae8694bc7400ea5335f5dc"}, 1048 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:992b792a9e7f5771ccdc18eec4e526a11f23b75d9be5de3ec552ff719333897a"}, 1049 | {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:66b030a9cc6099dafe4253239e8e625cc063bb9bb115bebed6260d92dd86f6b1"}, 1050 | {file = "winrt_windows_devices_bluetooth_genericattributeprofile-2.3.0.tar.gz", hash = "sha256:f40f94bf2f7243848dc10e39cfde76c9044727a05e7e5dfb8cb7f062f3fd3dda"}, 1051 | ] 1052 | 1053 | [package.dependencies] 1054 | winrt-runtime = "2.3.0" 1055 | 1056 | [package.extras] 1057 | all = ["winrt-Windows.Devices.Bluetooth[all] (==2.3.0)", "winrt-Windows.Devices.Enumeration[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)"] 1058 | 1059 | [[package]] 1060 | name = "winrt-windows-devices-enumeration" 1061 | version = "2.3.0" 1062 | description = "Python projection of Windows Runtime (WinRT) APIs" 1063 | category = "main" 1064 | optional = false 1065 | python-versions = "<3.14,>=3.9" 1066 | files = [ 1067 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp310-cp310-win32.whl", hash = "sha256:461360ab47967f39721e71276fdcfe87ad2f71ba7b09d721f2f88bcdf16a6924"}, 1068 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a7d7b01d43d5dcc1f3846db12f4c552155efae75469f36052623faed7f0f74a8"}, 1069 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:6478fbe6f45172a9911c15b061ec9b0f30c9f4845ba3fd1e9e1bb78c1fb691c4"}, 1070 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp311-cp311-win32.whl", hash = "sha256:30be5cba8e9e81ea8dd514ba1300b5bb14ad7cc4e32efe908ddddd14c73e7f61"}, 1071 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86c2a1865e0a0146dd4f51f17e3d773d3e6732742f61838c05061f28738c6dbd"}, 1072 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:1b50d9304e49a9f04bc8139831b75be968ff19a1f50529d5eb0081dae2103d92"}, 1073 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp312-cp312-win32.whl", hash = "sha256:42ed0349f0290a1b0a101425a06196c5d5db1240db6f8bd7d2204f23c48d727b"}, 1074 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:83e385fbf85b9511699d33c659673611f42b98bd3a554a85b377a34cc3b68b2e"}, 1075 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:26f855caee61c12449c6b07e22ea1ad470f8daa24223d8581e1fe622c70b48a8"}, 1076 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp313-cp313-win32.whl", hash = "sha256:a5f2cff6ee584e5627a2246bdbcd1b3a3fd1e7ae0741f62c59f7d5a5650d5791"}, 1077 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:7516171521aa383ccdc8f422cc202979a2359d0d1256f22852bfb0b55d9154f0"}, 1078 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:80d01dfffe4b548439242f3f7a737189354768b203cca023dc29b267dfe5595a"}, 1079 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp39-cp39-win32.whl", hash = "sha256:990a375cd8edc2d30b939a49dcc1349ede3a4b8e4da78baf0de5e5711d3a4f00"}, 1080 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e7bedf0eac2066d7d37b1d34071b95bb57024e9e083867be1d24e916e012ac0"}, 1081 | {file = "winrt_Windows.Devices.Enumeration-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:c53b673b80ba794f1c1320a5e0a14d795193c3f64b8132ebafba2f49c7301c2f"}, 1082 | {file = "winrt_windows_devices_enumeration-2.3.0.tar.gz", hash = "sha256:a14078aac41432781acb0c950fcdcdeb096e2f80f7591a3d46435f30221fc3eb"}, 1083 | ] 1084 | 1085 | [package.dependencies] 1086 | winrt-runtime = "2.3.0" 1087 | 1088 | [package.extras] 1089 | all = ["winrt-Windows.ApplicationModel.Background[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Security.Credentials[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)", "winrt-Windows.UI.Popups[all] (==2.3.0)", "winrt-Windows.UI[all] (==2.3.0)"] 1090 | 1091 | [[package]] 1092 | name = "winrt-windows-foundation" 1093 | version = "2.3.0" 1094 | description = "Python projection of Windows Runtime (WinRT) APIs" 1095 | category = "main" 1096 | optional = false 1097 | python-versions = "<3.14,>=3.9" 1098 | files = [ 1099 | {file = "winrt_Windows.Foundation-2.3.0-cp310-cp310-win32.whl", hash = "sha256:ea7b0e82be5c05690fedaf0dac5aa5e5fefd7ebf90b1497e5993197d305d916d"}, 1100 | {file = "winrt_Windows.Foundation-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:6807dd40f8ecd6403679f6eae0db81674fdcf33768d08fdee66e0a17b7a02515"}, 1101 | {file = "winrt_Windows.Foundation-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:0a861815e97ace82583210c03cf800507b0c3a97edd914bfffa5f88de1fbafcc"}, 1102 | {file = "winrt_Windows.Foundation-2.3.0-cp311-cp311-win32.whl", hash = "sha256:c79b3d9384128b6b28c2483b4600f15c5d32c1f6646f9d77fdb3ee9bbaef6f81"}, 1103 | {file = "winrt_Windows.Foundation-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fdd9c4914070dc598f5961d9c7571dd7d745f5cc60347603bf39d6ee921bd85c"}, 1104 | {file = "winrt_Windows.Foundation-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:62bbb0ffa273551d33fd533d6e09b6f9f633dc214225d483722af47d2525fb84"}, 1105 | {file = "winrt_Windows.Foundation-2.3.0-cp312-cp312-win32.whl", hash = "sha256:d36f472ac258e79eee6061e1bb4ce50bfd200f9271392d23479c800ca6aee8d1"}, 1106 | {file = "winrt_Windows.Foundation-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8de9b5e95a3fdabdb45b1952e05355dd5a678f80bf09a54d9f966dccc805b383"}, 1107 | {file = "winrt_Windows.Foundation-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:37da09c08c9c772baedb1958e5ee116fe63809f33c6820c69750f340b3dda292"}, 1108 | {file = "winrt_Windows.Foundation-2.3.0-cp313-cp313-win32.whl", hash = "sha256:2b00fad3f2a3859ccae41eee12ab44434813a371c2f3003b4f2419e5eecb4832"}, 1109 | {file = "winrt_Windows.Foundation-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:686619932b2a2c689cbebc7f5196437a45fd2056656ef130bb10240bb111086a"}, 1110 | {file = "winrt_Windows.Foundation-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b38dcb83fe82a7da9a57d7d5ad5deb09503b5be6d9357a9fd3016ca31673805d"}, 1111 | {file = "winrt_Windows.Foundation-2.3.0-cp39-cp39-win32.whl", hash = "sha256:2d6922de4dc38061b86d314c7319d7c6bd78a52d64ee0c93eb81474bddb499bc"}, 1112 | {file = "winrt_Windows.Foundation-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:1513e43adff3779d2f611d8bdf9350ac1a7c04389e9e6b1d777c5cd54f46e4fc"}, 1113 | {file = "winrt_Windows.Foundation-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:c811e4a4f79b947fbbb50f74d34ef6840dd2dd26e0199bd61a4185e48c6a84a8"}, 1114 | {file = "winrt_windows_foundation-2.3.0.tar.gz", hash = "sha256:c5766f011c8debbe89b460af4a97d026ca252144e62d7278c9c79c5581ea0c02"}, 1115 | ] 1116 | 1117 | [package.dependencies] 1118 | winrt-runtime = "2.3.0" 1119 | 1120 | [package.extras] 1121 | all = ["winrt-Windows.Foundation.Collections[all] (==2.3.0)"] 1122 | 1123 | [[package]] 1124 | name = "winrt-windows-foundation-collections" 1125 | version = "2.3.0" 1126 | description = "Python projection of Windows Runtime (WinRT) APIs" 1127 | category = "main" 1128 | optional = false 1129 | python-versions = "<3.14,>=3.9" 1130 | files = [ 1131 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp310-cp310-win32.whl", hash = "sha256:d2fca59eef9582a33c2797b1fda1d5757d66827cc34e6fc1d1c94a5875c4c043"}, 1132 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d14b47d9137aebad71aa4fde5892673f2fa326f5f4799378cb9f6158b07a9824"}, 1133 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:cca5398a4522dffd76decf64a28368cda67e81dc01cad35a9f39cc351af69bdd"}, 1134 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp311-cp311-win32.whl", hash = "sha256:3808af64c95a9b464e8e97f6bec57a8b22168185f1c893f30de69aaf48c85b17"}, 1135 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e9a3842a39feb965545124abfe79ed726adc5a1fc6a192470a3c5d3ec3f7a74"}, 1136 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:751c2a68fef080dfe0af892ef4cebf317844e4baa786e979028757fe2740fba4"}, 1137 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp312-cp312-win32.whl", hash = "sha256:498c1fc403d3dc7a091aaac92af471615de4f9550d544347cb3b169c197183b5"}, 1138 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:4d1b1cacc159f38d8e6b662f6e7a5c41879a36aa7434c1580d7f948c9037419e"}, 1139 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:398d93b76a2cf70d5e75c1f802e1dd856501e63bc9a31f4510ac59f718951b9e"}, 1140 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp313-cp313-win32.whl", hash = "sha256:1e5f1637e0919c7bb5b11ba1eebbd43bc0ad9600cf887b59fcece0f8a6c0eac3"}, 1141 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:c809a70bc0f93d53c7289a0a86d8869740e09fff0c57318a14401f5c17e0b912"}, 1142 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:269942fe86af06293a2676c8b2dcd5cb1d8ddfe1b5244f11c16e48ae0a5d100f"}, 1143 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp39-cp39-win32.whl", hash = "sha256:936b1c5720b564ec699673198addee97f3bdb790622d24c8fd1b346a9767717c"}, 1144 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:905a6ac9cd6b51659a9bba08cf44cfc925f528ef34cdd9c3a6c2632e97804a96"}, 1145 | {file = "winrt_Windows.Foundation.Collections-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:1d6eac85976bd831e1b8cc479d7f14afa51c27cec5a38e2540077d3400cbd3ef"}, 1146 | {file = "winrt_windows_foundation_collections-2.3.0.tar.gz", hash = "sha256:15c997fd6b64ef0400a619319ea3c6851c9c24e31d51b6448ba9bac3616d25a0"}, 1147 | ] 1148 | 1149 | [package.dependencies] 1150 | winrt-runtime = "2.3.0" 1151 | 1152 | [package.extras] 1153 | all = ["winrt-Windows.Foundation[all] (==2.3.0)"] 1154 | 1155 | [[package]] 1156 | name = "winrt-windows-storage-streams" 1157 | version = "2.3.0" 1158 | description = "Python projection of Windows Runtime (WinRT) APIs" 1159 | category = "main" 1160 | optional = false 1161 | python-versions = "<3.14,>=3.9" 1162 | files = [ 1163 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp310-cp310-win32.whl", hash = "sha256:2c0901aee1232e92ed9320644b853d7801a0bdb87790164d56e961cd39910f07"}, 1164 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba07dc25decffd29aa8603119629c167bd03fa274099e3bad331a4920c292b78"}, 1165 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:5b60b48460095c50a00a6f7f9b3b780f5bdcb1ec663fc09458201499f93e23ea"}, 1166 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp311-cp311-win32.whl", hash = "sha256:8388f37759df64ceef1423ae7dd9275c8a6eb3b8245d400173b4916adc94b5ad"}, 1167 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:e5783dbe3694cc3deda594256ebb1088655386959bb834a6bfb7cd763ee87631"}, 1168 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:0a487d19c73b82aafa3d5ef889bb35e6e8e2487ca4f16f5446f2445033d5219c"}, 1169 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp312-cp312-win32.whl", hash = "sha256:272e87e6c74cb2832261ab33db7966a99e7a2400240cc4f8bf526a80ca054c68"}, 1170 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:997bf1a2d52c5f104b172947e571f27d9916a4409b4da592ec3e7f907848dd1a"}, 1171 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:d56daa00205c24ede6669d41eb70d6017e0202371d99f8ee2b0b31350ab59bd5"}, 1172 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp313-cp313-win32.whl", hash = "sha256:7ac4e46fc5e21d8badc5d41779273c3f5e7196f1cf2df1959b6b70eca1d5d85f"}, 1173 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1460027c94c107fcee484997494f3a400f08ee40396f010facb0e72b3b74c457"}, 1174 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:e4553a70f5264a7733596802a2991e2414cdcd5e396b9d11ee87be9abae9329e"}, 1175 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp39-cp39-win32.whl", hash = "sha256:28e1117e23046e499831af16d11f5e61e6066ed6247ef58b93738702522c29b0"}, 1176 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:5511dc578f92eb303aee4d3345ee4ffc88aa414564e43e0e3d84ff29427068f0"}, 1177 | {file = "winrt_Windows.Storage.Streams-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6f5b3f8af4df08f5bf9329373949236ffaef22d021070278795e56da5326a876"}, 1178 | {file = "winrt_windows_storage_streams-2.3.0.tar.gz", hash = "sha256:d2c010beeb1dd7c135ed67ecfaea13440474a7c469e2e9aa2852db27d2063d44"}, 1179 | ] 1180 | 1181 | [package.dependencies] 1182 | winrt-runtime = "2.3.0" 1183 | 1184 | [package.extras] 1185 | all = ["winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Storage[all] (==2.3.0)", "winrt-Windows.System[all] (==2.3.0)"] 1186 | 1187 | [[package]] 1188 | name = "yarl" 1189 | version = "1.15.2" 1190 | description = "Yet another URL library" 1191 | category = "main" 1192 | optional = false 1193 | python-versions = ">=3.8" 1194 | files = [ 1195 | {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, 1196 | {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, 1197 | {file = "yarl-1.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43ebdcc120e2ca679dba01a779333a8ea76b50547b55e812b8b92818d604662c"}, 1198 | {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433da95b51a75692dcf6cc8117a31410447c75a9a8187888f02ad45c0a86c50"}, 1199 | {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d0124fa992dbacd0c48b1b755d3ee0a9f924f427f95b0ef376556a24debf01"}, 1200 | {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ded1b1803151dd0f20a8945508786d57c2f97a50289b16f2629f85433e546d47"}, 1201 | {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace4cad790f3bf872c082366c9edd7f8f8f77afe3992b134cfc810332206884f"}, 1202 | {file = "yarl-1.15.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c77494a2f2282d9bbbbcab7c227a4d1b4bb829875c96251f66fb5f3bae4fb053"}, 1203 | {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7f227ca6db5a9fda0a2b935a2ea34a7267589ffc63c8045f0e4edb8d8dcf956"}, 1204 | {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:31561a5b4d8dbef1559b3600b045607cf804bae040f64b5f5bca77da38084a8a"}, 1205 | {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e52474256a7db9dcf3c5f4ca0b300fdea6c21cca0148c8891d03a025649d935"}, 1206 | {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1af74a9529a1137c67c887ed9cde62cff53aa4d84a3adbec329f9ec47a3936"}, 1207 | {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:15c87339490100c63472a76d87fe7097a0835c705eb5ae79fd96e343473629ed"}, 1208 | {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:74abb8709ea54cc483c4fb57fb17bb66f8e0f04438cff6ded322074dbd17c7ec"}, 1209 | {file = "yarl-1.15.2-cp310-cp310-win32.whl", hash = "sha256:ffd591e22b22f9cb48e472529db6a47203c41c2c5911ff0a52e85723196c0d75"}, 1210 | {file = "yarl-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:1695497bb2a02a6de60064c9f077a4ae9c25c73624e0d43e3aa9d16d983073c2"}, 1211 | {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fcda20b2de7042cc35cf911702fa3d8311bd40055a14446c1e62403684afdc5"}, 1212 | {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0545de8c688fbbf3088f9e8b801157923be4bf8e7b03e97c2ecd4dfa39e48e0e"}, 1213 | {file = "yarl-1.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbda058a9a68bec347962595f50546a8a4a34fd7b0654a7b9697917dc2bf810d"}, 1214 | {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ac2bc069f4a458634c26b101c2341b18da85cb96afe0015990507efec2e417"}, 1215 | {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd126498171f752dd85737ab1544329a4520c53eed3997f9b08aefbafb1cc53b"}, 1216 | {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db817b4e95eb05c362e3b45dafe7144b18603e1211f4a5b36eb9522ecc62bcf"}, 1217 | {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:076b1ed2ac819933895b1a000904f62d615fe4533a5cf3e052ff9a1da560575c"}, 1218 | {file = "yarl-1.15.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8cfd847e6b9ecf9f2f2531c8427035f291ec286c0a4944b0a9fce58c6446046"}, 1219 | {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32b66be100ac5739065496c74c4b7f3015cef792c3174982809274d7e51b3e04"}, 1220 | {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34a2d76a1984cac04ff8b1bfc939ec9dc0914821264d4a9c8fd0ed6aa8d4cfd2"}, 1221 | {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0afad2cd484908f472c8fe2e8ef499facee54a0a6978be0e0cff67b1254fd747"}, 1222 | {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c68e820879ff39992c7f148113b46efcd6ec765a4865581f2902b3c43a5f4bbb"}, 1223 | {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:98f68df80ec6ca3015186b2677c208c096d646ef37bbf8b49764ab4a38183931"}, 1224 | {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56ec1eacd0a5d35b8a29f468659c47f4fe61b2cab948ca756c39b7617f0aa5"}, 1225 | {file = "yarl-1.15.2-cp311-cp311-win32.whl", hash = "sha256:eedc3f247ee7b3808ea07205f3e7d7879bc19ad3e6222195cd5fbf9988853e4d"}, 1226 | {file = "yarl-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ccaa1bc98751fbfcf53dc8dfdb90d96e98838010fc254180dd6707a6e8bb179"}, 1227 | {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d5161e8cb8f36ec778fd7ac4d740415d84030f5b9ef8fe4da54784a1f46c94"}, 1228 | {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa2bea05ff0a8fb4d8124498e00e02398f06d23cdadd0fe027d84a3f7afde31e"}, 1229 | {file = "yarl-1.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99e12d2bf587b44deb74e0d6170fec37adb489964dbca656ec41a7cd8f2ff178"}, 1230 | {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:243fbbbf003754fe41b5bdf10ce1e7f80bcc70732b5b54222c124d6b4c2ab31c"}, 1231 | {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:856b7f1a7b98a8c31823285786bd566cf06226ac4f38b3ef462f593c608a9bd6"}, 1232 | {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:553dad9af802a9ad1a6525e7528152a015b85fb8dbf764ebfc755c695f488367"}, 1233 | {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c3ff305f6e06650a761c4393666f77384f1cc6c5c0251965d6bfa5fbc88f7f"}, 1234 | {file = "yarl-1.15.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:353665775be69bbfc6d54c8d134bfc533e332149faeddd631b0bc79df0897f46"}, 1235 | {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f4fe99ce44128c71233d0d72152db31ca119711dfc5f2c82385ad611d8d7f897"}, 1236 | {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9c1e3ff4b89cdd2e1a24c214f141e848b9e0451f08d7d4963cb4108d4d798f1f"}, 1237 | {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:711bdfae4e699a6d4f371137cbe9e740dc958530cb920eb6f43ff9551e17cfbc"}, 1238 | {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4388c72174868884f76affcdd3656544c426407e0043c89b684d22fb265e04a5"}, 1239 | {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0e1844ad47c7bd5d6fa784f1d4accc5f4168b48999303a868fe0f8597bde715"}, 1240 | {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5cafb02cf097a82d74403f7e0b6b9df3ffbfe8edf9415ea816314711764a27b"}, 1241 | {file = "yarl-1.15.2-cp312-cp312-win32.whl", hash = "sha256:156ececdf636143f508770bf8a3a0498de64da5abd890c7dbb42ca9e3b6c05b8"}, 1242 | {file = "yarl-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:435aca062444a7f0c884861d2e3ea79883bd1cd19d0a381928b69ae1b85bc51d"}, 1243 | {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:416f2e3beaeae81e2f7a45dc711258be5bdc79c940a9a270b266c0bec038fb84"}, 1244 | {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:173563f3696124372831007e3d4b9821746964a95968628f7075d9231ac6bb33"}, 1245 | {file = "yarl-1.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ce2e0f6123a60bd1a7f5ae3b2c49b240c12c132847f17aa990b841a417598a2"}, 1246 | {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaea112aed589131f73d50d570a6864728bd7c0c66ef6c9154ed7b59f24da611"}, 1247 | {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ca3b9f370f218cc2a0309542cab8d0acdfd66667e7c37d04d617012485f904"}, 1248 | {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23ec1d3c31882b2a8a69c801ef58ebf7bae2553211ebbddf04235be275a38548"}, 1249 | {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75119badf45f7183e10e348edff5a76a94dc19ba9287d94001ff05e81475967b"}, 1250 | {file = "yarl-1.15.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e6fdc976ec966b99e4daa3812fac0274cc28cd2b24b0d92462e2e5ef90d368"}, 1251 | {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8657d3f37f781d987037f9cc20bbc8b40425fa14380c87da0cb8dfce7c92d0fb"}, 1252 | {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:93bed8a8084544c6efe8856c362af08a23e959340c87a95687fdbe9c9f280c8b"}, 1253 | {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69d5856d526802cbda768d3e6246cd0d77450fa2a4bc2ea0ea14f0d972c2894b"}, 1254 | {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ccad2800dfdff34392448c4bf834be124f10a5bc102f254521d931c1c53c455a"}, 1255 | {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a880372e2e5dbb9258a4e8ff43f13888039abb9dd6d515f28611c54361bc5644"}, 1256 | {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c998d0558805860503bc3a595994895ca0f7835e00668dadc673bbf7f5fbfcbe"}, 1257 | {file = "yarl-1.15.2-cp313-cp313-win32.whl", hash = "sha256:533a28754e7f7439f217550a497bb026c54072dbe16402b183fdbca2431935a9"}, 1258 | {file = "yarl-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:5838f2b79dc8f96fdc44077c9e4e2e33d7089b10788464609df788eb97d03aad"}, 1259 | {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fbbb63bed5fcd70cd3dd23a087cd78e4675fb5a2963b8af53f945cbbca79ae16"}, 1260 | {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2e93b88ecc8f74074012e18d679fb2e9c746f2a56f79cd5e2b1afcf2a8a786b"}, 1261 | {file = "yarl-1.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af8ff8d7dc07ce873f643de6dfbcd45dc3db2c87462e5c387267197f59e6d776"}, 1262 | {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66f629632220a4e7858b58e4857927dd01a850a4cef2fb4044c8662787165cf7"}, 1263 | {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833547179c31f9bec39b49601d282d6f0ea1633620701288934c5f66d88c3e50"}, 1264 | {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa738e0282be54eede1e3f36b81f1e46aee7ec7602aa563e81e0e8d7b67963f"}, 1265 | {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a13a07532e8e1c4a5a3afff0ca4553da23409fad65def1b71186fb867eeae8d"}, 1266 | {file = "yarl-1.15.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c45817e3e6972109d1a2c65091504a537e257bc3c885b4e78a95baa96df6a3f8"}, 1267 | {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:670eb11325ed3a6209339974b276811867defe52f4188fe18dc49855774fa9cf"}, 1268 | {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:d417a4f6943112fae3924bae2af7112562285848d9bcee737fc4ff7cbd450e6c"}, 1269 | {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bc8936d06cd53fddd4892677d65e98af514c8d78c79864f418bbf78a4a2edde4"}, 1270 | {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:954dde77c404084c2544e572f342aef384240b3e434e06cecc71597e95fd1ce7"}, 1271 | {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5bc0df728e4def5e15a754521e8882ba5a5121bd6b5a3a0ff7efda5d6558ab3d"}, 1272 | {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b71862a652f50babab4a43a487f157d26b464b1dedbcc0afda02fd64f3809d04"}, 1273 | {file = "yarl-1.15.2-cp38-cp38-win32.whl", hash = "sha256:63eab904f8630aed5a68f2d0aeab565dcfc595dc1bf0b91b71d9ddd43dea3aea"}, 1274 | {file = "yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9"}, 1275 | {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a32d58f4b521bb98b2c0aa9da407f8bd57ca81f34362bcb090e4a79e9924fefc"}, 1276 | {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:766dcc00b943c089349d4060b935c76281f6be225e39994c2ccec3a2a36ad627"}, 1277 | {file = "yarl-1.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bed1b5dbf90bad3bfc19439258c97873eab453c71d8b6869c136346acfe497e7"}, 1278 | {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed20a4bdc635f36cb19e630bfc644181dd075839b6fc84cac51c0f381ac472e2"}, 1279 | {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d538df442c0d9665664ab6dd5fccd0110fa3b364914f9c85b3ef9b7b2e157980"}, 1280 | {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c6cf1d92edf936ceedc7afa61b07e9d78a27b15244aa46bbcd534c7458ee1b"}, 1281 | {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce44217ad99ffad8027d2fde0269ae368c86db66ea0571c62a000798d69401fb"}, 1282 | {file = "yarl-1.15.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47a6000a7e833ebfe5886b56a31cb2ff12120b1efd4578a6fcc38df16cc77bd"}, 1283 | {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e52f77a0cd246086afde8815039f3e16f8d2be51786c0a39b57104c563c5cbb0"}, 1284 | {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f9ca0e6ce7774dc7830dc0cc4bb6b3eec769db667f230e7c770a628c1aa5681b"}, 1285 | {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:136f9db0f53c0206db38b8cd0c985c78ded5fd596c9a86ce5c0b92afb91c3a19"}, 1286 | {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:173866d9f7409c0fb514cf6e78952e65816600cb888c68b37b41147349fe0057"}, 1287 | {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:6e840553c9c494a35e449a987ca2c4f8372668ee954a03a9a9685075228e5036"}, 1288 | {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:458c0c65802d816a6b955cf3603186de79e8fdb46d4f19abaec4ef0a906f50a7"}, 1289 | {file = "yarl-1.15.2-cp39-cp39-win32.whl", hash = "sha256:5b48388ded01f6f2429a8c55012bdbd1c2a0c3735b3e73e221649e524c34a58d"}, 1290 | {file = "yarl-1.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:81dadafb3aa124f86dc267a2168f71bbd2bfb163663661ab0038f6e4b8edb810"}, 1291 | {file = "yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a"}, 1292 | {file = "yarl-1.15.2.tar.gz", hash = "sha256:a39c36f4218a5bb668b4f06874d676d35a035ee668e6e7e3538835c703634b84"}, 1293 | ] 1294 | 1295 | [package.dependencies] 1296 | idna = ">=2.0" 1297 | multidict = ">=4.0" 1298 | propcache = ">=0.2.0" 1299 | 1300 | [metadata] 1301 | lock-version = "2.0" 1302 | python-versions = ">=3.8,<3.14" 1303 | content-hash = "cfe54773ec2d0b67e7601d01b5a7d207aa6bcdeabeadcfd923f592d26677da3d" 1304 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "linak-controller" 3 | version = "1.3.0-dev" 4 | description = "Command line tool for controlling the Linak standing desks" 5 | authors = ["Rhys Tyers"] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = ">=3.8,<3.14" 10 | aiohttp = "^3.8.4" 11 | appdirs = "^1.4.4" 12 | bleak = "^0.22.3" 13 | PyYAML = "^6.0" 14 | 15 | [tool.poetry.scripts] 16 | linak-controller = 'linak_controller.main:init' 17 | idasen-controller = 'linak_controller.main:init' 18 | 19 | [tool.poetry.group.dev.dependencies] 20 | black = "^23.10.1" 21 | 22 | [build-system] 23 | requires = ["poetry-core>=1.0.0"] 24 | build-backend = "poetry.core.masonry.api" -------------------------------------------------------------------------------- /recipes/RECIPES.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | ## Linux 4 | 5 | ### Albert Launcher 6 | 7 | You can use the [albert](https://github.com/albertlauncher/albert) launcher along with two `.desktop` files to allow you to trigger this script from the launcher. An example of a desktop file for this is: 8 | 9 | ``` 10 | [Desktop Entry] 11 | Name=Desk - Sit 12 | Exec=/path/to/linak-controller --move-to sit 13 | Icon=/home/user/linak-controller/sit-icon.png 14 | Type=Application 15 | Comment=Lower desk to sitting height. 16 | 17 | ``` 18 | 19 | (You can find the `linak-controller` path with `where linak-controller`) 20 | 21 | ### Scheduled standing periods 22 | 23 | You can add some cron jobs to automatically raise and lower your desk. This way, the healthier habit is automatic. 24 | The following cron raises the desk at 10 AM and 3 PM, and lowers it an hour later, Monday through Friday. 25 | 26 | ``` 27 | 00 10 * * 1-5 python3 linak-controller --move-to stand 28 | 00 11 * * 1-5 python3 linak-controller --move-to sit 29 | 00 15 * * 1-5 python3 linak-controller --move-to stand 30 | 00 16 * * 1-5 python3 linak-controller --move-to sit 31 | ``` 32 | 33 | ## macOS 34 | 35 | ### Scheduled new body positions 36 | 37 | Get into different body positions regularly, the next position is the best! 38 | 39 | 1. Find out path of `linak-controller` 40 | 41 | ``` 42 | which linak-controller 43 | ``` 44 | 45 | For example installed with Homebrew `/opt/homebrew/bin/linak-controller` 46 | 47 | 48 | 2. Edit crontab 49 | 50 | ``` 51 | crontab -e 52 | ``` 53 | 54 | 3. Insert schedules to move desk to stand and sit position. Replace `` from 1. step 55 | 56 | ``` 57 | 00 6-18/2 * * 1-5 /linak-controller --move-to stand >/dev/null 2>&1 58 | 25 6-18/2 * * 1-5 /linak-controller --move-to sit >/dev/null 2>&1 59 | 50 6-18/2 * * 1-5 /linak-controller --move-to stand >/dev/null 2>&1 60 | 10 7-18/2 * * 1-5 /linak-controller --move-to sit >/dev/null 2>&1 61 | 30 7-18/2 * * 1-5 /linak-controller --move-to stand >/dev/null 2>&1 62 | 45 7-18/2 * * 1-5 /linak-controller --move-to sit >/dev/null 2>&1 63 | ``` 64 | 65 | This cronjob runs between 6am and 6pm, for example: 66 | 67 | ..., 8:00↑, 8:25↓, 8:50↑, 9:10↓, 9:30↑, 9:45↓, 10:00↑, 10:25↓, ... 68 | 69 | Note: Long-lasting constant body positions should be avoided. This applies to sitting and(!) standing. 70 | 71 | 4. Add `linak-controller` and `cron` in macOS *System Preferences* → *Security* → *Bluetooth* 72 | 73 | Without this important step `linak-controller` will not move the desk because Bluetooth permissions are missing. 74 | 75 | ![macOS System Preferences](images/macos-system-preferences-security-bluetooth.png) 76 | 77 | 5. See installed cron jobs 78 | 79 | ``` 80 | crontab -l 81 | ``` 82 | 83 | Successfully tested with macOS 12.5, iTerm2 3.4.16, 84 | 85 | ## Windows 86 | 87 | ### Autohotkey 88 | 89 | A AutoHotkey script from @aienabled to drive the desk to stand and sit mode by pressing Ctrl+Alt+Shift+Up or Down arrow respectively: 90 | 91 | ``` 92 | ;Idasen Desk - Stand 93 | ^!+Up::Run "C:\Users\...\Desk - Stand.lnk" 94 | 95 | ;Idasen Desk - Sit 96 | ^!+Down::Run "C:\Users\...\Desk - Sit.lnk" 97 | ``` 98 | 99 | These are shortcut files on the desktop but it's not necessary and could be simple python calls. 100 | 101 | ## Docker 102 | 103 | In the docker directory is a `docker-compose.yml` file that will run the linak-controller in server mode with `docker compose up -d`. 104 | -------------------------------------------------------------------------------- /recipes/docker/.gitignore: -------------------------------------------------------------------------------- 1 | /data/ 2 | -------------------------------------------------------------------------------- /recipes/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | 3 | WORKDIR /app 4 | # allows Python's output to be seen in the log: 5 | ENV PYTHONUNBUFFERED=1 6 | 7 | # install required packages: 8 | RUN apt-get update && \ 9 | apt-get install -y bluez bluetooth 10 | RUN pip install poetry 11 | 12 | # linak-controller dependencies: 13 | COPY poetry.lock pyproject.toml ./ 14 | RUN poetry install 15 | 16 | COPY ./ ./ 17 | 18 | ENTRYPOINT ["sh", "recipes/docker/docker_entrypoint.sh"] 19 | -------------------------------------------------------------------------------- /recipes/docker/Dockerfile.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !poetry.lock 3 | !pyproject.toml 4 | !recipes/docker/docker_entrypoint.sh 5 | !linak_controller/ 6 | -------------------------------------------------------------------------------- /recipes/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Do `sudo killall -9 bluetoothd` on your host before starting. 2 | 3 | version: '3' 4 | services: 5 | linakcontroller: 6 | build: 7 | context: ../.. 8 | dockerfile: recipes/docker/Dockerfile 9 | restart: always 10 | 11 | volumes: 12 | # your configuration file: 13 | - "./data/config.yaml:/app/config.yaml:ro" 14 | # remember paired bluetooth devices: 15 | - "./data/bluetooth/:/var/lib/bluetooth/:rw" 16 | 17 | # required for bluetooth: 18 | network_mode: host 19 | cap_add: 20 | - NET_ADMIN 21 | - SYS_ADMIN 22 | -------------------------------------------------------------------------------- /recipes/docker/docker_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # From https://stackoverflow.com/a/64126744 4 | service dbus start 5 | bluetoothd & 6 | 7 | /bin/bash 8 | 9 | poetry run python -m linak_controller.main --config config.yaml --tcp-server 10 | -------------------------------------------------------------------------------- /recipes/images/macos-system-preferences-security-bluetooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhyst/linak-controller/28b3f663f09a8f97589cf185697c61eae111298e/recipes/images/macos-system-preferences-security-bluetooth.png -------------------------------------------------------------------------------- /reference/advertised-services.md: -------------------------------------------------------------------------------- 1 | Raw advertised services: 2 | 3 | ``` 4 | [NEW] Primary Service (Handle 0x72a1) 5 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service000a 6 | 00001801-0000-1000-8000-00805f9b34fb 7 | Generic Attribute Profile 8 | [NEW] Characteristic (Handle 0x72a1) INDICATE? 0x20 9 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service000a/char000b 10 | 00002a05-0000-1000-8000-00805f9b34fb 11 | Service Changed 12 | [NEW] Descriptor (Handle 0x98e4) CLIENT_CHARACTERISTIC_CONFIG_UUID / NOTIFCATION ? 13 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service000a/char000b/desc000d 14 | 00002902-0000-1000-8000-00805f9b34fb 15 | Client Characteristic Configuration 16 | [NEW] Primary Service (Handle 0x72a1) CONTROL 17 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service000e 18 | 99fa0001-338a-1024-8a49-009c0215f78a 19 | Vendor specific 20 | [NEW] Characteristic (Handle 0x72a1) COMMAND 0x0c 21 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service000e/char000f 22 | 99fa0002-338a-1024-8a49-009c0215f78a 23 | Vendor specific 24 | [NEW] Characteristic (Handle 0x72a1) ERROR 0x12 25 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service000e/char0011 26 | 99fa0003-338a-1024-8a49-009c0215f78a 27 | Vendor specific 28 | [NEW] Descriptor (Handle 0xa514) CLIENT_CHARACTERISTIC_CONFIG_UUID / NOTIFCATION ? 29 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service000e/char0011/desc0013 30 | 00002902-0000-1000-8000-00805f9b34fb 31 | Client Characteristic Configuration 32 | [NEW] Primary Service (Handle 0x72a1) DPG 33 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0014 34 | 99fa0010-338a-1024-8a49-009c0215f78a 35 | Vendor specific 36 | [NEW] Characteristic (Handle 0x72a1) DPG 0x1e 37 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0014/char0015 38 | 99fa0011-338a-1024-8a49-009c0215f78a 39 | Vendor specific 40 | [NEW] Descriptor (Handle 0xae54) CLIENT_CHARACTERISTIC_CONFIG_UUID / NOTIFCATION ? 41 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0014/char0015/desc0017 42 | 00002902-0000-1000-8000-00805f9b34fb 43 | Client Characteristic Configuration 44 | [NEW] Primary Service (Handle 0x72a1) REFERENCE_OUTPUT 45 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0018 46 | 99fa0020-338a-1024-8a49-009c0215f78a 47 | Vendor specific 48 | [NEW] Characteristic (Handle 0x72a1) ONE - NOTIFIES WHEN DESK MOVES 0x12 49 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0018/char0019 50 | 99fa0021-338a-1024-8a49-009c0215f78a 51 | Vendor specific 52 | [NEW] Descriptor (Handle 0xbc64) CLIENT_CHARACTERISTIC_CONFIG_UUID / NOTIFCATION ? 53 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0018/char0019/desc001b 54 | 00002902-0000-1000-8000-00805f9b34fb 55 | Client Characteristic Configuration 56 | [NEW] Characteristic (Handle 0x72a1) MASK 0x02 57 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0018/char001c 58 | 99fa0029-338a-1024-8a49-009c0215f78a 59 | Vendor specific 60 | [NEW] Characteristic (Handle 0x72a1) DETECT_MASK 0x02 61 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0018/char001e 62 | 99fa002a-338a-1024-8a49-009c0215f78a 63 | Vendor specific 64 | [NEW] Primary Service (Handle 0x72a1) REFERENCE_INPUT 65 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0020 66 | 99fa0030-338a-1024-8a49-009c0215f78a 67 | Vendor specific 68 | [NEW] Characteristic (Handle 0x72a1) ONE 0x0c 69 | /org/bluez/hci0/dev_E8_5B_5B_24_22_E4/service0020/char0021 70 | 99fa0031-338a-1024-8a49-009c0215f78a 71 | Vendor specific 72 | ``` 73 | 74 | My guess at what these are: 75 | 76 | ``` 77 | 00002a05-0000-1000-8000-00805f9b34fb INDICATE? 0x20 78 | 99fa0002-338a-1024-8a49-009c0215f78a COMMAND 0x0c 79 | 99fa0003-338a-1024-8a49-009c0215f78a ERROR 0x12 80 | 99fa0011-338a-1024-8a49-009c0215f78a DPG 0x1e 81 | 99fa0021-338a-1024-8a49-009c0215f78a ONE (REFERENCE_OUTPUT) 0x12 82 | 99fa0029-338a-1024-8a49-009c0215f78a MASK 0x02 83 | 99fa002a-338a-1024-8a49-009c0215f78a DETECT_MASK 0x02 84 | 99fa0030-338a-1024-8a49-009c0215f78a ONE (REFERENCE_INPUT) 0x0c 85 | ``` 86 | 87 | Writing to the characteristices: 88 | 89 | Writing to command takes little endian unsigned shorts 90 | Writing to Reference Input takes little endian signed shorts 91 | 92 | ``` 93 | import java.math.BigInteger; 94 | // Code to convert the byte array declarations in the decompiled app to numbers 95 | public class HelloWorld{ 96 | 97 | /* Try this: */ 98 | public static short byteArrayToShortLE(final byte[] b, final int offset) 99 | { 100 | short value = 0; 101 | for (int i = 0; i < 2; i++) 102 | { 103 | value |= (b[i + offset] & 0x000000FF) << (i * 8); 104 | } 105 | 106 | return value; 107 | } 108 | 109 | public static void main(String []args){ 110 | byte[] b = new byte[]{-1, 127}; 111 | int i = new BigInteger(b).intValue(); 112 | System.out.println(byteArrayToShortLE(b, 0)); 113 | } 114 | } 115 | ``` 116 | 117 | Convert characteristic value: 118 | 119 | ``` 120 | raw = characteristic.read_value() 121 | print("Inital height: {}".format(int.from_bytes(bytes([int(raw[0])]) + bytes([int(raw[1])]), 'little'))) 122 | ``` 123 | -------------------------------------------------------------------------------- /reference/desk-internals.md: -------------------------------------------------------------------------------- 1 | ## Connection and Commands 2 | 3 | Connecting and pairing can be done by any bluetooth device and there is no authentication. Once connected the desk communicates using Bluetooth LE, using the GATT protocol. GATT is quite complex and I do not understand much of it but the useful bit is that the desk advertises some `characteristics` which are addresses that bytes can be written to and read from. There's various other things like `services` and `descriptors` but they were not relevant to getting this working. 4 | 5 | Python has several packages available for communicating over GATT so the only tricky bit is working out what each of the characteristics do and what data they want. It seems like in general they're expecting quite simple data to be exchanged. 6 | 7 | The desk is from Ikea but it is a rebranded Linak device, and Linak publish an app to control it. I was able to examine the app to find out missing information. This included mapping the characteristic UUIDs to functionality (the two important ones being the characteristic that accepts commands to control the desk, and the characteristic that broadcasts the current height of the desk), and also finding out the command codes and the format they needed to be in. 8 | 9 | For example to move the desk up you encode `71` into bytes as an unsigned little endian short and write that to the characteristic identified by the UUID `99fa0002-338a-1024-8a49-009c0215f78a`. The other command codes are similar short numbers. For some reason there is another characteristic (reference input) that accepts up/down/stop commands but it requires signed little endian shorts. I don't understand why it is like this. 10 | 11 | ## Behaviour 12 | 13 | Sending move commands to the desk seems to make the motors run for about one second in the desired direction. If another move command is sent within that second then the motion continues with no slowing or stopping. If no move command is recieved in that second then the motor slows down towards the end and then stops. If you send a move command late, then there will some stuttering as the desk may have already started to slow the motors. You can stop the motion part way through by sending a stop command though it sometimes does not respond immediately. As the desk moves it sends notifications of the current height to a characteristic. This can be monitored to work out when to stop moving, but it also seems to be a little bit slow and the final notified value is often not the same as the actual final value if a measuremment is made at rest. 14 | 15 | The height values the desk provides are in 10ths of a millimetre, and correspond to the height above the desks lowest setting i.e. if you lower the desk as far as it will go then the desk will report its height as being zero. The minimum raw height value is zero and the maximum height is 6500. This corresponds to a range of 620mm to 1270mm off the floor. 16 | 17 | The desk appears to be pretty good at not doing anything stupid if you send it stupid commands. It won't try to go below the minimum height or above the maximum height and it doesn't do much if you send lots of commands in quick succession. The usual hit detection works, and it will stop moving if it hits an object and will not respond to further commands until a stop command is sent. 18 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | rm -rf dist/* 2 | poetry build 3 | -------------------------------------------------------------------------------- /scripts/publish: -------------------------------------------------------------------------------- 1 | poetry publish --build 2 | --------------------------------------------------------------------------------