Hand Control Module (总览)
一个统一的灵巧手控制模块,支持多种灵巧手类型的控制接口,包括PantheonHand灵巧手和GaiaHand灵巧手。提供高级抽象接口和底层控制功能,适用于机器人研究、教育和工业应用。
详细文档:各手型详细说明请参阅
docs/目录:
- Hand Control Module (PantheonHand) - PantheonHand 详细文档
- Hand Control Module (GaiaHand) - GaiaHand 详细文档
📋 目录
✨ 功能特性
🔧 核心功能
- 统一接口: 提供标准化的手部控制接口,支持多种手部类型
- 多手部支持: 支持单手、双手和混合模式
- 实时控制: 支持实时关节角度控制和位置控制
- 手势执行: 内置多种预设手势,支持自定义手势
- 运动学计算: 提供正逆运动学计算功能
- 状态监控: 实时监控电机状态和手部状态
🚀 高级功能
- 异步通信: 支持异步电机状态查询,提高响应速度
- 广播控制: 支持单点控制和广播控制模式
- 平滑运动: 支持关节平滑过渡和轨迹规划
- 紧急停止: 提供紧急停止功能,确保安全
- 自动检测: 自动检测串口设备和CAN设备
- 错误处理: 完善的错误处理和恢复机制
🛠️ 开发友好
- 类型提示: 完整的Python类型提示支持
- 详细文档: 提供详细的API文档和使用示例
- 日志系统: 完善的日志记录和调试功能
- 单元测试: 包含完整的单元测试套件
- 跨平台: 支持Windows、Linux和macOS
🤖 支持的灵巧手
PantheonHand
- 通信方式: CAN总线通信
- 控制精度: 高精度位置和力矩控制
- 自由度: 可配置自由度
- 应用场景: 工业应用、协作机器人、研究平台
GaiaHand
-
通信方式: 串口通信 (RS232/RS485)
-
控制精度: 高精度位置控制
-
自由度:
- GaiaHand: 15个自由度 (5个手指 × 3个关节)
- Gaia16Hand: 16个自由度 (拇指4关节 + 其他手指各3关节)
-
应用场景: 精细操作、抓取任务、手势展示,触觉导纳
🚀 快速开始
环境要求
- Python 3.7+ (建议Python3.12)
- CAN驱动 (PantheonHand)
- 串口驱动 (GaiaHand)
- conda 或 pip (推荐使用 conda 进行环境管理)
- Git (用于克隆仓库)
克隆仓库
# 克隆仓库
git clone https://gitee.com/stellarrobot/handsdk.git
环境与依赖
# 创建新的 conda 环境(Python 3.12)
conda create -n handsdk python=3.12
# 激活环境
conda activate handsdk
# 安装基础依赖
pip install pyserial==3.5 numpy==2.4.2 typing-extensions==4.15.0 scipy==1.17.1
# 或者使用requirements.txt
cd handsdk # 确保在handsdk文件夹下
pip install -r requirements.txt
安装模块
# 确保在handsdk文件夹下,上一步如果已经进入,此步可以忽略
cd handsdk
# 开发模式安装
pip install -e .
# 或者直接安装
pip install .
运行示例
PantheonHand示例
# Windows
python example\pantheonhand_example.py
# Linux / macOS
python example/pantheonhand_example.py
GaiaHand示例
# Windows
python example\gaiahand_example.py
# Linux / macOS
python example/gaiahand_example.py
Linux系统环境配置
USB_CANFD权限设置
因Linux系统下将涉及到usb底层驱动的调用,运行时,一定要加sudo获取权限运行,否则USB设备没有权限操作。 现提供一种USB权限设置,配置后,可以不加权限运行。下面创建一个新的 udev 规则。名称取为:99-myusb.rules
# 新建 udev 规则
sudo nano /etc/udev/rules.d/99-myusb.rules
注意:
1、 数字 99 最好不要改动,否则可能设置失败
2、 要加 sudo
在99-myusb.rules中,添加下面的内容
##
ACTION=="add",SUBSYSTEMS=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="0053",
GROUP="users", MODE="0777"
USB_CAN权限设置
增加文件:/etc/udev/rules.d/50-usbcan.rules,
sudo nano /etc/udev/rules.d/50-usbcan.rules
50-usbcan.rules内容如下:
SUBSYSTEMS=="usb",ATTRS{idVendor}=="0471",ATTRS{idProduct}=="1200", GROUP="users", MODE="0666"
重新加载udev规则后插拔设备即可应用新权限:
sudo udevadm control --reload
📖 基本使用
1. 创建手部实例
from hand import create_hand, HandType, HandSide
# 创建Pantheon手部实例
pantheon_hand = create_hand(
hand_type=HandType.PANTHEON,
hand_side=HandSide.RIGHT,
can_config={
'dev_type': 41, # USB-CAN
'channel': 0,
'arbitration_bitrate': 500000
}
)
# 创建Gaia灵巧手实例
gaia_hand = create_hand(
hand_type=HandType.GAIA,
hand_side=HandSide.RIGHT,
port="COM3",
baudrate=230400
)
2. 连接和基本控制
# 连接手部
if gaia_hand.connect():
print("手部连接成功")
# 使能所有电机
gaia_hand.enable_all_motors(True)
# 设置关节角度
from hand import FingerType, JointType
gaia_hand.set_joint_angle(FingerType.INDEX, JointType.JOINT_1, 45.0)
# 执行手势
from hand import GestureType
gaia_hand.perform_gesture(GestureType.FIST)
# 断开连接
gaia_hand.disconnect()
3. 高级控制功能
# 使用move_joints_pos进行批量控制
positions = {
6: [1, 1, 1, 0.5] # 手部1,食指关节1,角度0.5弧度
}
gaia_hand.move_joints_pos(positions, speed=0.8)
# 运动学计算
x, y = gaia_hand.forward_kinematics(FingerType.INDEX)
print(f"食指末端位置: ({x:.3f}, {y:.3f})")
# 逆运动学
target_x, target_y = 0.1, 0.05 # 目标位置(米)
joint_angles = gaia_hand.inverse_kinematics(FingerType.INDEX, target_x, target_y)
📚 API文档
MkDocs 依赖安装、构建与本地预览等操作说明见 tools/README.md。
核心类
Hand (抽象基类)
手部控制的抽象基类,定义了标准接口。
主要方法:
connect(): 连接手部硬件disconnect(): 断开连接is_connected(): 检查连接状态set_joint_angle(): 设置关节角度get_joint_angle(): 获取关节角度perform_gesture(): 执行手势emergency_stop(): 紧急停止
GaiaHandAdapter
Gaia灵巧手的适配器类。
特殊功能:
- 串口通信管理
- 15自由度控制
- 高精度位置控制
- 实时状态监控
PantheonHandAdapter
Pantheon手部的适配器类。
特殊功能:
- CAN总线通信
- 可配置自由度
- 力矩控制
- 工业级稳定性
枚举类型
HandType
class HandType(Enum):
GAIA = "gaia" # Gaia灵巧手
PANTHEON = "pantheon" # Pantheon手部
HandSide
class HandSide(Enum):
RIGHT = "right" # 右手
LEFT = "left" # 左手
DOUBLE = "double" # 双手
FingerType
class FingerType(Enum):
THUMB = "拇指" # 拇指
INDEX = "食指" # 食指
MIDDLE = "中指" # 中指
RING = "无名指" # 无名指
LITTLE = "小指" # 小指
JointType
class JointType(Enum):
JOINT_1 = "关节1" # 第一关节
JOINT_2 = "关节2" # 第二关节
JOINT_3 = "关节3" # 第三关节
GestureType
class GestureType(Enum):
FIST = "握拳" # 握拳
OPEN = "张开" # 张开
POINT = "指向" # 指向
VICTORY = "胜利" # 胜利手势
OK = "OK" # OK手势
THUMB_UP = "点赞" # 点赞
工具函数
串口工具
from hand import get_available_ports, auto_detect_gaia_ports
# 获取可用串口
ports = get_available_ports()
# 自动检测Gaia手部串口
gaia_ports = auto_detect_gaia_ports()
💡 示例代码
基础示例
查看 example/ 目录中的完整示例:
gaia_hand_usage_example.py: Gaia手部使用示例motor_usage_example.py: 底层电机控制示例move_joints_pos.py: 关节位置控制示例
快速示例
# 简单的手部控制示例
from hand import create_hand, HandType, HandSide, FingerType, JointType, GestureType
def simple_hand_control():
# 创建手部实例
hand = create_hand(HandType.GAIA, HandSide.RIGHT, port="COM3")
try:
# 连接手部
if hand.connect():
print("手部连接成功")
# 使能电机
hand.enable_all_motors(True)
# 执行一系列动作
hand.perform_gesture(GestureType.OPEN)
time.sleep(1)
hand.perform_gesture(GestureType.FIST)
time.sleep(1)
hand.perform_gesture(GestureType.POINT)
time.sleep(1)
print("动作执行完成")
else:
print("手部连接失败")
except Exception as e:
print(f"发生错误: {e}")
finally:
hand.disconnect()
if __name__ == "__main__":
simple_hand_control()
🏗️ 架构设计
模块结构
hand/
├── hand/
│ ├── __init__.py # 模块入口
│ ├── core.py # 核心抽象类和适配器
│ ├── gaiahand/ # Gaia手部实现
│ │ ├── gaia_hand.py # Gaia手部适配器
│ │ ├── motor.py # 电机控制
│ │ └── hand_mappings.py # 映射关系
│ ├── pantheonhand/ # Pantheon手部实现
│ │ ├── pantheon_hand.py # Pantheon手部适配器
│ │ └── can_utils/ # CAN通信工具
│ └── utils/ # 工具函数
│ └── serial_utils.py # 串口工具
├── example/ # 示例代码
├── docs/ # 文档
├── tests/ # 测试代码
└── setup.py # 安装配置
设计模式
- 工厂模式: 使用
create_hand()工厂函数创建手部实例 - 适配器模式: 通过适配器类统一不同手部类型的接口
- 策略模式: 支持不同的通信策略(串口/CAN)
- 观察者模式: 状态变化通知机制
通信协议
串口协议 (GaiaHand)
- 波特率: 230400
- 数据位: 8
- 停止位: 1
- 校验位: 无
CAN协议 (PantheonHand)
- 波特率: 500000
- 设备类型: USB-CAN/PCI-CAN
- 通道数: 可配置
🔧 故障排除
常见问题
1. 连接失败
症状: 无法连接到手部硬件 解决方案:
- 检查串口号是否正确
- 确认硬件连接正常
- 检查驱动是否安装
- 验证波特率设置
2. 通信超时
症状: 命令执行超时 解决方案:
- 检查通信线路质量
- 降低波特率
- 增加超时时间
- 检查硬件状态
3. 电机不响应
症状: 电机不执行命令 解决方案:
- 检查电机使能状态
- 验证关节角度范围
- 检查电源供应
- 查看错误日志
4. 位置控制异常
症状: 位置控制不准确 解决方案:
- 执行手部回零操作
- 检查编码器状态
- 验证运动学参数
- 校准关节角度
调试方法
- 启用详细日志:
import logging
logging.basicConfig(level=logging.DEBUG)
- 使用串口调试工具:
from hand import get_available_ports
ports = get_available_ports()
print(f"可用串口: {ports}")
- 检查硬件状态:
status = hand.get_motor_status()
print(f"电机状态: {status}")
🤝 贡献指南
我们欢迎社区贡献!请遵循以下步骤:
开发环境设置
# 克隆仓库
git clone https://gitee.com/stellarrobot/handsdk.git
cd handsdk
# 安装开发依赖
pip install -e .[dev]
# 运行测试
pytest tests/
代码规范
- 遵循PEP 8代码风格
- 添加类型提示
- 编写单元测试
- 更新文档
提交规范
- 使用清晰的提交信息
- 包含测试用例
- 更新相关文档
📄 许可证
本项目采用 MIT 许可证 - 查看 LICENSE 文件了解详情。
📞 联系我们
- 项目主页: https://gitee.com/stellarrobot/handsdk
- 问题反馈: https://gitee.com/stellarrobot/handsdk/issues
- 邮箱: zhiqiangtan89@gmail.com
- 文档: https://hand-control.readthedocs.io/
🙏 致谢
感谢所有为这个项目做出贡献的开发者和用户!
注意: 使用本模块时请确保在安全环境下进行测试,并遵循相关的安全操作规程。
Release files for handsdk 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
Total release size: 59.1 MB
Release files / handsdk-1.0.0-cp314-cp314-win_amd64.whl
| Download URL | handsdk-1.0.0-cp314-cp314-win_amd64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.14 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
23120e84bed756a5107f0e2ab5719c7b764d3b477a3acb2944900015144823e2
|
|
BLAKE2b-256 checksum How to use checksums |
f608865497a321aeca56bb36f1b864d963d2aa25d5333de87a2e6477337a39c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / handsdk-1.0.0-cp314-cp314-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp314-cp314-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 4.1 MB |
| Tags | CPython 3.14 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
d7e3a5c777e64d5b71bddd823fa23b7e8411eb9ded94c4aa3b3cb8d6c861aa47
|
|
BLAKE2b-256 checksum How to use checksums |
46487272e36ac2eb8dcd477383f12b6233081fb881439200d6aede6667831f48
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / handsdk-1.0.0-cp313-cp313-win_amd64.whl
| Download URL | handsdk-1.0.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
1b479ffed8205e60292fd2b86d8e1be9fb935590827fcb62dccfaa6c4bfa82ef
|
|
BLAKE2b-256 checksum How to use checksums |
6846cefb8efbc114529b8e99ce1075061c7600808c0be2cb5231d10f7ae309b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / handsdk-1.0.0-cp313-cp313-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp313-cp313-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 4.1 MB |
| Tags | CPython 3.13 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
41013c2ff577ae571caeb686831994dd12f75086ce97d496a94d5bc635ebf577
|
|
BLAKE2b-256 checksum How to use checksums |
00161a7d586f8a04d61bd6ce87f8ce7d5c1fede6a90b69d2a908921f84069444
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / handsdk-1.0.0-cp312-cp312-win_amd64.whl
| Download URL | handsdk-1.0.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
9474bc2c040a6c0d0b77a38a752a925d6aab70269283478e117546056d66d324
|
|
BLAKE2b-256 checksum How to use checksums |
5f6b8c48b932a21849d4a04882f3081c09cb40affdb5cd315989b922ca9a2d65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / handsdk-1.0.0-cp312-cp312-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp312-cp312-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 4.1 MB |
| Tags | CPython 3.12 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
b2f972383a73968b6ce8307246e2f1188b75b88395639c1fc3db892029cfbac7
|
|
BLAKE2b-256 checksum How to use checksums |
962932f6e73c534d07c24da41f78e45b069588831fc5d08540c4617098775f89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / handsdk-1.0.0-cp311-cp311-win_amd64.whl
| Download URL | handsdk-1.0.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 3.3 MB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
9dec086d64e769e34c13d61405284ebcfa7cef0190a348dc4c58a2446509a3e5
|
|
BLAKE2b-256 checksum How to use checksums |
e1ecbec69a1af53ffa9cb965e225a099128c38180e049c5b3ebeca0fab684a4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / handsdk-1.0.0-cp311-cp311-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp311-cp311-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 4.2 MB |
| Tags | CPython 3.11 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
a2c944096568b57b1c53497f3d45ddbfaf9bd2b6d93f4351ea61cbdc38000d0f
|
|
BLAKE2b-256 checksum How to use checksums |
4cd4468b1b476333fa6dd1b2e8b9fa52a458137372cf72679f0556d42e5c0f8f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / handsdk-1.0.0-cp310-cp310-win_amd64.whl
| Download URL | handsdk-1.0.0-cp310-cp310-win_amd64.whl |
|---|---|
| Size | 3.3 MB |
| Tags | CPython 3.10 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
f3d1905fb72f7bc3a2cbe4491cb65946703f4de9a68799028a5bfa25e428fabe
|
|
BLAKE2b-256 checksum How to use checksums |
8237a420ccd9b64f1da266896308e8d67ac1dca0329374a8f51d970c5b1189f9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / handsdk-1.0.0-cp310-cp310-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp310-cp310-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 4.2 MB |
| Tags | CPython 3.10 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
65e12574fb0037f5ddc4b099a6cc1adda42d86c109e44be55a23872b9e3f7d6c
|
|
BLAKE2b-256 checksum How to use checksums |
730494dc56d3ae1a4ad0a1bcd202b282b50ffe52085de9bce00100883a0667bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / handsdk-1.0.0-cp39-cp39-win_amd64.whl
| Download URL | handsdk-1.0.0-cp39-cp39-win_amd64.whl |
|---|---|
| Size | 3.3 MB |
| Tags | CPython 3.9 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
9462d50b62a01af7a0d52e2cbd8effe99b927782d1353ded5823f9df9e18cb29
|
|
BLAKE2b-256 checksum How to use checksums |
caf845b8287427fc8df97dfe28417b4b1731d54814316849be2fc05d11c54dcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / handsdk-1.0.0-cp39-cp39-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp39-cp39-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 4.2 MB |
| Tags | CPython 3.9 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
66ab6960a44a43bce3e4bdaf34ea14d40d2c1f463cd361ff1b8e4fd4994809f9
|
|
BLAKE2b-256 checksum How to use checksums |
dc2c60f63b73571568ac9607a3664b655dae4b14aabf64c55160f8e2a6f29885
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / handsdk-1.0.0-cp38-cp38-win_amd64.whl
| Download URL | handsdk-1.0.0-cp38-cp38-win_amd64.whl |
|---|---|
| Size | 3.4 MB |
| Tags | CPython 3.8 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
14cf81345679d5c71ea28a41b6bc13df5dba54d9435a795b9d2af156b8866d36
|
|
BLAKE2b-256 checksum How to use checksums |
d0d5696a7e0f0e963ba4ae637e1b006d3db575118e5e49cb8b526233a1ed7fb5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / handsdk-1.0.0-cp38-cp38-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp38-cp38-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 4.3 MB |
| Tags | CPython 3.8 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
bbe781f585b10469558116754ce82adbf154c3016750e5a3ddf1dac5bc655812
|
|
BLAKE2b-256 checksum How to use checksums |
66d3b3210f8ed5f22294768ca24c6e20c1594e10b6119066d6fce4c3ba137fd0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / handsdk-1.0.0-cp38-abi3-manylinux_2_38_aarch64.whl
| Download URL | handsdk-1.0.0-cp38-abi3-manylinux_2_38_aarch64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | CPython 3.8 Linux glibc 2.38+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
ecae2cd6edfd1446a4436f64c44a8aa3ecad7c67db524aefb1e8bbfd2d28ddc8
|
|
BLAKE2b-256 checksum How to use checksums |
f6b7768baf4757be677e70b3732cb75c322d42f3934b5ea87684a1cfa50d612d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / handsdk-1.0.0-cp38-abi3-manylinux_2_35_x86_64.whl
| Download URL | handsdk-1.0.0-cp38-abi3-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 3.7 MB |
| Tags | CPython 3.8 Linux glibc 2.35+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
920983eed7b5832f1247cee943d9e3c308a0af5808c282e51546b27e887d4b92
|
|
BLAKE2b-256 checksum How to use checksums |
0a97f7bfa43e82a81f19f9d9e67bdf2b942be3c14b8c38432c9d015700474563
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|