DroneTSP


NameDroneTSP JSON
Version 1.0.1 PyPI version JSON
download
home_pageNone
SummaryDrone TSP Gymnasium environment and wrappers
upload_time2025-09-09 14:54:28
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseThe MIT License Copyright (c) 2023 Farama Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords drone environment gymnasium reinforcement-learning tsp
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # DroneTSP

Môi trường Drone TSP cho Gymnasium mô phỏng drone giao hàng trong thành phố (TP.HCM), với các nút: kho (depot), khách hàng và trạm sạc. Toàn bộ đặc tả dưới đây được tổng hợp từ mã nguồn trong thư mục `gymnasium_env/envs`.

## Cài đặt

```bash
pip install -e .
```

Sau cài đặt, môi trường được đăng ký ở id: `gymnasium_env/DroneTsp-v1`.

## Sử dụng nhanh

```python
import gymnasium as gym

env = gym.make(
    id="gymnasium_env/DroneTsp-v1",
    render_mode="human",         # "human" hoặc "rgb_array"
    num_customer_nodes=5,
    num_charge_nodes=1,
    package_weight=40,            # sức chứa tối đa (kg)
    min_package_weight=1,
    max_package_weight=5,
    max_energy=-1.0,              # âm để bỏ giới hạn năng lượng
    max_charge_times=-1           # âm để bỏ giới hạn số lần sạc
)

obs, info = env.reset(options={"new_coordinates": True})
done = False
while not done:
    action = env.unwrapped._sample()  # Lấy ngẫu nhiên node khách hàng chưa đi
    obs, reward, terminated, truncated, info = env.step(action)
    done = terminated or truncated
```

## DroneTspEnv

- Mã nguồn: `gymnasium_env/envs/drone_tsp.py`
- Đăng ký: `gymnasium_env/DroneTsp-v1`

### Tham số khởi tạo

- `render_mode`: `None | "human" | "rgb_array"`
- `num_customer_nodes` (int): số khách hàng (mặc định 5)
- `num_charge_nodes` (int): số trạm sạc (mặc định 1)
- `package_weight` (float): sức chứa tối đa của drone (kg, mặc định 40)
- `min_package_weight` (float): khối lượng đơn tối thiểu (kg, mặc định 1)
- `max_package_weight` (float): khối lượng đơn tối đa (kg, mặc định 5)
- `max_energy` (float): ngưỡng năng lượng tiêu thụ; `-1` để bỏ giới hạn
- `max_charge_times` (int): số lần nạp năng lượng tối đa; âm để bỏ giới hạn

### Không gian quan sát (`observation_space`)

- `Dict` gồm:
  - `nodes`: `Box(shape=(N, 5), dtype=float32)` với `N = 1 + num_customer_nodes + num_charge_nodes`
    - Mỗi node được mã hóa: `[lon, lat, node_type, package_weight, visited_order]`
    - `node_type`: 0=depot, 1=customer, 2=charging_station
  - `total_distance`: tổng quãng đường đã đi (m)
  - `energy_consumption`: năng lượng tiêu thụ tích lũy hiện tại
  - `charge_count`: số lần sạc đã thực hiện

### Không gian hành động (`action_space`)

- `Discrete(N, start=0)` với cùng `N` như trên
- `0`: depot; `1..num_customer_nodes`: khách hàng; còn lại: trạm sạc

### Quy tắc bước (`step`)

- Trả về: `(observation, reward, terminated, truncated, info)`
- `reward`: là khoảng cách (m) của cạnh vừa di chuyển (theo `geodesic`) cho hành động hiện tại.
- Khi đi đến khách hàng: giảm `remain_packages_weight` theo `package_weight` của node đó; ghi `visited_order`.
- Khi đến trạm sạc: tăng `charge_count` và đặt lại `total_energy_consumption` về 0.
- Khi `action == 0` (đi về depot):
  - tăng `charge_count`, đặt lại `total_energy_consumption` về 0
  - nạp lại sức chứa: `remain_packages_weight = package_weight`
  - `truncated = True` (kết thúc sớm một vòng hành trình)
- Điều kiện dừng:
  - `terminated = True` nếu `action == 0` và tất cả các node không phải trạm sạc đã được ghé (`visited_order > 0`)
  - `truncated = True` nếu vượt `max_energy` hoặc vượt `max_charge_times` hoặc khi `action == 0`

### Reset

- `env.reset(seed=None, options=None)`:
  - `options["new_coordinates"] = True` (mặc định): tạo lại toàn bộ vị trí các node (TP.HCM trong khung [10.75–10.80] x [106.65–106.72])
  - Nếu `False`: giữ nguyên toạ độ cũ, đặt lại `visited_order` và trạng thái tích lũy

### Render

- `render_mode="human"`: sinh bản đồ HTML ở `render/index.html` bằng `folium`, hiển thị đường đi theo thứ tự `visited_order`
- `render_mode="rgb_array"`: trả về frame từ `_render_frame()` (không vẽ GUI ngoài)

### Trường thông tin (`info`)

- `drone_speed` (m/s), `customers` (danh sách node khách hàng),
  `distance_histories`, `energy_consumption_histories`, `charge_count`,
  `remain_packages_weight`, `max_energy`.

## Các mô-đun trong `envs/`

### `interfaces.py`

- `NODE_TYPES`: Enum các loại node: `depot=0`, `customer=1`, `charging_station=2`
- `Node`: dataclass gồm `lon, lat, node_type, package_weight, visited_order`

### `node_transformer.py`

- `NodeTransformer.encode(Node) -> np.ndarray[5]`: mã hoá Node thành mảng 5 phần tử
- `NodeTransformer.decode(arr) -> Node`: giải mã về Node
- `NodeTransformer.get_shape() -> int`: kích thước vector nút (=5)

### `utils.py`

- `generate_packages_weight(max_weight, total_packages)`:
  sinh danh sách khối lượng nguyên, tổng xấp xỉ `max_weight`
- `calc_energy_consumption(gij, distanceij, speedij=15)`:
  tính năng lượng tiêu thụ cho cạnh theo công thức trong bài báo; đầu ra làm tròn 2 chữ số
- `total_distance_of_a_random_route(nodes)`:
  tổng quãng đường qua danh sách node theo thứ tự
- `calc_distance(node_a, node_b)`:
  khoảng cách địa lý giữa hai điểm `[lon, lat]`

### `folium_exporter.py`

- `export_to_folium(nodes, path_indices, file_path="render/index.html")`:
  vẽ map, đánh dấu màu theo loại node và vẽ `Polyline` theo `path_indices`

## Ghi chú

- Id môi trường đúng là `gymnasium_env/DroneTsp-v1` (được đăng ký tại `gymnasium_env/__init__.py`).
- `reward` hiện là khoảng cách bước đi; nếu bạn muốn phần thưởng tập trung mục tiêu (ví dụ nhỏ hoá tổng quãng đường, năng lượng, số lần sạc), bạn có thể điều chỉnh trong `step()` để trả về giá trị phù hợp.

## Đóng góp

- Cài đặt `pre-commit` và chạy `pre-commit install`
- Mở PR với mô tả rõ thay đổi và cách kiểm thử

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "DroneTSP",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "drone, environment, gymnasium, reinforcement-learning, tsp",
    "author": null,
    "author_email": "Tr\u01b0\u01a1ng H\u1ea3i \u0110\u0103ng <haidanghth910@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/0a/90/0b855c9552311565f97418280e81976a5520d67cb513e9455c0014543e97/dronetsp-1.0.1.tar.gz",
    "platform": null,
    "description": "# DroneTSP\n\nM\u00f4i tr\u01b0\u1eddng Drone TSP cho Gymnasium m\u00f4 ph\u1ecfng drone giao h\u00e0ng trong th\u00e0nh ph\u1ed1 (TP.HCM), v\u1edbi c\u00e1c n\u00fat: kho (depot), kh\u00e1ch h\u00e0ng v\u00e0 tr\u1ea1m s\u1ea1c. To\u00e0n b\u1ed9 \u0111\u1eb7c t\u1ea3 d\u01b0\u1edbi \u0111\u00e2y \u0111\u01b0\u1ee3c t\u1ed5ng h\u1ee3p t\u1eeb m\u00e3 ngu\u1ed3n trong th\u01b0 m\u1ee5c `gymnasium_env/envs`.\n\n## C\u00e0i \u0111\u1eb7t\n\n```bash\npip install -e .\n```\n\nSau c\u00e0i \u0111\u1eb7t, m\u00f4i tr\u01b0\u1eddng \u0111\u01b0\u1ee3c \u0111\u0103ng k\u00fd \u1edf id: `gymnasium_env/DroneTsp-v1`.\n\n## S\u1eed d\u1ee5ng nhanh\n\n```python\nimport gymnasium as gym\n\nenv = gym.make(\n    id=\"gymnasium_env/DroneTsp-v1\",\n    render_mode=\"human\",         # \"human\" ho\u1eb7c \"rgb_array\"\n    num_customer_nodes=5,\n    num_charge_nodes=1,\n    package_weight=40,            # s\u1ee9c ch\u1ee9a t\u1ed1i \u0111a (kg)\n    min_package_weight=1,\n    max_package_weight=5,\n    max_energy=-1.0,              # \u00e2m \u0111\u1ec3 b\u1ecf gi\u1edbi h\u1ea1n n\u0103ng l\u01b0\u1ee3ng\n    max_charge_times=-1           # \u00e2m \u0111\u1ec3 b\u1ecf gi\u1edbi h\u1ea1n s\u1ed1 l\u1ea7n s\u1ea1c\n)\n\nobs, info = env.reset(options={\"new_coordinates\": True})\ndone = False\nwhile not done:\n    action = env.unwrapped._sample()  # L\u1ea5y ng\u1eabu nhi\u00ean node kh\u00e1ch h\u00e0ng ch\u01b0a \u0111i\n    obs, reward, terminated, truncated, info = env.step(action)\n    done = terminated or truncated\n```\n\n## DroneTspEnv\n\n- M\u00e3 ngu\u1ed3n: `gymnasium_env/envs/drone_tsp.py`\n- \u0110\u0103ng k\u00fd: `gymnasium_env/DroneTsp-v1`\n\n### Tham s\u1ed1 kh\u1edfi t\u1ea1o\n\n- `render_mode`: `None | \"human\" | \"rgb_array\"`\n- `num_customer_nodes` (int): s\u1ed1 kh\u00e1ch h\u00e0ng (m\u1eb7c \u0111\u1ecbnh 5)\n- `num_charge_nodes` (int): s\u1ed1 tr\u1ea1m s\u1ea1c (m\u1eb7c \u0111\u1ecbnh 1)\n- `package_weight` (float): s\u1ee9c ch\u1ee9a t\u1ed1i \u0111a c\u1ee7a drone (kg, m\u1eb7c \u0111\u1ecbnh 40)\n- `min_package_weight` (float): kh\u1ed1i l\u01b0\u1ee3ng \u0111\u01a1n t\u1ed1i thi\u1ec3u (kg, m\u1eb7c \u0111\u1ecbnh 1)\n- `max_package_weight` (float): kh\u1ed1i l\u01b0\u1ee3ng \u0111\u01a1n t\u1ed1i \u0111a (kg, m\u1eb7c \u0111\u1ecbnh 5)\n- `max_energy` (float): ng\u01b0\u1ee1ng n\u0103ng l\u01b0\u1ee3ng ti\u00eau th\u1ee5; `-1` \u0111\u1ec3 b\u1ecf gi\u1edbi h\u1ea1n\n- `max_charge_times` (int): s\u1ed1 l\u1ea7n n\u1ea1p n\u0103ng l\u01b0\u1ee3ng t\u1ed1i \u0111a; \u00e2m \u0111\u1ec3 b\u1ecf gi\u1edbi h\u1ea1n\n\n### Kh\u00f4ng gian quan s\u00e1t (`observation_space`)\n\n- `Dict` g\u1ed3m:\n  - `nodes`: `Box(shape=(N, 5), dtype=float32)` v\u1edbi `N = 1 + num_customer_nodes + num_charge_nodes`\n    - M\u1ed7i node \u0111\u01b0\u1ee3c m\u00e3 h\u00f3a: `[lon, lat, node_type, package_weight, visited_order]`\n    - `node_type`: 0=depot, 1=customer, 2=charging_station\n  - `total_distance`: t\u1ed5ng qu\u00e3ng \u0111\u01b0\u1eddng \u0111\u00e3 \u0111i (m)\n  - `energy_consumption`: n\u0103ng l\u01b0\u1ee3ng ti\u00eau th\u1ee5 t\u00edch l\u0169y hi\u1ec7n t\u1ea1i\n  - `charge_count`: s\u1ed1 l\u1ea7n s\u1ea1c \u0111\u00e3 th\u1ef1c hi\u1ec7n\n\n### Kh\u00f4ng gian h\u00e0nh \u0111\u1ed9ng (`action_space`)\n\n- `Discrete(N, start=0)` v\u1edbi c\u00f9ng `N` nh\u01b0 tr\u00ean\n- `0`: depot; `1..num_customer_nodes`: kh\u00e1ch h\u00e0ng; c\u00f2n l\u1ea1i: tr\u1ea1m s\u1ea1c\n\n### Quy t\u1eafc b\u01b0\u1edbc (`step`)\n\n- Tr\u1ea3 v\u1ec1: `(observation, reward, terminated, truncated, info)`\n- `reward`: l\u00e0 kho\u1ea3ng c\u00e1ch (m) c\u1ee7a c\u1ea1nh v\u1eeba di chuy\u1ec3n (theo `geodesic`) cho h\u00e0nh \u0111\u1ed9ng hi\u1ec7n t\u1ea1i.\n- Khi \u0111i \u0111\u1ebfn kh\u00e1ch h\u00e0ng: gi\u1ea3m `remain_packages_weight` theo `package_weight` c\u1ee7a node \u0111\u00f3; ghi `visited_order`.\n- Khi \u0111\u1ebfn tr\u1ea1m s\u1ea1c: t\u0103ng `charge_count` v\u00e0 \u0111\u1eb7t l\u1ea1i `total_energy_consumption` v\u1ec1 0.\n- Khi `action == 0` (\u0111i v\u1ec1 depot):\n  - t\u0103ng `charge_count`, \u0111\u1eb7t l\u1ea1i `total_energy_consumption` v\u1ec1 0\n  - n\u1ea1p l\u1ea1i s\u1ee9c ch\u1ee9a: `remain_packages_weight = package_weight`\n  - `truncated = True` (k\u1ebft th\u00fac s\u1edbm m\u1ed9t v\u00f2ng h\u00e0nh tr\u00ecnh)\n- \u0110i\u1ec1u ki\u1ec7n d\u1eebng:\n  - `terminated = True` n\u1ebfu `action == 0` v\u00e0 t\u1ea5t c\u1ea3 c\u00e1c node kh\u00f4ng ph\u1ea3i tr\u1ea1m s\u1ea1c \u0111\u00e3 \u0111\u01b0\u1ee3c gh\u00e9 (`visited_order > 0`)\n  - `truncated = True` n\u1ebfu v\u01b0\u1ee3t `max_energy` ho\u1eb7c v\u01b0\u1ee3t `max_charge_times` ho\u1eb7c khi `action == 0`\n\n### Reset\n\n- `env.reset(seed=None, options=None)`:\n  - `options[\"new_coordinates\"] = True` (m\u1eb7c \u0111\u1ecbnh): t\u1ea1o l\u1ea1i to\u00e0n b\u1ed9 v\u1ecb tr\u00ed c\u00e1c node (TP.HCM trong khung [10.75\u201310.80] x [106.65\u2013106.72])\n  - N\u1ebfu `False`: gi\u1eef nguy\u00ean to\u1ea1 \u0111\u1ed9 c\u0169, \u0111\u1eb7t l\u1ea1i `visited_order` v\u00e0 tr\u1ea1ng th\u00e1i t\u00edch l\u0169y\n\n### Render\n\n- `render_mode=\"human\"`: sinh b\u1ea3n \u0111\u1ed3 HTML \u1edf `render/index.html` b\u1eb1ng `folium`, hi\u1ec3n th\u1ecb \u0111\u01b0\u1eddng \u0111i theo th\u1ee9 t\u1ef1 `visited_order`\n- `render_mode=\"rgb_array\"`: tr\u1ea3 v\u1ec1 frame t\u1eeb `_render_frame()` (kh\u00f4ng v\u1ebd GUI ngo\u00e0i)\n\n### Tr\u01b0\u1eddng th\u00f4ng tin (`info`)\n\n- `drone_speed` (m/s), `customers` (danh s\u00e1ch node kh\u00e1ch h\u00e0ng),\n  `distance_histories`, `energy_consumption_histories`, `charge_count`,\n  `remain_packages_weight`, `max_energy`.\n\n## C\u00e1c m\u00f4-\u0111un trong `envs/`\n\n### `interfaces.py`\n\n- `NODE_TYPES`: Enum c\u00e1c lo\u1ea1i node: `depot=0`, `customer=1`, `charging_station=2`\n- `Node`: dataclass g\u1ed3m `lon, lat, node_type, package_weight, visited_order`\n\n### `node_transformer.py`\n\n- `NodeTransformer.encode(Node) -> np.ndarray[5]`: m\u00e3 ho\u00e1 Node th\u00e0nh m\u1ea3ng 5 ph\u1ea7n t\u1eed\n- `NodeTransformer.decode(arr) -> Node`: gi\u1ea3i m\u00e3 v\u1ec1 Node\n- `NodeTransformer.get_shape() -> int`: k\u00edch th\u01b0\u1edbc vector n\u00fat (=5)\n\n### `utils.py`\n\n- `generate_packages_weight(max_weight, total_packages)`:\n  sinh danh s\u00e1ch kh\u1ed1i l\u01b0\u1ee3ng nguy\u00ean, t\u1ed5ng x\u1ea5p x\u1ec9 `max_weight`\n- `calc_energy_consumption(gij, distanceij, speedij=15)`:\n  t\u00ednh n\u0103ng l\u01b0\u1ee3ng ti\u00eau th\u1ee5 cho c\u1ea1nh theo c\u00f4ng th\u1ee9c trong b\u00e0i b\u00e1o; \u0111\u1ea7u ra l\u00e0m tr\u00f2n 2 ch\u1eef s\u1ed1\n- `total_distance_of_a_random_route(nodes)`:\n  t\u1ed5ng qu\u00e3ng \u0111\u01b0\u1eddng qua danh s\u00e1ch node theo th\u1ee9 t\u1ef1\n- `calc_distance(node_a, node_b)`:\n  kho\u1ea3ng c\u00e1ch \u0111\u1ecba l\u00fd gi\u1eefa hai \u0111i\u1ec3m `[lon, lat]`\n\n### `folium_exporter.py`\n\n- `export_to_folium(nodes, path_indices, file_path=\"render/index.html\")`:\n  v\u1ebd map, \u0111\u00e1nh d\u1ea5u m\u00e0u theo lo\u1ea1i node v\u00e0 v\u1ebd `Polyline` theo `path_indices`\n\n## Ghi ch\u00fa\n\n- Id m\u00f4i tr\u01b0\u1eddng \u0111\u00fang l\u00e0 `gymnasium_env/DroneTsp-v1` (\u0111\u01b0\u1ee3c \u0111\u0103ng k\u00fd t\u1ea1i `gymnasium_env/__init__.py`).\n- `reward` hi\u1ec7n l\u00e0 kho\u1ea3ng c\u00e1ch b\u01b0\u1edbc \u0111i; n\u1ebfu b\u1ea1n mu\u1ed1n ph\u1ea7n th\u01b0\u1edfng t\u1eadp trung m\u1ee5c ti\u00eau (v\u00ed d\u1ee5 nh\u1ecf ho\u00e1 t\u1ed5ng qu\u00e3ng \u0111\u01b0\u1eddng, n\u0103ng l\u01b0\u1ee3ng, s\u1ed1 l\u1ea7n s\u1ea1c), b\u1ea1n c\u00f3 th\u1ec3 \u0111i\u1ec1u ch\u1ec9nh trong `step()` \u0111\u1ec3 tr\u1ea3 v\u1ec1 gi\u00e1 tr\u1ecb ph\u00f9 h\u1ee3p.\n\n## \u0110\u00f3ng g\u00f3p\n\n- C\u00e0i \u0111\u1eb7t `pre-commit` v\u00e0 ch\u1ea1y `pre-commit install`\n- M\u1edf PR v\u1edbi m\u00f4 t\u1ea3 r\u00f5 thay \u0111\u1ed5i v\u00e0 c\u00e1ch ki\u1ec3m th\u1eed\n",
    "bugtrack_url": null,
    "license": "The MIT License\n        \n        Copyright (c) 2023 Farama Foundation\n        \n        Permission is hereby granted, free of charge, to any person obtaining a copy\n        of this software and associated documentation files (the \"Software\"), to deal\n        in the Software without restriction, including without limitation the rights\n        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n        copies of the Software, and to permit persons to whom the Software is\n        furnished to do so, subject to the following conditions:\n        \n        The above copyright notice and this permission notice shall be included in\n        all copies or substantial portions of the Software.\n        \n        THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n        THE SOFTWARE.",
    "summary": "Drone TSP Gymnasium environment and wrappers",
    "version": "1.0.1",
    "project_urls": null,
    "split_keywords": [
        "drone",
        " environment",
        " gymnasium",
        " reinforcement-learning",
        " tsp"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "7b29e10ea2d4f973181db9b9fdc95c9afd79fd8a04f2545e46a6131f5d737e0b",
                "md5": "f3f58c896ca0a890daf28dc113300eaa",
                "sha256": "6765519594ac3fbe7e307f7dccba6679fdc6fee4cffaf780eb2da8b794a51f32"
            },
            "downloads": -1,
            "filename": "dronetsp-1.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f3f58c896ca0a890daf28dc113300eaa",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 16713,
            "upload_time": "2025-09-09T14:54:27",
            "upload_time_iso_8601": "2025-09-09T14:54:27.544237Z",
            "url": "https://files.pythonhosted.org/packages/7b/29/e10ea2d4f973181db9b9fdc95c9afd79fd8a04f2545e46a6131f5d737e0b/dronetsp-1.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "0a900b855c9552311565f97418280e81976a5520d67cb513e9455c0014543e97",
                "md5": "84e59d6353606a943dbed66f35707409",
                "sha256": "0eca20deb1552135299fe7cd289ac92ebce7881dc6e7a6e329cc3c9fe539d04f"
            },
            "downloads": -1,
            "filename": "dronetsp-1.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "84e59d6353606a943dbed66f35707409",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 13089,
            "upload_time": "2025-09-09T14:54:28",
            "upload_time_iso_8601": "2025-09-09T14:54:28.744304Z",
            "url": "https://files.pythonhosted.org/packages/0a/90/0b855c9552311565f97418280e81976a5520d67cb513e9455c0014543e97/dronetsp-1.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-09 14:54:28",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "dronetsp"
}
        
Elapsed time: 1.12004s