commit 515eb3f09cddd25a0b6f2ba0cb6a815426596b31 Author: xtkuang <87661715@qq.com> Date: Mon Jul 20 16:59:37 2026 +0800 project init diff --git a/README.md b/README.md new file mode 100644 index 0000000..43d148d --- /dev/null +++ b/README.md @@ -0,0 +1,421 @@ +# cmvr-edge-ai + +`cmvr-edge-ai` 是部署在机器人边缘端的、配置驱动的 AI 流水线运行时。它把上游传感器、AI 算法和下游机器人/平台接口拆成可组合节点,通过 YAML 把节点连接成有向无环图(DAG)。协议只出现在边界连接器中,算法节点使用统一的内部消息,因此以后增加 HTTP、gRPC、UDP 或 QUIC 时不需要改动算法本身。 + +当前版本已经包含可运行的检测链路和框架基础设施: + +- 严格 YAML 配置校验、环境变量展开和 DAG 编译检查; +- `Source -> Operator -> Sink` 异步运行时; +- 每条边独立的有界队列和明确的溢出策略; +- 版本化插件注册表、检测模型注册表和 Python entry point 扩展机制; +- cmvr-es RGB 相机 gRPC Source(带退避上限的指数重连)、AGV gRPC Sink; +- PyAV H264/H265 有状态解码、通用检测模型节点和重复命中规则节点; +- Construction PPE YOLOv8 与六类 PPE YOLOv8n 模型注册,以及按标签、置信度和最大 FPS 的部署配置; +- 平台 HTTP JSON Sink,支持有限重试、`raise/log_and_drop` 失败策略,并使用告警 + `event_id` 作为幂等键; +- `RobotCommand -> ApprovedRobotCommand` 安全门和无重试的类型化 AGV 命令映射; +- 文本/JSON 日志、共享 gRPC Channel 与 HTTP Client; +- 可直接执行的 smoke、PPE 检测和对话占位配置。 + +`detect_server/pipeline.yaml` 当前只启用 Construction PPE 模型,并经过时间窗口规则向 +8081 上报告警。六类 PPE 模型及第二 HTTP 平台的实现仍保留在注册表和连接器中,但不在 +当前 Pipeline 图中实例化,因此不会加载第二份权重、执行第二次推理或访问 8082。 +VAD/ASR/LLM/TTS 尚未内置;`talk_server/pipeline.yaml` 仍使用模拟音频数据,等待 +cmvr-es 音频双向流 proto 落地。 + +## 架构概览 + +```mermaid +flowchart LR + A["cmvr-es / 平台
gRPC、HTTP、未来 UDP/QUIC"] --> B["Source 连接器"] + B --> C["每条边独立的有界队列"] + C --> D["Operator DAG
解码、检测、VAD、ASR、LLM、TTS、策略"] + D --> E["Safety Gate"] + D --> F["平台 Sink"] + E --> G["机器人执行器 Sink"] +``` + +内部节点传递 `Envelope[T]`,其中包含载荷、`schema/version`、采集时间、序号、截止时间、`trace_id` 和 `session_id`。连接器负责 protobuf/HTTP JSON 与内部契约之间的转换,AI 插件不应直接依赖 cmvr-es protobuf。 + +完整设计和配置字段见 [docs/architecture.md](docs/architecture.md)。 + +## 目录 + +```text +cmvr_edge_ai/ +├── .python-version # uv 默认 Python 3.10 +├── uv.lock # 所有 profile 的可复现依赖锁 +├── configs/ +│ └── smoke.yaml # 不依赖外部服务的最小运行验证 +├── detect_server/ +│ └── pipeline.yaml # cmvr-es 相机 -> PPE 告警 -> HTTP 平台 +├── talk_server/ +│ ├── nodes/ # 对话插件预留目录 +│ └── pipeline.yaml # 模拟音频 -> 对话占位 -> 日志 +├── scripts/ +│ ├── bootstrap.sh # 一键创建 uv 环境、生成 bindings 并自检 +│ └── generate_cmvr_stubs.py # 从 cmvr-es proto 生成 Python bindings +├── src/cmvr_edge_ai/ +│ ├── config/ # 配置模型、加载与环境变量展开 +│ ├── core/ # Envelope、组件接口、队列和 DAG 运行时 +│ ├── contracts/ # 协议无关的图像、音频、AI 和控制契约 +│ ├── detection/ # 模型注册、视频解码、推理与时间窗口规则 +│ ├── plugins/ # 插件注册、发现和内置基础插件 +│ ├── connectors/ # cmvr-es 与平台边界连接器 +│ ├── transports/ # gRPC/HTTP 连接池;UDP/QUIC 扩展位置 +│ ├── workers/ # 显式线程 offload 与常驻进程 Worker 工具 +│ ├── observability/ # 低开销文本/JSON 日志 +│ ├── application.py # 多 Pipeline 与共享网络客户端的所有者 +│ ├── compiler.py # 配置到可执行 DAG 的编译器 +│ └── cli.py # validate/run/plugins/models +└── tests/ +``` + +## 快速开始 + +项目使用 `uv` 管理 Python、`.venv` 和锁定依赖。`.python-version` 默认选择 +Python 3.10,支持范围是 3.10~3.12;首次执行时,uv 会在本机没有合适解释器时 +自动安装。先确认已经安装 uv: + +```bash +uv --version +``` + +未安装时可使用 uv 官方安装器: + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +只验证框架和模拟对话链路时,一条命令创建最小环境并自检: + +```bash +cd /home/xtkuang/Projects/cmvr/cmvr_edge_ai +bash scripts/bootstrap.sh --profile core +``` + +`uv.lock` 是安装的唯一版本来源;bootstrap 使用 `uv sync --locked`,不会在用户 +机器上重新选择依赖版本。无需 `source .venv/bin/activate`,统一通过 +`uv run --no-sync` 使用已经安装好的环境: + +```bash +uv run --no-sync cmvr-edge-ai validate --config configs/smoke.yaml +uv run --no-sync cmvr-edge-ai plugins +uv run --no-sync cmvr-edge-ai run \ + --config configs/smoke.yaml \ + --log-level INFO \ + --log-format text +``` + +预期会看到两条日志,内容分别包含 `framework-ready` 和 `bounded-dag-running`。该 Source 是有限数据源,数据处理完后进程会自然退出。 + +bootstrap 支持以下环境: + +| Profile | 安装内容 | 命令 | +|---|---|---| +| `core` | 框架核心和模拟 smoke/talk 链路 | `bash scripts/bootstrap.sh --profile core` | +| `detection-cpu` | gRPC、HTTP、PyAV、Pillow 告警图片和固定版本 CPU YOLO;默认值 | `bash scripts/bootstrap.sh` | +| `dev` | `detection-cpu` 加测试和 protobuf codegen 工具,并运行完整测试 | `bash scripts/bootstrap.sh --profile dev` | + +如果只希望安装、不执行自检,可加 `--skip-check`。完整参数通过以下命令查看: + +```bash +bash scripts/bootstrap.sh --help +``` + +不要使用 `uv sync --all-extras`:`yolo` 与 `yolo-cpu` 是为不同 PyTorch 来源准备的 +互斥环境。请使用 bootstrap profile,或显式只选择其中一个 extra。 +告警图片的画框和 JPEG 编码由独立的 `image` extra 提供;默认的 +`detection-cpu`/`dev` profile 已安装它,手动组合检测环境时也必须选择 +`--extra image`。 + +CLI 的四个子命令如下: + +| 命令 | 用途 | +|---|---| +| `validate -c FILE [--pipeline ID]` | 加载配置、展开环境变量、构造插件并校验 DAG;不启动 Pipeline | +| `run -c FILE [--pipeline ID]` | 启动选中的 Pipeline;未指定时启动所有 `enabled: true` 的 Pipeline | +| `plugins` | 列出内置插件和已安装 entry point 插件 | +| `models` | 列出检测模型 ID、名称、backend 和注册的全部标签 | + +`--pipeline` 可以重复传入。`run` 还支持 `--log-level` 和 `--log-format text|json`。配置错误退出码为 `2`,运行错误为 `1`,键盘中断为 `130`。 + +## 运行 PPE 检测链路 + +默认 bootstrap 就是当前 YAML 使用的 CPU 检测环境。它会从相邻的 +`../cmvr-es` 读取 proto、用锁定的 `grpcio-tools` 生成 bindings,然后安装完整 +检测依赖并校验 smoke 和 PPE 配置: + +```bash +cd /home/xtkuang/Projects/cmvr/cmvr_edge_ai +bash scripts/bootstrap.sh +``` + +如果 cmvr-es 不在相邻目录,显式指定路径: + +```bash +bash scripts/bootstrap.sh \ + --cmvr-es-root /home/xtkuang/Projects/cmvr/cmvr-es +``` + +bootstrap 默认使用 portable codegen,所以不要求 cmvr-es 已经编译出 `protoc`。 +要跳过生成(例如部署包已经包含匹配版本的 bindings),使用 +`--skip-codegen`。手动生成时可以执行: + +```bash +uv sync --locked --only-group codegen +.venv/bin/python scripts/generate_cmvr_stubs.py \ + --cmvr-es-root /home/xtkuang/Projects/cmvr/cmvr-es \ + --portable +``` + +生成文件默认写入 `src/cmvr/...`,使 `cmvr.api.*_pb2` 可以被连接器导入。 +生成后应再次执行目标 profile 的 `uv sync --locked`,让可编辑安装识别新包;bootstrap +已经按这个顺序处理。 + +在 `detect_server/pipeline.yaml` 中配置部署参数: + +```yaml +endpoints: + cmvr_es: + target: 127.0.0.1:50052 + platform: + base_url: http://127.0.0.1:8081 + +pipelines: + detection: + nodes: + camera: + with: + device_id: right_hand_cam + stream_log_interval_s: 5 + detector: + with: + attach_frame: true + inference_log_interval_s: 5 + model_options: + weights: /home/xtkuang/Projects/cmvr/changan_robot/construction-ppe-yolov8/best.pt + device: cpu + repeat_gate: + with: + alert_image: + enabled: true + jpeg_quality: 85 + platform: + with: + failure_mode: log_and_drop +``` + +然后验证并运行检测链路,不再要求预先导出环境变量: + +```bash +uv run --no-sync cmvr-edge-ai models +uv run --no-sync cmvr-edge-ai validate \ + --config detect_server/pipeline.yaml \ + --pipeline detection +uv run --no-sync cmvr-edge-ai run --config detect_server/pipeline.yaml \ + --pipeline detection \ + --log-level INFO \ + --log-format json +``` + +启动后,detector 会先输出一条 `detection model loaded`,表示对应权重已经成功 +加载。收到解码帧并完成真实 `predict` 后,会立即输出第一条 `detection inference`,之后 +按 `inference_log_interval_s` 聚合输出一次;其中 `window_frames` 是本周期推理帧数, +`window_detections` 是检测框总数,`hit_labels` 是各标签的检测框累计数。持续只有 loaded 而没有 +inference,说明相机或 decoder 尚未把帧送到模型;inference 中 detection 为 0 只表示 +当前阈值下没有命中。短时调试可设为 `1` 秒,生产环境可设为 `30`~`60` 秒,省略则关闭 +周期推理日志。这里使用标准日志而不是裸 `print`,因此与 `--log-format json` 兼容。 + +相机连接器会在每次首次连接或重连时先发 `CameraService.StartCamera`,收到成功反馈后 +才建立 `GetRGBImageStream`。终端会依次出现 `camera start requested/succeeded`、 +`camera stream opening`、`camera stream first frame` 和周期性的 `camera stream progress`。 +如果只有 opening 而没有 first frame,progress 中会持续显示 +`first_frame_received=false window_frames=0`,用于区分“RPC 已建立但相机没有出帧”。 + +运行前需要确认 Construction PPE 权重存在、cmvr-es 已启用 `right_hand_cam`,8081 平台的 +`/v1/detection-alerts` 可访问。默认 `yolo-cpu` profile 将 PyTorch 2.7.0 和 +torchvision 0.22.0 绑定到官方 CPU wheel,并固定 checkpoint 记录的 Ultralytics +8.4.31。`model_options.device` 在该环境中应保持 `cpu`,`half` 应保持 `false`。 + +x86 CUDA 和 Jetson/JetPack 的 PyTorch wheel 与驱动强绑定,不能复用这个 CPU +profile。项目保留了不绑定 CPU index 的 `yolo` extra 作为设备专用环境的基础,但 +GPU 部署前应为目标设备建立单独的 uv source/lock(或使用 NVIDIA 容器),再把 YAML +中的 `device` 改为 `cuda:0`;不要只改 YAML 就认为 CUDA 环境已经就绪。 + +`detection.model@1` 根据 `model` 从 `DetectionModelRegistry` 解析模型。`detect_labels` 只选择需要检测的标签,省略时检测注册模型的全部标签;`confidence` 是全局阈值,也可以用 `label_confidence` 为个别标签覆盖。当前内置 `construction-ppe-yolov8@1` 的 19 个标签和 `ppe-6classes-yolov8n@1` 的 6 个标签都可以通过 `cmvr-edge-ai models` 查看。`attach_frame: true` 让检测结果临时携带对应的解码帧,供后续告警节点使用;因此原 detector 到 repeat gate 的队列应保持较小,避免堆积未压缩图像。 + +六类模型的标签是 `Gloves`、`Vest`、`goggles`、`helmet`、`mask` 和 `safety_shoe`, +语义是“画面中检测到了该装备”,不是“人员缺少该装备”。它没有 `Person` 或 `No-*` +类,也没有人员与装备关联能力,因此不能只靠配置推断某个人未佩戴 PPE。该模型当前仅 +注册、未被 `detect_server/pipeline.yaml` 引用;需要恢复第二分支时,应同时配置 detector、 +8082 endpoint、HTTP Sink 和两条关联 edge。 + +若以后恢复双模型配置,应从 decoder 输出端口 fan-out,让两个 detector 共享同一个相机 +订阅和 PyAV decoder;两个模型仍会分别加载和推理,并共享应用的有界线程池。 + +`detection.repeat_gate@1` 只在一个规则的 `window_ms` 内看到至少 `min_hits` 个不同帧后生成 `DetectionAlert/v1`。同一帧有多个同类框仍只算一次;触发后进入 `cooldown_ms`,冷却期间不累计,结束后必须重新满足次数。`scope: source` 按相机统计;`scope: track` 按 `track_id` 统计,但当前 YOLO adapter 只做逐帧检测,不产生 `track_id`,因此使用 track 规则前必须增加跟踪/关联节点。 + +启用 `alert_image` 后,repeat gate 只在规则真正触发时使用 Pillow 对阈值帧画框并编码 JPEG,不会给每一帧都生成图片。告警的 `detections` 和图片中的 bounding boxes 都来自达到 `min_hits` 的阈值帧;窗口内更早帧只参与 `hit_count`、时间范围和最大置信度统计。HTTP JSON 中图片位于 `payload.image`: + +```json +{ + "image": { + "media_type": "image/jpeg", + "width": 1280, + "height": 720, + "encoding": "base64", + "data": "..." + } +} +``` + +这里的外层对象是 `DetectionAlert` payload 的片段;HTTP JSON Sink 会把内部 JPEG +`bytes` 转成上述扁平 Base64 图片对象。未启用图片,或运行时因第三方结果未附带 +帧、坏帧等原因渲染失败时,告警仍会发送且 `payload.image` 为 `null`。 +同一阈值帧若同时触发多条规则或多个 track,只编码一次相关框的并集,并让这些 +告警共享同一个不可变 JPEG 对象,以限制边缘端瞬时 CPU 和内存开销。 + +对话占位链路不依赖音频 proto: + +```bash +uv run --no-sync cmvr-edge-ai validate --config talk_server/pipeline.yaml +uv run --no-sync cmvr-edge-ai run --config talk_server/pipeline.yaml +``` + +## 配置最小示例 + +```yaml +api_version: cmvr.edge.ai/v1 + +runtime: + thread_workers: 2 + shutdown_timeout_s: 5 + +pipelines: + example: + enabled: true + nodes: + source: + uses: core.sequence_source@1 + with: + items: [hello] + schema_name: TextEvent + schema_version: 1 + sink: + uses: core.log_sink@1 + edges: + - from: source.output + to: sink.input + qos: + profile: request + capacity: 4 + overflow: block +``` + +配置模型是严格的,多余字段会报错。字符串支持以下环境变量形式: + +- `${NAME}`:变量必须存在; +- `${NAME:-default}`:未设置或为空时使用默认值; +- `env://NAME`:整个字符串取自必填环境变量。 + +v1 支持五个 `qos.profile`,并在编译期约束其溢出策略:编码 H264/H265 在解码前必须使用 `video_contiguous`,只能阻塞或拒绝;解码后的完整图像可使用 `realtime_latest` 丢旧帧控制延迟;`audio_contiguous` 和 `request` 只能阻塞或拒绝;`telemetry` 支持全部策略。未填写 QoS 时使用无损的 `request + block + capacity=1`。`reject` 会抛异常,`error` 是它的兼容别名。`max_age_ms`、`put_timeout_ms` 已预留,设置后 `validate` 会拒绝配置。完整矩阵见架构文档。 + +## 注册新的检测模型 + +检测模型和 DAG 插件是两层注册:流水线固定使用通用的 `detection.model@1`,具体模型通过 `DetectionModelRegistry` 注册 `DetectionModelSpec`。每个 spec 必须给出版本化 `model_id`、面向运维的 `name`、有序且唯一的 `supported_labels`、`backend` 和 factory。factory 返回实现 `load/predict/close` 的 `DetectionModel`;部署 YAML 中的 `model_options` 原样交给它。第三方模型包可以使用 `cmvr_edge_ai.detection_models` entry point 发布 spec 或注册回调。安装后先执行 `cmvr-edge-ai models`,再让配置引用其中的精确模型 ID。 + +模型实际输出的标签仍会在通用 Operator 边界二次校验和过滤;模型返回未注册标签会让节点失败。直接相连的重复规则若引用了 detector 没有选择的标签,也会在 `validate` 阶段被编译器拒绝。 + +## 开发插件 + +插件必须使用带版本的稳定 ID,例如 `example.text_upper@1`,并声明节点种类、输入/输出端口及 schema。工厂签名固定为 `(node_id, params)`;组件分别继承 `Source`、`Operator` 或 `Sink`。 + +```python +from collections.abc import Mapping +from typing import Any + +from cmvr_edge_ai.contracts import TextEvent +from cmvr_edge_ai.core import Emission, Envelope, Operator +from cmvr_edge_ai.plugins import PluginKind, PluginRegistry, PluginSpec + + +class UppercaseOperator(Operator): + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + self.node_id = node_id + + async def process( + self, envelope: Envelope[Any], input_port: str = "input" + ) -> Emission: + event = envelope.payload + if not isinstance(event, TextEvent): + raise TypeError("expected TextEvent") + result = TextEvent(text=event.text.upper(), role=event.role, final=event.final) + return Emission( + "output", + envelope.with_payload(result, schema_name="TextEvent", schema_version=1), + ) + + +def register_plugins(registry: PluginRegistry) -> None: + registry.register( + PluginSpec( + plugin_id="example.text_upper@1", + kind=PluginKind.OPERATOR, + factory=UppercaseOperator, + inputs={"input": "TextEvent/v1"}, + outputs={"output": "TextEvent/v1"}, + description="Uppercase text events", + ) + ) +``` + +在插件包的 `pyproject.toml` 中注册: + +```toml +[project.entry-points."cmvr_edge_ai.plugins"] +example = "my_cmvr_plugin.plugins:register_plugins" +``` + +安装插件包后,用下面的命令确认发现成功: + +```bash +uv run --no-sync cmvr-edge-ai plugins +``` + +配置文件不能通过 `module:Class` 任意导入代码;只会使用内置或已安装 entry point 注册的插件。插件完整生命周期、返回值规范和连接器开发约定见 [docs/architecture.md](docs/architecture.md#5-插件开发约定)。 + +## 机器人控制安全边界 + +控制链固定为: + +```text +Policy -> RobotCommand/v1 + -> safety.robot_command_gate@1 + -> ApprovedRobotCommand/v1 + -> cmvr.grpc.agv_command_sink@1 +``` + +AGV Sink 只接受 `ApprovedRobotCommand`,原始 `RobotCommand` 会在运行时被拒绝。配置编译器还要求安全门是执行器的直接前驱;安全门与 actuator 之间不能插入其他节点,也不能增加绕过安全门的输入边。内置安全门负责过期检查和默认 5 秒的最大 TTL、动作/设备白名单、参数范围、最小发送间隔及默认开启的单调序号检查;不合格命令会被丢弃并记录 warning,不会因为一条业务拒绝停止整条 Pipeline。 + +AGV Sink 必须绑定固定的非空 `device_id`。`set_velocity` 默认禁用;`unsafe_allow_unleased_velocity: true` 只是面向隔离测试场景的显式逃生开关,并且仍要求 Sink 配置 `vx/vy/wz` 的 `velocity_limits`。它只能限制当前客户端发出的数值,并在正常关闭时尽力调用 `stopVelocityControl`;进程崩溃、`SIGKILL`、断电或网络分区时无法保证停车。 + +真实机器人要启用持续速度控制,必须先在 cmvr-es 服务端实现带过期时间的 lease/deadman:客户端停止续租后,由 cmvr-es 在独立于 edge-ai 进程的安全路径中自动清零速度并停车。客户端的安全门、TTL 和 shutdown hook 不能替代这项服务端保护。 + +## 当前实现边界 + +- 音频内部契约 `AudioChunk/v1` 已定义,但 cmvr-es 麦克风/扬声器双向流 proto 和连接器尚未落地。 +- UDP 与 QUIC 目录目前是扩展占位,没有可用传输实现。 +- 运行时 v1 只支持 `execution.mode: async|inline`,两者当前都是单 task、单并发执行。`thread`、`process`、`model_worker` 是保留值;`concurrency != 1`、非空 `max_in_flight/timeout_s`、`ordered: false` 也是保留配置,都会在编译期被拒绝。插件仍可在组件内部显式使用 `workers.run_blocking()`、`PersistentProcessWorker` 或自有模型 Worker,但不能把 `execution` 声明误当作自动调度。 +- v1 对未实现的声明采取 fail-closed:显式设置 `runtime.max_processes/process_start_method/health_bind/reserved_memory_mb`、非默认 pipeline `priority` 或任何非空 `resources` 都会在编译期被拒绝。健康状态和队列统计目前只能通过 Python API 获取。 +- 当前没有配置热更新、配置 overlay、持久化 outbox/spool 或共享内存图像池。HTTP Sink + 只有当前进程内的有限重试;默认 `failure_mode: raise` 会终止 Pipeline,当前检测配置的 + `log_and_drop` 则在重试耗尽后记录 WARNING 并丢弃该告警。两种模式在进程退出或断电时 + 都可能丢失未上报数据。 +- 相机 Source 可以重连,但 cmvr-es 当前服务端通过 `getLatestEncodedFrame` 获取最新编码数据;如果上游在 edge-ai 收到之前已跳过 H264/H265 参考包,`video_contiguous` 无法补回数据,解码器只能在错误后重置并等待关键帧。正式部署应验证 cmvr-es 输出的是连续 access unit 流,或改为对 AI 友好的原始/JPEG/可检测不连续性的接口。 +- 内置 YOLO adapter 不运行 tracker,所有检测的 `track_id` 都为空;当前示例因此使用 `scope: source`。 +- PPE 权重仓库对权重的许可说明与 Ultralytics runtime/checkpoint 中的 AGPL 信息需要在商业交付前核对,并同时确认训练数据和权重分发许可。 +- 端口 schema 在编译期按字符串匹配;运行时不会自动验证 Python payload 类型,插件必须在边界处主动检查。 +- AGV 执行器不会自动重试。安全门已提供动作/设备白名单、TTL、参数范围、最小间隔和单调序号检查,但设备状态、command ID 去重、来源仲裁、服务端 lease/deadman 和急停优先级仍需按机器人型号补齐。服务端 deadman 未完成前,禁止把 `set_velocity` 用于真实机器人持续运动。 + +这些限制及推荐演进顺序在 [docs/architecture.md](docs/architecture.md#9-当前限制与演进顺序) 中有更详细说明。 diff --git a/configs/smoke.yaml b/configs/smoke.yaml new file mode 100644 index 0000000..d148214 --- /dev/null +++ b/configs/smoke.yaml @@ -0,0 +1,41 @@ +api_version: cmvr.edge.ai/v1 + +runtime: + thread_workers: 2 + shutdown_timeout_s: 5 + +pipelines: + smoke: + enabled: true + nodes: + source: + uses: core.sequence_source@1 + with: + items: + - framework-ready + - bounded-dag-running + schema_name: TextEvent + schema_version: 1 + + operator: + uses: core.passthrough@1 + + sink: + uses: core.log_sink@1 + with: + logger: cmvr_edge_ai.smoke + + edges: + - from: source.output + to: operator.input + qos: + profile: request + capacity: 4 + overflow: block + + - from: operator.output + to: sink.input + qos: + profile: telemetry + capacity: 4 + overflow: block diff --git a/detect_server/README.md b/detect_server/README.md new file mode 100644 index 0000000..7c535fc --- /dev/null +++ b/detect_server/README.md @@ -0,0 +1,293 @@ +# PPE 检测流水线 + +`pipeline.yaml` 是当前可运行的园区施工安全装备检测链路。当前只启用 Construction +PPE 模型和 8081 告警平台;六类模型与 8082 模拟平台的实现保留但不实例化: + +```text +cmvr-es CameraService + -> H264/H265 ImageFrame/v1 + -> media.video_decoder.pyav@1 + -> BGR8 ImageFrame/v1 + -> construction-ppe-yolov8@1 -> DetectionResult/v1 + -> detection.repeat_gate@1 -> DetectionAlert/v1 + -> POST 127.0.0.1:8081/v1/detection-alerts +``` + +## 安装与启动 + +从仓库根目录执行一键安装。默认 profile 安装锁定的 CPU 检测环境、生成 cmvr-es +bindings,并验证 smoke 与本检测配置: + +```bash +cd /home/xtkuang/Projects/cmvr/cmvr_edge_ai +bash scripts/bootstrap.sh +``` + +cmvr-es 不在相邻的 `../cmvr-es` 时指定实际路径: + +```bash +bash scripts/bootstrap.sh \ + --cmvr-es-root /home/xtkuang/Projects/cmvr/cmvr-es +``` + +环境固定 Python 3.10、Ultralytics 8.4.31、PyTorch 2.7.0 CPU 和 torchvision +0.22.0,并通过 `image` extra 安装 Pillow 以生成告警图片;所有具体包版本记录在 +仓库的 `uv.lock` 中。GPU/Jetson 需要单独适配与驱动或 JetPack 匹配的 PyTorch +wheel,不能直接复用 `detection-cpu` profile。手动组合依赖时必须显式增加 +`--extra image`,不能只依赖 YOLO 间接安装 Pillow。 + +直接编辑 `detect_server/pipeline.yaml` 中的部署参数: + +```yaml +endpoints: + cmvr_es: + target: 127.0.0.1:50052 + platform: + base_url: http://127.0.0.1:8081 + +pipelines: + detection: + nodes: + camera: + with: + device_id: right_hand_cam + stream_log_interval_s: 5 + detector: + with: + attach_frame: true + inference_log_interval_s: 5 + model_options: + weights: /home/xtkuang/Projects/cmvr/changan_robot/construction-ppe-yolov8/best.pt + device: cpu + repeat_gate: + with: + alert_image: + enabled: true + jpeg_quality: 85 + platform: + with: + failure_mode: log_and_drop +``` + +完成配置后启动,不需要再通过 shell `export` 传入这些值: + +```bash +uv run --no-sync cmvr-edge-ai models +uv run --no-sync cmvr-edge-ai validate \ + -c detect_server/pipeline.yaml \ + --pipeline detection +uv run --no-sync cmvr-edge-ai run -c detect_server/pipeline.yaml \ + --pipeline detection \ + --log-level INFO \ + --log-format json +``` + +运行时会看到类似下面两类 JSON 日志: + +```json +{"level":"INFO","logger":"cmvr_edge_ai.detection.operator","message":"detection model loaded node=detector model=construction-ppe-yolov8@1 ..."} +{"level":"INFO","logger":"cmvr_edge_ai.detection.operator","message":"detection inference node=detector model=construction-ppe-yolov8@1 total_frames=1 window_frames=1 window_detections=2 hit_labels=No-Helmet:2 ..."} +``` + +- `detection model loaded`:checkpoint 已成功加载并完成标签顺序校验;当前 detector + 应出现一条; +- `detection inference`:模型确实收到解码帧并执行了 `predict`;第一帧立即输出, + 后续按配置周期聚合; +- `window_detections=0 hit_labels=none`:模型在工作,但本周期没有高于配置阈值的命中; +- 只有 loaded、长期没有 inference:优先检查 cmvr-es 相机流、decoder 和关键帧; +- 短时观察可把 `inference_log_interval_s` 改为 `1` 秒,长期运行建议 `30`~`60` 秒, + 省略该字段会关闭周期推理日志。 + +运行前确认 cmvr-es 已启用 `right_hand_cam`、Construction PPE 权重存在,并且 8081 +平台接受 `POST /v1/detection-alerts`。默认 profile 下 `model_options.device` 应设为 +`cpu`;只有完成设备专用的 CUDA/Jetson PyTorch 环境适配后,才能改为 `cuda:0` 等值。 + +## 模型与标签 + +`detection.model@1` 不绑定某一个框架;它通过 `DetectionModelRegistry` 查找配置中的 +`model`。每个 `DetectionModelSpec` 注册版本化模型 ID、模型名称、backend 和有序 +`supported_labels`。注册表当前包含两个内置模型,但 Pipeline 只引用第一个: + +- `construction-ppe-yolov8@1`:19 类,包含原分支用于违规告警的 `No-*` 标签; +- `ppe-6classes-yolov8n@1`:`Gloves`、`Vest`、`goggles`、`helmet`、`mask`、 + `safety_shoe` 六个正向装备标签。 + +可用 `cmvr-edge-ai models` 核对 ID、名称、backend 和标签顺序。六类模型只表达 +“检测到某件装备”,不包含 `Person` 或 `No-*` 类,也不执行人员/PPE 关联;所以它 +不能直接判断某个人缺少装备。需要这种语义时,仍应增加人员检测、空间关联和缺失 +判定节点,不能把“没有检测到 helmet”直接当成“人员未戴安全帽”。 + +检测节点的关键参数: + +| 参数 | 语义 | +|---|---| +| `model` | 必须是已注册的精确模型 ID | +| `detect_labels` | 本次部署需要的标签;省略表示模型的全部注册标签 | +| `confidence` | 所有选中标签的默认置信度阈值 | +| `label_confidence` | 可选的逐标签阈值覆盖 | +| `max_fps` | 推理启动频率上限;过密的解码帧会被跳过 | +| `inference_log_interval_s` | 可选推理摘要周期(秒);首帧立即输出,省略表示关闭周期日志 | +| `attach_frame` | 是否让 `DetectionResult` 临时附带对应解码帧;生成告警图片时必须为 `true` | +| `model_options` | backend 私有选项;当前 YOLO 支持 weights、device、imgsz、iou、half、max_det、agnostic_nms | + +配置阶段会拒绝未知模型、模型不支持的 `detect_labels`、重复标签和非法阈值。YOLO 加载 checkpoint 时还会严格比较 checkpoint 的实际标签及顺序与注册信息,避免类别编号静默错位。 + +## 六类逐推理结果分支 + +这一分支当前默认关闭。恢复后,六类 detector 不连接 repeat gate,而是把每次完成推理产生的 +`DetectionResult/v1` 直接交给第二个 HTTP Sink。其 POST Envelope 的 +`schema` 为 `DetectionResult/v1`,`payload` 示例为: + +```json +{ + "detections": [ + { + "label": "helmet", + "confidence": 0.91, + "box": {"x_min": 120.0, "y_min": 60.0, "x_max": 250.0, "y_max": 220.0}, + "track_id": null + } + ], + "model_id": "ppe-6classes-yolov8n@1", + "inference_ms": 34.2, + "model_name": "PPE Detection YOLOv8n (6 Classes)" +} +``` + +该结构刻意不同于原分支的 `DetectionAlert/v1`:它没有 `rule_id`、`hit_count`、 +`event_id` 或 `image`,空检测帧也会以空 `detections` 数组上报。`cooldown_ms` 是 +repeat gate 的内部规则配置,不属于 `DetectionAlert` payload。由于 +payload 没有 event ID,HTTP Sink 的 `Idempotency-Key` 回退使用 Envelope +`trace_id`。这只是模拟的逐结果接口;若平台要求另一套字段命名或嵌套,需要增加 +平台专用转换节点/Sink,而不是仅修改 endpoint。 + +## 重复触发规则 + +`detection.repeat_gate@1` 按规则维护滑动时间窗口。规则字段如下: + +| 参数 | 语义 | +|---|---| +| `id` | 唯一规则 ID,写入告警 | +| `labels` | 任一标签匹配即视为该规则在当前帧命中 | +| `min_confidence` | 规则侧最低置信度,可高于 detector 阈值 | +| `min_hits` | 触发所需的不同帧数 | +| `window_ms` | 上述命中必须落入的时间窗口 | +| `cooldown_ms` | 告警后的静默期;期间不累计,结束后重新计数 | +| `scope` | `source` 按相机统计;`track` 按相机内的 `track_id` 分别统计 | + +一次命中按 `(source_id, sequence)` 的不同视频帧计算:同一帧出现多个相同标签框只算一次,重复投递同一帧也不会增加计数。一个规则即使配置多个标签,同一 scope 在一帧内仍只增加一次。`time_source` 可选 `captured`、`received` 或优先采集时间的 `auto`;当前 cmvr-es 成功帧通常没有 `header.timestamp`,示例显式使用 `received`。 + +`alert_image.enabled` 默认为 `false`;启用后要求 detector 同时配置 +`attach_frame: true`。`jpeg_quality` 是 `1..95` 的整数,默认 85。repeat gate +只在规则达到 `min_hits`、准备输出告警时,才用 Pillow 在当前阈值帧上绘制匹配的 +bounding boxes、标签和置信度并编码 JPEG,不会为每个推理结果都渲染。告警里的 +`detections` 和图片框均来自阈值帧;窗口内更早的命中只参与次数、时间范围和 +`max_confidence` 统计。 + +同一阈值帧同时触发多条规则或多个 track 时,repeat gate 对这些告警相关框取并集, +只画框并编码一次,然后让本帧产生的告警共享该 JPEG,避免在边缘端重复编码。 +非有限坐标、反向/退化框、非法置信度等 detection 会在规则计数前被忽略,并记录在 +`invalid_detections` 健康指标中,避免 `NaN/Inf` 令整个 HTTP JSON 告警无法发送。 + +当前 YOLO adapter 是逐帧检测,不执行人员跟踪,输出的 `track_id` 为 `None`。因此示例使用 `scope: source`;如果业务要求“同一个人连续多次违规”,必须在 detector 和 repeat gate 之间加入 tracker 及人员/PPE 关联节点,之后才能使用 `scope: track`。 + +## 相机启动与视频流日志 + +cmvr-es 的 `StartCamera` 和 `GetRGBImageStream` 是两个不同阶段:前者打开物理相机, +后者只启动编码和流式传输。连接器在每个首次连接或重连 session 中严格执行: + +```text +StartCamera -> 检查 feedback.header.success -> GetRGBImageStream -> 等待首帧 +``` + +`StartCamera` 使用 `cmvr_es.timeout_s`;RPC 异常或 `header.success=false` 都进入同一套 +指数退避重连。正常关闭只取消当前 stream,不自动调用设备级 `StopCamera`,避免影响 +同一相机的其他客户端。 + +使用 `--log-level INFO --log-format json` 时,按顺序关注: + +- `camera source configured`:endpoint、device 和超时配置完成; +- `camera start requested/succeeded`:StartCamera 已被调用并成功; +- `camera stream opening`:流请求已创建,正在等待第一帧; +- `camera stream first frame`:已经收到真实编码帧,包含 codec、尺寸、字节数和远程序号; +- `camera stream progress`:每 `stream_log_interval_s` 秒输出帧数、FPS、bitrate、关键帧、 + 最后序号和 `last_frame_age_s`;即使完全没帧也会输出 `first_frame_received=false`; +- `camera stream disconnected`:包含错误类型、重连次数和退避时间; +- `camera stream closed`、`camera source stopped`:当前 session 和本地 Source 已清理。 + +INFO 日志不会打印帧二进制。示例设置 `stream_log_interval_s: 5` 便于联调,正式部署 +可改为 30~60 秒。 + +## 视频连续性和背压 + +H264/H265 是有参考关系的编码流,解码前不能任意丢包: + +- `camera -> decoder` 使用 `video_contiguous + block`,保持 edge-ai 内部已经接受的数据连续; +- decoder 在新 session、编码参数变化、sequence gap 或解码错误后释放上下文,等待下一个关键帧; +- `decoder -> detector` 已经是完整 BGR 图像,使用 `realtime_latest + drop_oldest` 和容量 1,以有限内存换取较低实时延迟; +- `detector -> repeat_gate` 在 `attach_frame: true` 时携带未压缩图像,示例把队列容量限制为 2,避免排队的 `DetectionResult` 长时间占用大量内存。 + +该边使用 `drop_oldest` 偏向低延迟;持续过载时,被丢弃的推理结果不会计入 +`min_hits`。如果业务更重视每次推理结果都参与计数,可改为 `overflow: block`,但要 +接受延迟向上游传播,并继续保持很小的队列容量。 + +恢复第二模型时,应从 decoder 输出端口 fan-out,以避免第二次相机订阅和第二次 +H264/H265 解码;这不会复用模型计算,两个 YOLO 实例仍会分别加载权重并共享应用的 +有界线程池。 + +这个保证只覆盖 edge-ai 内部。当前 cmvr-es 服务端通过 `getLatestEncodedFrame` 读取最新编码数据;如果它在负载或时序竞争下已经跳过参考包,`video_contiguous` 无法恢复丢失内容,而且当前 edge-ai 的本地 sequence 不能可靠暴露这种上游跳包。此时 PyAV 可能报错,decoder 会重置并等待关键帧。上线前必须用真实摄像头长时间验证连续性;更稳妥的方案是让 cmvr-es 提供连续 access unit 流,或额外提供原始/JPEG/带显式 discontinuity 的 AI 接口。 + +相机 Source 默认启用指数退避重连;每次重新订阅生成新的 `session_id`,使 decoder 主动重置。`reconnect_initial_s`、`reconnect_max_s` 和可选 `max_reconnect_attempts` 可配置;省略最大次数表示持续重连直到进程关闭。 + +## HTTP 输出可靠性 + +每次规则触发会产生带唯一 `event_id` 的 `DetectionAlert/v1`。HTTP Sink 优先把该 +`event_id` 放入 `Idempotency-Key`,没有事件 ID 时才回退到 `trace_id`。它对连接/超时 +错误和配置的临时 HTTP 状态执行有限指数退避,示例最多尝试 3 次。 + +`failure_mode` 支持两种明确语义:默认 `raise` 在最终投递失败时让 Pipeline 失败;当前 +检测配置使用 `log_and_drop`,最终连接失败、超时或非 2xx 时输出 +`HTTP report dropped ... action=drop` WARNING,丢弃当前告警并继续处理后续视频帧。 +`CancelledError`、序列化错误和未知本地程序异常不会被吞掉。 + +当前唯一 HTTP 输出将告警发送到 +`http://127.0.0.1:8081/v1/detection-alerts`。8082 六类逐推理结果 endpoint 当前未配置。 + +启用告警图片后,POST JSON 的 `payload.image` 结构如下: + +```json +{ + "media_type": "image/jpeg", + "width": 1280, + "height": 720, + "encoding": "base64", + "data": "/9j/4AAQSk..." +} +``` + +`width/height` 是 JPEG 的像素尺寸;HTTP JSON Sink 将内部 JPEG `bytes` 特判为 +上述扁平对象,`encoding` 固定为 `base64`。关闭 `alert_image`,或运行时因第三方 +结果未附带帧、坏帧等原因渲染失败时,告警仍会发送且该字段为 `null`。Base64 会 +额外增加约三分之一的体积,平台和反向代理需要配置足够的请求体上限。 + +这不是持久化 outbox:`log_and_drop` WARNING 表示该告警已经永久丢失,不会自动补发; +每次告警在放弃前仍会完成有限重试,因此平台离线时 Sink 队列会短暂阻塞。进程崩溃、 +断电或告警仍在内存队列中时也可能丢失。平台必须按幂等键去重;对“不可丢告警”的 +部署,还需要后续增加有界磁盘 outbox、投递确认和恢复发送。 + +## 部署风险 + +- 模型 README 报告某些小样本类别存在漏检,实际园区需要按相机角度、光照、遮挡和距离重新标定置信度与 `min_hits`;规则降噪不能补偿模型系统性漏检。 +- 权重目录对权重许可的描述与 Ultralytics runtime/checkpoint 中的 AGPL 信息需要在商业部署前核对;同时确认训练数据来源和权重再分发权利。 +- `.pt` 使用 PyTorch checkpoint loader,只加载可信构建和受控分发的权重,并固定、验证实际 Ultralytics 8.x 版本。 + +如果检测结果还要驱动机器人,控制路径必须保持: + +```text +DetectionResult -> policy -> RobotCommand/v1 + -> safety.robot_command_gate@1 + -> ApprovedRobotCommand/v1 + -> cmvr.grpc.agv_command_sink@1 +``` + +编译器要求 actuator 的每个直接前驱都是安全门,安全门和执行器之间不能插入普通变换节点,也不能存在绕过路径。 diff --git a/detect_server/pipeline.yaml b/detect_server/pipeline.yaml new file mode 100644 index 0000000..2ce7667 --- /dev/null +++ b/detect_server/pipeline.yaml @@ -0,0 +1,180 @@ +api_version: cmvr.edge.ai/v1 + +runtime: + thread_workers: 3 + shutdown_timeout_s: 8 + +endpoints: + cmvr_es: + transport: grpc + # cmvr-es gRPC address. Change this value for each deployed robot. + target: 192.168.0.102:50052 + tls: false + timeout_s: 5 + options: + max_receive_mb: 32 + + platform: + transport: http + # Violation-alert platform HTTP base URL. + base_url: http://127.0.0.1:8081 + timeout_s: 3 + +pipelines: + detection: + enabled: true + nodes: + camera: + uses: cmvr.grpc.camera_rgb_stream@1 + with: + endpoint: cmvr_es + device_id: wrist_cam + pixel_format: BGR8 + reconnect: true + reconnect_initial_s: 0.5 + reconnect_max_s: 10 + # Log stream state immediately on first frame and emit a periodic + # progress/stall heartbeat without printing every encoded frame. + stream_log_interval_s: 5 + + decoder: + uses: media.video_decoder.pyav@1 + + detector: + uses: detection.model@1 + with: + model: construction-ppe-yolov8@1 + # Omitting detect_labels means all registered labels. This example + # asks the backend to return only PPE violations used by the rules. + detect_labels: + - No-Boots + - No-Ear-Protection + - No-Glass + - No-Glove + - No-Helmet + - No-Mask + - No-Vest + confidence: 0.50 + max_fps: 10 + # Log the first completed inference immediately, then aggregate one + # heartbeat every 5 seconds so model activity is visible without + # printing every frame. Set to 1 for one-second debugging, or omit to disable. + inference_log_interval_s: 5 + # Keep the decoded threshold frame available to repeat_gate so an + # annotated alert image can be rendered only when a rule triggers. + attach_frame: true + model_options: + # Model artifact and inference device are deployment configuration, + # not process environment requirements. + weights: /home/xtkuang/Projects/cmvr/changan_robot/construction-ppe-yolov8/best.pt + device: cpu + imgsz: 640 + iou: 0.70 + half: false + max_det: 100 + + repeat_gate: + uses: detection.repeat_gate@1 + with: + # cmvr-es currently omits capture timestamps on successful stream + # frames, so received time is the deterministic deployment default. + time_source: received + alert_image: + enabled: true + jpeg_quality: 85 + rules: + - id: no-boots + labels: [No-Boots] + min_hits: 3 + window_ms: 2000 + cooldown_ms: 30000 + min_confidence: 0.50 + scope: source + - id: no-ear-protection + labels: [No-Ear-Protection] + min_hits: 3 + window_ms: 2000 + cooldown_ms: 30000 + min_confidence: 0.50 + scope: source + - id: no-glass + labels: [No-Glass] + min_hits: 3 + window_ms: 2000 + cooldown_ms: 30000 + min_confidence: 0.50 + scope: source + - id: no-glove + labels: [No-Glove] + min_hits: 3 + window_ms: 2000 + cooldown_ms: 30000 + min_confidence: 0.50 + scope: source + - id: no-helmet + labels: [No-Helmet] + min_hits: 3 + window_ms: 2000 + cooldown_ms: 30000 + min_confidence: 0.50 + scope: source + - id: no-mask + labels: [No-Mask] + min_hits: 3 + window_ms: 2000 + cooldown_ms: 30000 + min_confidence: 0.50 + scope: source + - id: no-vest + labels: [No-Vest] + min_hits: 3 + window_ms: 2000 + cooldown_ms: 30000 + min_confidence: 0.50 + scope: source + + platform: + uses: platform.http_json_sink@1 + with: + endpoint: platform + path: /v1/detection-alerts + # Platform outages must not stop camera capture or inference. After + # bounded retries, log a WARNING and drop only this report. + failure_mode: log_and_drop + max_attempts: 3 + retry_initial_s: 0.25 + retry_max_s: 2 + + edges: + # Encoded H264/H265 packets must remain contiguous before decode. + - from: camera.frames + to: decoder.frames + qos: + profile: video_contiguous + capacity: 8 + overflow: block + + # Once frames are decoded, keeping only the newest frame bounds latency. + - from: decoder.frames + to: detector.frames + qos: + profile: realtime_latest + capacity: 1 + overflow: drop_oldest + + - from: detector.detections + to: repeat_gate.detections + qos: + profile: telemetry + # DetectionResult carries a decoded frame when attach_frame is enabled; + # keep this queue short so raw image buffers cannot accumulate. Under + # sustained overload drop_oldest also means dropped results do not count. + capacity: 2 + overflow: drop_oldest + + - from: repeat_gate.alerts + to: platform.input + qos: + profile: telemetry + capacity: 64 + overflow: block diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..73b5cb6 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,812 @@ +# cmvr-edge-ai 架构与开发指南 + +本文描述当前代码已经实现的架构、配置语义和扩展边界。文中的“当前”指运行时 v1;规划能力会明确标注,避免把配置字段误认为已经具备的调度能力。 + +## 1. 目标与边界 + +`cmvr-edge-ai` 负责在单台机器人边缘计算设备上,把三类模块可靠地连接起来: + +1. 上游数据:cmvr-es 的相机、未来的麦克风音频流,以及平台下发的数据; +2. 中间 AI:视频解码、检测、VAD、ASR、LLM、TTS、决策和策略; +3. 下游输出:cmvr-es 的 AGV/机械臂/扬声器控制,以及平台 HTTP/gRPC 接口。 + +框架刻意保持轻量:默认不依赖 Kafka、Redis、Celery 或 Kubernetes。一个进程内使用 `asyncio` 管理 I/O 和 DAG;需要 CPU/GPU 隔离的插件自行接入有限线程池或常驻 Worker。这样可以在算力、内存和显存有限的边缘端按需增加复杂度。 + +核心设计约束: + +- **配置驱动**:YAML 只选择已注册插件、连接端口并声明 QoS/资源,不执行任意 Python 导入; +- **协议隔离**:protobuf、HTTP JSON、未来 QUIC 包只在连接器中出现; +- **内部契约稳定**:算法通过 `Envelope` 和版本化 schema 交流; +- **有界资源**:所有边都有固定容量,满载行为必须显式配置; +- **故障显式**:节点异常默认终止 Pipeline;只有连接器显式配置的有界重试和 + `log_and_drop` 等降级策略可以改变该行为; +- **控制失效安全**:执行器只接收已批准命令,且其每个直接前驱都必须是安全门;持续运动还依赖 cmvr-es 服务端 lease/deadman。 + +## 2. 分层架构 + +```mermaid +flowchart TB + subgraph External["外部系统"] + CE["cmvr-es
gRPC / future QUIC"] + PLAT["业务平台
HTTP / gRPC"] + end + + subgraph Boundary["协议边界"] + SRC["Source Connectors"] + SNK["Sink Connectors"] + POOL["共享 gRPC Channel / HTTP Client"] + end + + subgraph Runtime["单进程 asyncio 运行时"] + ENV["Envelope + 内部契约"] + QUEUE["每条 Edge 的有界队列"] + OPS["Operator DAG"] + SAFE["Safety Gate"] + LIFE["生命周期 / 健康状态 / 优雅退出"] + end + + CFG["严格 YAML + 插件注册表 + DAG 编译器"] --> Runtime + CE --> SRC + PLAT --> SRC + SRC --> ENV + ENV --> QUEUE --> OPS + OPS --> SAFE --> SNK + OPS --> SNK + SNK --> CE + SNK --> PLAT + POOL --- SRC + POOL --- SNK + LIFE --- SRC + LIFE --- OPS + LIFE --- SNK +``` + +### 2.1 配置层 + +`config/loader.py` 使用 `yaml.safe_load` 读取文件并递归展开环境变量,随后由 Pydantic 严格模型校验。所有模型均设置 `extra="forbid"`,拼错字段会立即失败。 + +`compiler.py` 继续完成无法只靠字段类型判断的检查: + +- `api_version` 必须等于 `cmvr.edge.ai/v1`; +- 插件必须存在,工厂创建的对象必须与声明的 Source/Operator/Sink 种类一致; +- Source 不能有入边,Sink 不能有出边,Operator/Sink 至少有一条入边; +- 端口必须存在,schema 必须相同或其中一端为通配符 `*`; +- 图必须无环,不能存在重复边; +- 带 `transport:` 标签的连接器只能引用相同 transport 的 endpoint; +- 活跃 Pipeline 中不能包含 `enabled: false` 的节点; +- 每个带 `actuator` 标签 Sink 的直接前驱都必须是带 `safety_gate` 标签的节点,安全门和执行器之间不能插入其他节点; +- v1 只接受 `execution.mode: async|inline` 和默认单并发语义;保留的 mode/并发字段会在编译期被拒绝; +- 显式保留 runtime 字段、非 `normal` priority 和非空 resources 会被拒绝,不会被静默忽略; +- QoS profile 必须是 v1 支持的五种之一,且 overflow 必须符合该 profile 的语义; +- 直接连接的 `detection.model@1 -> detection.repeat_gate@1` 会检查规则标签确实包含在 detector 本次选择的标签中; +- 当前未实现的 `max_age_ms` 和 `put_timeout_ms` 不能设置。 + +`validate` 会实际调用插件工厂,因此也会检查构造函数所需参数、已注册模型 ID 和标签选择;但不会执行组件的 `setup()`。YOLO 权重是否存在、网络连通性、cmvr protobuf 是否存在、PyAV/Ultralytics/Pillow 可选依赖是否可导入,以及 checkpoint 内部标签是否与注册信息一致,要到 `run` 的 setup 或首个编码帧时才检查;启用 `alert_image` 后 Pillow 会在 repeat gate 的 setup 阶段提前检查。 + +### 2.2 内部契约层 + +协议边界进入运行时后统一变为: + +```text +Envelope[T] +├── payload: T +├── schema_name + schema_version +├── source_id + sequence +├── captured_at_ns + received_at_ns +├── deadline_ns +├── trace_id + session_id +└── attributes +``` + +`Envelope` 是冻结 dataclass,`attributes` 被复制为只读 mapping。它只保证浅层不可变:如果 payload 是 NumPy 数组等可变对象,发布后必须把它当作只读数据,才能安全地 fan-out 并为以后零拷贝/共享内存保留空间。 + +当前稳定内部 payload: + +| Schema | Python 类型 | 用途 | +|---|---|---| +| `ImageFrame/v1` | `ImageFrame` | 编码视频包或解码后的图像 buffer | +| `AudioChunk/v1` | `AudioChunk` | 连续音频块,包含格式、采样率和 discontinuity 标记 | +| `DetectionResult/v1` | `DetectionResult` | 检测框、标签、置信度和推理时间;可临时附带对应解码帧供告警节点使用 | +| `DetectionAlert/v1` | `DetectionAlert` | 规则 ID、模型、scope、时间窗口、命中数、置信度、阈值帧检测框、可选 JPEG 告警图和唯一 event ID | +| `TextEvent/v1` | `TextEvent` | ASR/LLM/TTS 链路中的文本事件 | +| `ChatTurn/v1` | `ChatTurn` | 带 session 和历史的对话输入 | +| `RobotCommand/v1` | `RobotCommand` | 带 TTL、序号和 command ID 的协议无关控制命令 | +| `ApprovedRobotCommand/v1` | `ApprovedRobotCommand` | 由可信安全门批准后交给执行器的控制命令 | + +`PluginSpec` 中的 schema 是编译期契约字符串。当前运行时不会反射检查 payload 的 Python 类型;连接器和算法插件仍应使用 `isinstance` 或自己的严格模型在边界处失败。 + +### 2.3 组件层 + +运行时只有三类组件: + +| 组件 | 输入/输出 | 核心方法 | +|---|---|---| +| `Source` | 无输入,一个或多个输出端口 | `messages()` 返回 async iterator | +| `Operator` | 一个或多个输入/输出端口 | `process(envelope, input_port)` | +| `Sink` | 一个或多个输入端口,无输出 | `consume(envelope, input_port)` | + +所有组件共享以下生命周期: + +```text +setup(context) -> start() -> 执行 -> health() -> stop() +``` + +建议在 `__init__` 只解析轻量参数,在 `setup` 建立网络连接或加载模型,在 `stop` 释放资源并保证幂等。`ComponentContext` 提供 `pipeline_id`、`node_id`、全局 shutdown event,以及 endpoint、共享连接池和节点配置等 metadata。 + +Operator 可以返回: + +- `None`:过滤该消息; +- `Envelope`:从默认 `output` 端口发送; +- `Emission(port, envelope)`:从指定端口发送; +- 上述对象的同步或异步 iterable:一进多出。 + +Source 也可以直接 yield `Envelope` 或 `Emission`。返回原始 bytes、模型对象或其他未包装值会在产生该值的节点附近报错。 + +### 2.4 DAG 运行时 + +当前每个 Pipeline 在一个 asyncio event loop 中运行,每个节点一个 task。`execution.mode: async` 和 `inline` 在 v1 中都由该 task await 组件方法,Operator 和 Sink 每次只处理一个消息。v1 不会复制节点 task,也不提供配置驱动的并行调度。 + +每条有向边拥有独立队列。一个输出端口 fan-out 到多条边时,同一个只读 Envelope 会送入每个队列;队列之间的容量和丢弃计数独立。注意:路由会等待所有边的 `put()`,因此任意 fan-out 分支使用 `overflow: block` 且消费变慢时,仍会对共同生产者产生背压。希望“平台慢但控制链不停”时,平台分支必须选择合适的丢弃策略,或在后续加入独立 spool。 + +多个入边到同一节点时,运行时处理最先就绪的边,不保证不同边之间的全局顺序。单条边内部保持 FIFO(`drop_*` 导致的丢弃除外)。 + +节点自然结束后,其全部出边会关闭;消费者会读完已接受的数据后结束。收到 SIGINT/SIGTERM 时: + +1. 设置全局 shutdown event; +2. 取消 Source task,并先调用 Source 的 `stop()`; +3. 让 Operator/Sink 在 `shutdown_timeout_s` 内排空队列; +4. 超时后取消剩余 task、丢弃未处理消息; +5. 按 setup 的反序释放组件和共享网络客户端。 + +任何未被组件显式处理的节点执行异常都会包装为带 node ID 的 `NodeExecutionError`,停止 +Pipeline,并由 CLI 以运行错误退出。相机 Source 和平台 HTTP Sink 内部实现了各自明确、 +有界的重连/重试语义;HTTP Sink 还可显式选择 `log_and_drop`,但框架仍不提供通用节点级 +supervisor 或 retry policy。 + +## 3. 配置参考 + +### 3.1 根字段 + +| 字段 | 必填 | 说明 | +|---|---:|---| +| `api_version` | 是 | 当前只能是 `cmvr.edge.ai/v1` | +| `runtime` | 否 | 进程级运行参数 | +| `endpoints` | 否 | 命名的外部服务连接信息 | +| `pipelines` | 是 | 至少一个命名 Pipeline | + +endpoint、pipeline 和 node 名称只能包含字母、数字、`_`、`-`,且不能以数字开头。端口引用固定使用 `node.port`。 + +### 3.2 `runtime` + +| 字段 | 默认值 | 当前语义 | +|---|---:|---| +| `thread_workers` | `4` | 创建共享 `ThreadPoolExecutor`,并通过 ComponentContext 提供给插件 | +| `max_processes` | `1` | 保留;v1 配置中必须省略,显式设置(即使写默认值)也会被拒绝 | +| `process_start_method` | `spawn` | 保留;v1 配置中必须省略 | +| `shutdown_timeout_s` | `10.0` | 优雅排空的最长等待时间 | +| `health_bind` | `null` | 保留;v1 配置中必须省略,当前不监听健康检查端口 | +| `reserved_memory_mb` | `0` | 保留;v1 配置中必须省略,当前不执行内存仲裁 | + +v1 可配置的 runtime 字段只有 `thread_workers` 和 `shutdown_timeout_s`。边缘端应保持较小的 `thread_workers`;第三方推理库本身还可能创建线程,应同时限制 OpenMP/MKL/模型运行时线程数,避免过度订阅 CPU。 + +### 3.3 `endpoints.` + +| 字段 | 默认值 | 说明 | +|---|---:|---| +| `transport` | 无 | 必填,转为小写;当前连接器支持 `grpc`、`http` | +| `target` | `null` | gRPC 地址,例如 `127.0.0.1:50052` | +| `base_url` | `null` | HTTP 基础 URL | +| `bind` | `null` | 为未来 ingress/server 连接器预留 | +| `tls` | `false` | 仅用于 gRPC channel 是否使用 TLS;HTTP endpoint 禁止设置该字段 | +| `timeout_s` | `5.0` | 连接器请求超时;流式相机订阅不使用该 unary timeout | +| `metadata` | `{}` | gRPC metadata 预留;当前 HTTP 连接器把它作为默认 header | +| `options` | `{}` | 传输私有参数 | + +当前 gRPC options 支持: + +```yaml +options: + root_certificates: /path/to/ca.pem + max_receive_mb: 32 + max_send_mb: 8 + channel_options: + grpc.keepalive_time_ms: 20000 +``` + +HTTP 是否使用 TLS 由 `base_url` 的 `https://` scheme 决定,证书验证默认开启;只有隔离测试环境才可显式设置 `options.verify_tls: false`。当前 gRPC `metadata` 尚未自动附加到 RPC,若需要鉴权应在连接器中显式实现。 + +### 3.4 `pipelines.` + +| 字段 | 默认值 | 说明 | +|---|---:|---| +| `enabled` | `true` | 未指定 `--pipeline` 时是否启动 | +| `priority` | `normal` | v1 必须为 `normal`;`low/high` 或数值优先级会因无调度器而被拒绝 | +| `nodes` | 无 | 至少一个节点 | +| `edges` | `[]` | 有向连接列表 | + +不要在活跃 Pipeline 中保留 `enabled: false` 节点;当前编译器会直接拒绝。要暂时关闭逻辑,请禁用整个 Pipeline 或从图和配置中移除该节点。 + +### 3.5 `nodes.` + +| 字段 | 默认值 | 说明 | +|---|---:|---| +| `uses` | 无 | 版本化插件 ID,例如 `cmvr.grpc.camera_rgb_stream@1` | +| `with` | `{}` | 原样传给插件工厂的参数;Python 模型内字段名为 `params` | +| `execution` | 见下表 | 调度意图声明 | +| `resources` | `{}` | 预留资源需求字段;v1 必须保持为空/默认值 | +| `enabled` | `true` | 当前只允许活跃 Pipeline 中为 `true` | + +`execution` 字段: + +| 字段 | 默认值 | 允许值/限制 | +|---|---:|---| +| `mode` | `async` | v1 支持 `async`、`inline`;`thread/process/model_worker` 为保留值并被编译器拒绝 | +| `concurrency` | `1` | v1 必须为 `1`,其他值被编译器拒绝 | +| `max_in_flight` | `null` | 预留;非空值被编译器拒绝 | +| `timeout_s` | `null` | 预留;非空值被编译器拒绝 | +| `ordered` | `true` | v1 必须为 `true`,`false` 被编译器拒绝 | + +`async` 与 `inline` 当前没有调度差异,都是 event loop 中的单 task、单 in-flight 调用。配置模型保留其他枚举值是为了后续版本演进,不代表 v1 能执行它们;`validate` 会 fail closed,而不是静默退化到 async。 + +`resources` 模型预留了 `cpu_cores`、`memory_mb`、`gpu_device`、`gpu_memory_mb`、`exclusive_gpu`,但 v1 无法强制执行资源隔离,因此任何非空/非默认声明都会被编译器拒绝。不要依赖“只写说明但不生效”的资源配置;当前应在部署层和插件实现中限制 CPU/GPU。 + +### 3.6 `edges[]` 与 QoS + +```yaml +- from: camera.frames + to: decoder.frames + qos: + profile: video_contiguous + capacity: 8 + overflow: block +- from: decoder.frames + to: detector.frames + qos: + profile: realtime_latest + capacity: 1 + overflow: drop_oldest +``` + +| 字段 | 默认值 | 当前语义 | +|---|---:|---| +| `profile` | `request` | v1 支持 `realtime_latest`、`video_contiguous`、`audio_contiguous`、`request`、`telemetry` | +| `capacity` | `1` | 队列最大消息数 | +| `overflow` | `block` | `drop_oldest`、`drop_newest`、`block`、`reject`;`error` 是 `reject` 的兼容别名 | +| `max_age_ms` | `null` | 已预留;设置后编译失败 | +| `put_timeout_ms` | `null` | 已预留;设置后编译失败 | + +溢出行为: + +| 策略 | 队列满时行为 | 适合场景 | +|---|---|---| +| `drop_oldest` | 丢掉最旧消息,再接受新消息 | 实时视频,优先处理最新画面 | +| `drop_newest` | 保留已有消息,拒收这次新消息但不抛异常 | 需要保留已排队批次的低优先级数据 | +| `block` | 生产者等待空位 | 必须连续的视频编码包、音频或请求;会传播背压 | +| `reject` / `error` | 抛出队列满异常,Pipeline 失败 | 不能静默丢失且希望监督器介入的控制链 | + +v1 强制执行的 profile/overflow 矩阵: + +| profile | 允许的 overflow | 典型用途与注意事项 | +|---|---|---| +| `realtime_latest` | `drop_oldest`、`drop_newest` | 已解码的完整视频帧;常用容量 `1..2`,优先限制推理延迟 | +| `video_contiguous` | `block`、`reject`、`error` | H264/H265 等 inter-frame 编码流在解码前不能静默丢包;此保证不覆盖上游在发送前已经丢失的数据 | +| `audio_contiguous` | `block`、`reject`、`error` | 连续音频不能静默丢弃;阻塞预算必须明确,拒绝时应重建连续性 | +| `request` | `block`、`reject`、`error` | 请求/控制;使用有限容量和消息 TTL,禁止执行积压旧动作 | +| `telemetry` | 所有五种策略 | 平台上报;当前无磁盘 spool,按数据重要性选择阻塞、丢弃或失败 | + +队列记录入队、出队、水位、丢弃、拒绝和强制关闭丢弃计数,当前可通过 Python `PipelineRuntime.edge_stats()` 获取,尚未暴露为服务指标。 + +## 4. 内置插件与连接器 + +运行 `cmvr-edge-ai plugins` 可查看当前实际注册结果。 + +### 4.1 基础插件 + +| 插件 ID | 类型 | 端口 | 用途 | +|---|---|---|---| +| `core.sequence_source@1` | Source | `output: *` | 有限模拟数据源,用于 smoke/replay | +| `core.passthrough@1` | Operator | `input: * -> output: *` | 占位和协议无关透传 | +| `core.log_sink@1` | Sink | `input: *` | 把 Envelope 摘要和 payload 写日志 | +| `safety.robot_command_gate@1` | Operator | `RobotCommand/v1 -> ApprovedRobotCommand/v1` | TTL、白名单、参数限值、序号与限频,并建立批准边界 | + +安全门参数: + +```yaml +with: + allowed_actions: [set_velocity, emergency_stop] + allowed_devices: [src1100] + min_interval_ms: 100 + max_ttl_ms: 5000 + enforce_monotonic_sequence: true + argument_limits: + set_velocity: + vx: {min: -0.3, max: 0.3} + vy: {min: -0.2, max: 0.2} + wz: {min: -0.5, max: 0.5} +``` + +`allowed_actions` 必须非空;`allowed_devices` 为空表示不额外限定设备。`max_ttl_ms` 默认 5000,拒绝有效期异常长的命令。`require_limits_for` 可以列出还必须配置参数范围的 action;`set_velocity` 无条件要求 `argument_limits.set_velocity` 同时定义 `vx/vy/wz`。范围边界必须是有限数且 `min <= max`。过期、过快、重放、乱序、未授权、缺少参数或越界命令都会 fail-closed 丢弃并写审计 warning,不会因一条业务拒绝停止整条控制 Pipeline。通过检查后,安全门把原始 `RobotCommand` 包装为带 `approved_by/approved_at_ns` 的 `ApprovedRobotCommand`。 + +### 4.2 检测模型、解码与触发规则 + +检测流水线内置三个模型无关插件: + +| 插件 ID | 输入 -> 输出 | 语义 | +|---|---|---| +| `media.video_decoder.pyav@1` | `ImageFrame/v1 -> ImageFrame/v1` | 用可选 PyAV 保持 H264/H265 decoder context,输出 packed `BGR8`;stream/session/codec/尺寸变化、sequence gap 或解码错误后重置并等待关键帧 | +| `detection.model@1` | `ImageFrame/v1 -> DetectionResult/v1` | 加载一个已注册模型,在线程池中推理,按部署标签和阈值二次过滤;可附带对应解码帧 | +| `detection.repeat_gate@1` | `DetectionResult/v1 -> DetectionAlert/v1` | 按不同帧、时间窗口、scope 和 cooldown 把逐帧检测转换为平台告警,仅在规则触发时按需画框并编码 JPEG | + +核心包不会强制安装大型视觉运行时。使用解码器安装 `video` extra;告警图的画框和 JPEG 编码由 Pillow 提供,安装独立的 `image` extra;内置 YOLO adapter 提供通用 `yolo` 和固定 CPU wheel 的 `yolo-cpu` 两个互斥 extra。当前 PPE CPU 链路通过 `bash scripts/bootstrap.sh` 一键生成 cmvr-es bindings,并按 `uv.lock` 安装 `grpc + http + video + image + yolo-cpu`。开发环境使用 `bash scripts/bootstrap.sh --profile dev`。PyAV、Pillow 和 Ultralytics 都延迟导入,因此不运行对应检测能力的对话服务不会加载它们。不要依赖 Ultralytics 间接带入 Pillow;手动组合告警图片环境时必须显式选择 `--extra image`。CUDA/Jetson 必须按目标驱动或 JetPack 建立独立依赖源和锁文件。 + +具体模型不再各自注册一套 DAG 插件。`DetectionModelRegistry` 保存 `DetectionModelSpec`: + +| 字段 | 说明 | +|---|---| +| `model_id` | 版本化稳定 ID,例如 `construction-ppe-yolov8@1` | +| `name` | 日志、CLI 和告警中的可读模型名称 | +| `supported_labels` | 有序、非空、无重复的标签;顺序必须与 backend 类别编号一致 | +| `backend` | backend 标识,例如 `ultralytics-yolo` | +| `factory` | 接收 `model_options` 并返回 `DetectionModel` 的工厂 | + +`cmvr-edge-ai models` 输出当前 model registry 的 ID、name、backend 和 labels。第三方包可在 `cmvr_edge_ai.detection_models` entry point 中暴露一个 `DetectionModelSpec` 或注册回调。当前内置 `construction-ppe-yolov8@1`(19 类)和 `ppe-6classes-yolov8n@1`(`Gloves/Vest/goggles/helmet/mask/safety_shoe`);YOLO adapter 在加载时严格比较 checkpoint `model.names` 与各自注册的标签及顺序,避免错误的类别编号继续运行。 + +`detection.model@1` 参数: + +```yaml +with: + model: construction-ppe-yolov8@1 + detect_labels: [No-Helmet, No-Vest] + confidence: 0.5 + label_confidence: + No-Helmet: 0.6 + max_fps: 10 + inference_log_interval_s: 5 + attach_frame: true + model_options: + weights: /home/xtkuang/Projects/cmvr/changan_robot/construction-ppe-yolov8/best.pt + device: cpu + imgsz: 640 +``` + +- `detect_labels` 省略时选择模型注册的全部标签;显式空列表、重复或未知标签会失败; +- `confidence` 是全局阈值,`label_confidence` 可逐标签覆盖;backend 接收所有选中标签中的最低阈值,通用 Operator 再逐框做严格后过滤; +- `max_fps` 是推理启动频率上限,跳过的帧不会产生 `DetectionResult`; +- `inference_log_interval_s` 省略时不输出周期推理日志;配置后第一帧立即输出,之后按 + 周期聚合 `window_frames/window_detections/hit_labels/avg_inference_ms`。模型加载成功 + 始终输出一次 INFO。这样可以区分“权重已加载但没有输入帧”“持续推理但没有命中”与 + “检测到目标”,同时避免逐帧日志拖慢边缘端; +- `attach_frame` 默认 `false`;启用时 `DetectionResult` 临时引用对应的解码帧,供 repeat gate 在阈值帧上生成告警图片。由于它携带未压缩图像,detector 到 repeat gate 的队列容量必须保持较小; +- `model_options` 属于 backend;内置 YOLO 支持 `weights/device/imgsz/iou/half/max_det/agnostic_nms`; +- YOLO 只接受解码后的 `BGR8/RGB8` packed buffer,并把 `RGB8` 转为 backend 使用的 BGR 顺序;它不运行 tracker,返回的 `track_id` 为 `None`。 + +示例 detector 到 repeat gate 使用小容量 `drop_oldest` 队列以控制延迟和原始帧内存; +持续过载时丢弃的推理结果不会参与 `min_hits`。需要每个已完成推理都参与计数时可改 +为 `block`,代价是背压和延迟向上游传播。 + +如果恢复六类模型,可把同一个 decoder 输出 fan-out 到两个 detector,而不是创建两个 +独立 Pipeline: + +```text +camera -> decoder -> construction detector -> repeat gate -> 8081 alert API + `-> six-class detector -----------------> 8082 result API +``` + +该可选双分支方案的两条 `decoder.frames` 出边使用独立的 +`realtime_latest + drop_oldest` 容量 1 队列, +路由的是同一个只读 `ImageFrame` 引用。这样只建立一个相机 gRPC stream 和一个 PyAV +decoder;如果把两个分支拆成两个 Pipeline,即使 gRPC channel 可以复用,也会创建 +两个订阅 RPC 和两个 decoder 实例。fan-out 只复用输入,两个 YOLO 模型仍分别加载并 +执行推理,共享进程级有界线程池。示例把六类 YOLOv8n 限制为 5 FPS,避免在尚未完成 +目标硬件测量前让两个 CPU 模型都追赶相机帧率。 + +六类模型只包含正向装备类,没有 `Person` 或 `No-*` 标签;它的 +`DetectionResult/v1` 表示当前推理帧中“检测到了哪些装备”,不能单独推断某个人 +缺少装备。该分支保持 `attach_frame: false`,绕过 repeat gate,把每个完成推理的 +结果直接发送到 `/v1/ppe-detections`,所以不会生成 `DetectionAlert` 的 `rule_id`、 +`hit_count`、event ID 或告警图字段;`cooldown_ms` 只是 repeat gate 的内部规则配置, +不属于告警 payload。输出边使用容量 16 的 `telemetry + drop_oldest`,模拟 +平台持续变慢时会优先保留较新的结果;要求逐条可靠送达时应改用可接受背压的策略或 +增加持久 outbox。 + +`detection.repeat_gate@1` 的 `rules` 每项包含 `id/labels/min_hits/window_ms/cooldown_ms/min_confidence/scope`。一个规则/scope 在同一 `(source_id, sequence)` 帧最多增加一次 hit,不按框数量累加;`window_ms` 内达到 `min_hits` 才输出告警。触发后清空命中窗口,`cooldown_ms` 内的帧不累计,冷却结束后重新计数。`time_source` 支持 `captured/received/auto`;`captured` 缺失会失败,`auto` 优先 captured 后回退 received。 + +告警图片是 repeat gate 的全局配置,不需要在每条 rule 中重复: + +```yaml +with: + time_source: received + alert_image: + enabled: true + jpeg_quality: 85 + rules: + - id: no-helmet + labels: [No-Helmet] + min_hits: 3 + window_ms: 2000 +``` + +`alert_image.enabled` 默认 `false`;启用时上游 detector 必须设置 +`attach_frame: true`。`jpeg_quality` 默认 85,只接受 `1..95` 的整数。Pillow 只在 +规则达到阈值并准备发出告警时绘制 bounding boxes、标签和置信度并编码 JPEG, +不会为每个 `DetectionResult` 产生图片。 + +`scope: source` 按相机和规则隔离状态;`scope: track` 还按 `track_id` 隔离,同一 track ID 在不同相机之间不会混合。没有 track ID 的检测会被 track 规则忽略。当前 Construction PPE 告警分支必须使用 `source`;要判断同一个人,需要先增加 tracker 和人员/PPE 关联节点。 + +每次触发创建 `DetectionAlert.event_id`。告警的 `detections` 和图片中的 bounding boxes 只来自达到 `min_hits` 的阈值帧,以控制内存和 HTTP payload;窗口内更早帧只参与 `labels/max_confidence/first_seen/last_seen/hit_count` 的聚合。内部可选图片为 JPEG bytes;HTTP wire 中 `payload.image` 被序列化为扁平对象: + +```json +{ + "media_type": "image/jpeg", + "width": 1280, + "height": 720, + "encoding": "base64", + "data": "/9j/4AAQSk..." +} +``` + +关闭图片,或因第三方结果未附带帧、坏帧等原因渲染失败时,告警仍会发送且 +`payload.image` 为 `null`;证据图失败不能阻断结构化告警。Base64 比原始 JPEG +额外增加约三分之一体积,平台入口和反向代理必须设置相应的请求体上限。 +同一阈值帧同时触发多条规则或多个 track 时,节点对所有相关告警框取并集并只编码 +一次,再让这些告警共享同一个不可变 JPEG,从而限制瞬时 CPU 与内存开销。 +非有限坐标、反向/退化框、非法置信度或 track ID 的 detection 会在规则计数前被 +忽略并增加 `invalid_detections` 健康计数,防止 `NaN/Inf` 破坏整个 HTTP JSON。 + +### 4.3 cmvr-es gRPC 连接器 + +`cmvr.grpc.camera_rgb_stream@1`: + +- 参数:`endpoint`、必填 `device_id`、可选 `pixel_format`(默认 `BGR8`)、 + `stream_log_interval_s`(默认 30 秒),以及 + `reconnect/reconnect_initial_s/reconnect_max_s/max_reconnect_attempts`; +- 每个首次连接和重连 session 都先调用 unary `CameraService.StartCamera`,复用 endpoint + 的 `timeout_s`,并检查业务反馈 `header.success/error_message`;只有成功后才调用 + `CameraService.GetRGBImageStream`。这两步不能省略:前者打开物理相机,后者在服务端 + 启动编码流; +- 发送一次订阅请求并保持 request side,兼容当前 cmvr-es 行为和未来真正双向协议; +- 把 `color_frame` 转为 `ImageFrame/v1`,保留 codec、关键帧、内参、序号和时间戳; +- 默认在 Start RPC、断流、流 RPC 或业务失败后指数退避重连;每次尝试使用新的 + `session_id`,收到一帧后重置连续失败计数;`max_reconnect_attempts` 省略表示持续尝试; +- INFO 日志覆盖 source configured、StartCamera 请求/成功、stream opening、首帧、周期 + progress、stream close 和 source stop;WARNING/ERROR 覆盖业务反馈错误、断流、重连与 + 重试耗尽。progress 即使尚未收到首帧也会定时输出,并报告帧数、吞吐、关键帧、 + codec、尺寸和最后一帧年龄,但不会输出图像 bytes; +- stop 时主动 cancel stream,并且等待重连期间也能被 shutdown event 立即打断。默认 + 不调用设备级 `StopCamera`,避免中断同一相机的其他客户端; +- 不做 H264 解码,编码包必须交给有状态解码器插件。 + +重连不能修复 cmvr-es 生产端已经丢失的编码包。当前 cmvr-es 服务端使用 `getLatestEncodedFrame` 取得最新数据,负载或时序竞争可能在 edge-ai 看到数据之前跳过 H264/H265 参考包;本地 `video_contiguous` 只保护已经入队的数据,而且 camera Source 的本地 sequence 不能可靠表示这种上游跳包。decoder 遇到 FFmpeg 错误会重置并等待关键帧,但上线前仍应验证服务端提供连续 access unit,或增加原始/JPEG/显式 discontinuity 的 AI 接口。 + +`cmvr.grpc.agv_command_sink@1`: + +- 参数:`endpoint` 和固定、非空的 `device_id`;一个 Sink 只绑定一台设备; +- 输入必须为 `ApprovedRobotCommand/v1`,直接传入原始 `RobotCommand` 会被拒绝; +- 支持 `emergency_stop`、`clear_fault`、`pause_navigation`、`resume_navigation`、`cancel_navigation`、`stop_velocity_control`、`stop_mapping` 和 `set_velocity`; +- `set_velocity` 默认禁用;只有显式设置 `unsafe_allow_unleased_velocity: true`,并在 Sink 再配置完整 `velocity_limits.vx/vy/wz` 后才会发出; +- `set_velocity` 的 `vx/vy/wz` 必须是有限数,并同时通过安全门和 Sink 两层范围校验; +- 同时检查 gRPC 调用异常与响应 `header.success`,并把 RPC timeout 限制在命令剩余 TTL 内; +- 不自动重试运动命令;正常关闭时若曾成功发送速度,会尽力调用 `stopVelocityControl`。 + +下面只展示 actuator 节点的危险参数;该节点仍必须直接连接前述安全门。此开关只应用于架空轮、受控台架等隔离环境: + +```yaml +actuator: + uses: cmvr.grpc.agv_command_sink@1 + with: + endpoint: cmvr_es + device_id: src1100 + unsafe_allow_unleased_velocity: true + velocity_limits: + vx: {min: -0.3, max: 0.3} + vy: {min: -0.2, max: 0.2} + wz: {min: -0.5, max: 0.5} +``` + +`unsafe_allow_unleased_velocity` 必须是 YAML 布尔字面量,字符串 `"true"/"false"` 会被拒绝。该 override 不是 crash-safety:edge-ai 被 `SIGKILL`、崩溃、断电或与 cmvr-es 网络分区时,Python `stop()` 没有机会可靠执行。真实机器人必须由 cmvr-es 服务端持有速度 lease,并在 lease/heartbeat 超时后独立执行清零和停车。 + +### 4.4 平台 HTTP 连接器 + +`platform.http_json_sink@1` 接收任意 schema: + +- 参数:`endpoint`、`path`(默认 `/`)、`max_attempts`(默认 3)、`retry_initial_s`、`retry_max_s` 和 `retry_statuses`; +- POST Envelope 元数据、输入端口、attributes 和 payload; +- bytes 转换为 `{encoding: base64, data: ...}`; +- `EncodedImage` 特判为 `media_type/width/height/encoding/data` 同层的扁平对象,避免 `payload.image.data` 再嵌套一层; +- payload 有非空 `event_id` 时以它作为 `Idempotency-Key`,否则使用 `trace_id`;平台仍必须真正实现按键去重; +- 对连接/超时类错误以及默认 `408/425/429/500/502/503/504` 执行有限指数退避;不在 + allowlist 的状态不会重试;最终失败由 `failure_mode` 决定:`raise` 抛异常, + `log_and_drop` 输出 WARNING、丢弃当前报告并继续; +- 没有持久化 outbox/spool。有限重试只存在于当前进程内,崩溃、断电或重启不会恢复尚未投递的告警。 + +同一个通用 Sink 可以按节点配置不同的 endpoint 和 path。当前检测配置只把 +`DetectionAlert/v1` 发到 `http://127.0.0.1:8081/v1/detection-alerts`。若恢复六类模型, +可把每次推理的 `DetectionResult/v1` 发到独立 endpoint;它与告警复用 Envelope JSON +外层,但 payload 契约不同,且没有 event ID,幂等键会回退为 `trace_id`。Sink 不支持 +通过 YAML 重命名或重排 payload 字段,如果目标平台要求自定义 wire contract,应增加 +平台专用转换节点或 Sink。 + +运行时对未处理异常采用 fail-fast。HTTP Sink 默认 `failure_mode: raise`,最终发送失败 +会终止它所在的 Pipeline;当前 8081 告警 Sink 显式使用 `log_and_drop`,所以平台离线只会 +产生 WARNING 并丢弃对应告警,不会停止相机和检测。该模式不是可靠投递机制。 + +## 5. 插件开发约定 + +### 5.1 创建组件 + +插件工厂固定接收 `node_id: str` 与 `params: Mapping[str, Any]`,应在构造阶段检查无需 I/O 的必填参数。下面展示一个最小 Operator: + +```python +from collections.abc import Mapping +from typing import Any + +from cmvr_edge_ai.contracts import TextEvent +from cmvr_edge_ai.core import Emission, Envelope, Operator + + +class UppercaseOperator(Operator): + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + self._node_id = node_id + self._prefix = str(params.get("prefix", "")) + + async def process( + self, + envelope: Envelope[Any], + input_port: str = "input", + ) -> Emission: + del input_port + event = envelope.payload + if not isinstance(event, TextEvent): + raise TypeError( + f"{self._node_id} expected TextEvent, got {type(event).__name__}" + ) + result = TextEvent( + text=self._prefix + event.text.upper(), + role=event.role, + final=event.final, + ) + return Emission( + "output", + envelope.with_payload( + result, + schema_name="TextEvent", + schema_version=1, + ), + ) +``` + +使用 `with_payload()` 可以保留 trace、session、sequence、采集时间和 deadline,只更新载荷与接收时间。除非开始了一次新的独立请求,不要随意生成新的 `trace_id`。 + +### 5.2 注册 PluginSpec + +```python +from cmvr_edge_ai.plugins import PluginKind, PluginRegistry, PluginSpec + + +def register_plugins(registry: PluginRegistry) -> None: + registry.register( + PluginSpec( + plugin_id="example.text_upper@1", + kind=PluginKind.OPERATOR, + factory=UppercaseOperator, + inputs={"input": "TextEvent/v1"}, + outputs={"output": "TextEvent/v1"}, + description="Uppercase a text event", + tags=frozenset({"domain:dialogue"}), + ) + ) +``` + +规则: + +- ID 必须包含版本,例如 `@1`;不兼容的端口或参数变更发布新版本; +- Source 不能声明 input,Sink 不能声明 output; +- 端口名必须与组件实际发送/接收的端口一致; +- 使用精确 schema;只在真正协议无关的调试/路由节点使用 `*`; +- 执行器 Sink 加 `actuator` 标签,并只声明/接受 `ApprovedRobotCommand/v1`;安全节点加 `safety_gate` 标签并完成 `RobotCommand -> ApprovedRobotCommand` 转换;不要为了通过编译给普通变换节点冒充安全标签;连接器加 `transport:grpc` 等标签; +- 同一个 ID 重复注册会失败。 + +### 5.3 通过 entry point 发布 + +第三方插件包的 `pyproject.toml`: + +```toml +[project] +name = "cmvr-edge-ai-example-plugin" +version = "0.1.0" +dependencies = ["cmvr-edge-ai>=0.1,<0.2"] + +[project.entry-points."cmvr_edge_ai.plugins"] +example = "my_cmvr_plugin.plugins:register_plugins" +``` + +entry point 可以直接导出一个 `PluginSpec`,也可以像示例一样导出接收 registry 的回调。安装包后,默认 registry 会在 CLI 启动时发现它。只安装可信插件:entry point 是 Python 可执行代码,虽然 YAML 本身不能任意导入模块,已安装插件仍拥有当前进程权限。 + +### 5.4 阻塞计算与模型 + +禁止直接在 `process()` 中执行长时间阻塞 I/O 或 Python/模型推理,否则整个 event loop 的 gRPC、HTTP、队列和其他 Pipeline 都会停顿。 + +框架提供 `workers.run_blocking()`。插件在 `setup()` 中取得应用共享的有界线程池,再显式 offload: + +```python +from cmvr_edge_ai.workers import run_blocking + +async def setup(self, context): + self._thread_executor = context.metadata["thread_executor"] + +async def process(self, envelope, input_port="input"): + result = await run_blocking( + self._blocking_infer, + envelope.payload, + executor=self._thread_executor, + ) + return envelope.with_payload( + result, + schema_name="DetectionResult", + schema_version=1, + ) +``` + +线程 offload 适合释放 GIL 的推理库或阻塞 SDK。不要省略 `executor` 后假定 `runtime.thread_workers` 仍然生效;未指定 executor 时会落到宿主 event loop 的默认 executor。 + +纯 Python CPU 密集任务、大模型或需要故障隔离的模型应由插件维护常驻进程/模型 Worker;不要每帧创建进程或重复加载模型。框架提供 `PersistentProcessWorker` 作为小型基础设施:它使用一个常驻子进程、有界请求/结果队列、串行关联和可选超时,并把子进程异常还原为 `RemoteWorkerError`。请求一旦超时或在途取消,Worker 会标记为 `poisoned` 并拒绝后续 submit,避免迟到结果被误配;插件必须 stop 后新建 Worker。插件仍负责在 `setup/start` 创建和启动它、在 `stop` 回收它,并确保 `spawn` 模式下 factory/payload 可序列化。 + +在 v1 配置中,节点仍必须使用 `execution.mode: async`(默认)或 `inline`,且保持默认单并发字段。`thread|process|model_worker` 以及非默认并发字段会在编译期被拒绝。上面的线程/进程 helper 是组件内部显式调用的实现细节,框架不会根据 YAML 自动 offload。插件需要自行限制 in-flight 数量并在 `stop()` 回收 Worker。若跨进程发送大图像,优先传编码数据;确认复制成为瓶颈后再实现固定大小共享内存池,并只通过 IPC 传 slot/shape/dtype/时间戳。 + +### 5.5 开发新协议连接器 + +新增 gRPC、HTTP、UDP 或 QUIC 支持时,应把 wire format 的解析和错误映射放在 `connectors/`/`transports/`,对 DAG 只暴露内部契约: + +```text +wire message -> 校验 -> 内部 payload -> Envelope -> AI Operators +AI result -> typed command/event -> wire message -> external service +``` + +不要让算法插件导入 `*_pb2`。连接器应: + +- 在 `setup` 获取/建立连接,在 `stop` 取消 stream 并释放资源; +- 为每条消息保留设备 ID、序号、采集时间、trace/session; +- 区分 transport failure 与业务响应失败; +- 明确超时、重连、幂等和背压策略; +- 对流式编码数据保持状态,不能把任意 H264 packet 当作独立图片; +- 为音频丢包/重连产生 `discontinuity`,不能静默拼接不连续 PCM; +- 对控制接口使用显式 action 到 RPC 的白名单映射,禁止从配置反射任意方法名。 +- 新增控制 Sink 时只接受 `ApprovedRobotCommand`,并要求安全门成为图上的直接前驱;不要兼容接收原始 `RobotCommand`。 + +gRPC transport 已提供有界的 `BidiRequestStream` 请求迭代器,可供未来真正的双向音频 RPC 使用。它限制为单消费者,正常 close 会排空已接受请求并原子唤醒被背压阻塞的 producer;若 gRPC 放弃迭代器则丢弃无法再发送的请求。它只解决 request side 的背压与关闭,不包含任何音频 proto、重连、响应解析或扬声器策略;这些仍属于具体连接器。 + +## 6. 并发和资源模型 + +当前真实执行关系如下: + +```mermaid +flowchart LR + MAIN["一个 Python 进程"] --> LOOP["一个 asyncio event loop"] + LOOP --> P1["Pipeline A: 每节点一个 task"] + LOOP --> P2["Pipeline B: 每节点一个 task"] + LOOP --> TP["共享有界 ThreadPoolExecutor"] + P1 -. "插件显式 workers.run_blocking" .-> TP + P2 -. "插件显式 workers.run_blocking" .-> TP + P1 -. "插件自行实现" .-> MW["可选常驻进程/Model Worker"] +``` + +多个 Pipeline 共用同一 event loop、gRPC ChannelPool、HttpClientPool 和应用线程池。同名 endpoint 会复用客户端;同一 endpoint ID 如果请求了冲突的 settings 会报错。线程池只通过 `ComponentContext.metadata["thread_executor"]` 提供;`execution.mode: thread` 在 v1 是非法保留值,不会自动调用线程池。 + +共享 gRPC channel 不等于共享流式 RPC 或 Source 实例。两个 Pipeline 各自配置同一 +camera node 时会建立两个订阅并重复解码;同一物理输入需要供多个模型使用时,应优先 +在一个 Pipeline 内从 decoder 的输出端口 fan-out。每个 fan-out 分支仍应使用独立的 +小容量队列,避免慢模型把实时图像积压在内存中。 + +边缘端容量规划建议: + +- 优先减少输入帧率、分辨率和队列容量,不用无界缓存换吞吐; +- 同一 GPU 上尽量复用一个常驻模型实例,避免每 Pipeline 重复加载; +- 编码视频在 decoder 前使用 `video_contiguous`,不能用 latest-frame 丢包;只有 decoder 输出完整图像后,才能在推理前使用 `realtime_latest` 主动丢旧帧;当前运行时不会根据 deadline 自动丢弃; +- 音频需要连续性,不能照搬视频的 `drop_oldest`; +- 平台上报与机器人控制使用不同边,并为非关键遥测选择非阻塞溢出策略; +- 通过实际端到端延迟、queue high watermark、drop counter 和显存峰值决定是否引入进程/共享内存。 + +## 7. 控制安全模型 + +机器人控制链必须保持以下形态: + +```text +AI 结果 -> Policy -> RobotCommand/v1 + -> Safety Gate + -> ApprovedRobotCommand/v1 + -> 类型化执行器 Sink +``` + +这里有四层进程内防线: + +1. 编译器要求 `actuator` 的每个直接前驱都带 `safety_gate` 标签;安全门与 Sink 之间不能插入转换/透传节点,也不能增加旁路输入; +2. 安全门只接收 `RobotCommand/v1`,检查通过后才输出 `ApprovedRobotCommand/v1`; +3. AGV Sink 的端口 schema 和运行时类型检查都只接受 `ApprovedRobotCommand`; +4. AGV Sink 只支持明确列出的 action,不会根据配置字符串反射调用任意 gRPC 方法,也不会自动重试运动命令。 + +`ApprovedRobotCommand` 是进程内类型边界,不是加密签名。只有可信插件可以被安装并赋予 `safety_gate` 标签;恶意或被篡改的 Python 插件仍拥有当前进程权限,因此插件供应链、包版本锁定和文件完整性属于安全模型的一部分。 + +当前安全门已经检查: + +- payload 必须是 `RobotCommand`; +- Envelope deadline 和 `RobotCommand.valid_until_ns`; +- action 白名单; +- 可选 device 白名单; +- 为 action 配置的每个参数必须存在、是有限数并位于 `[min, max]`; +- `set_velocity` 必须为 `vx/vy/wz` 配置完整范围;`require_limits_for` 可把同一规则扩展到其他 action; +- 默认按 `(device, action)` 拒绝 sequence 重放和乱序; +- 同一 `(device, action)` 的最小发送间隔。 + +投入真实机器人前仍必须由机器人型号相关 Policy/Safety 插件补齐: + +- 除已配置参数范围外的速度、位置、加速度和 jerk 联合约束; +- 当前模式、故障、急停、定位质量、电量及障碍物状态; +- command ID 去重和多来源优先级仲裁; +- 急停独立高优先级路径和权限控制; +- 审计日志、认证、TLS 和密钥管理。 + +最重要的边界位于进程外:持续速度命令必须由 cmvr-es 服务端发放短期 lease,并由独立 deadman 在续租/heartbeat 超时后清零速度。当前 Sink 会在调用前检查 TTL,并把客户端 RPC timeout 截断到剩余 TTL,但当前 cmvr-es 请求没有承载 `valid_until/command_id/sequence`,服务端无法据此拒绝网络中迟到的命令。`unsafe_allow_unleased_velocity`、客户端 TTL、安全门以及 Sink `stop()` 都不能覆盖进程崩溃、`SIGKILL`、断电或网络分区。cmvr-es 未实现 server-side lease/deadman 前,`set_velocity` 只允许在隔离测试环境使用,禁止用于真实机器人持续运动。 + +运动命令默认不重试。只有外部 API 提供明确幂等契约,并完成 command ID 去重后,才能为特定 action 增加有界重试。 + +## 8. 运维流程 + +推荐发布/启动顺序: + +1. 安装固定版本的核心包和可信插件包; +2. 如果使用 cmvr-es gRPC 连接器,安装匹配 cmvr-es proto 版本的 `cmvr-api` wheel,或运行 stub 生成脚本; +3. 在部署 YAML 中配置 endpoint、证书引用、模型路径和推理设备;凭据应通过部署平台的 secret 机制注入; +4. 执行 `cmvr-edge-ai plugins` 和 `cmvr-edge-ai models`,保存实际插件、模型及标签清单; +5. 执行 `cmvr-edge-ai validate -c ...`; +6. 确认 cmvr-es 设备启用、设备 ID 正确、平台接口可达; +7. 先运行回放/smoke,再连接真实传感器; +8. 控制链先使用空载/限速/人工急停条件验证; +9. 使用 SIGTERM 停止并给 `shutdown_timeout_s` 留出排空时间。 + +示例命令: + +```bash +uv run --no-sync cmvr-edge-ai models +uv run --no-sync cmvr-edge-ai validate -c detect_server/pipeline.yaml --pipeline detection +uv run --no-sync cmvr-edge-ai run -c detect_server/pipeline.yaml \ + --pipeline detection \ + --log-level INFO \ + --log-format json +``` + +当前日志可输出文本或单行 JSON。日志中不要写入音频原始数据、图像 base64、认证 metadata 或用户隐私内容;生产插件应只记录 trace ID、schema、耗时、尺寸、丢弃计数和经过脱敏的错误信息。 + +## 9. 当前限制与演进顺序 + +### 9.1 当前限制 + +- cmvr-es 音频双向流 proto 尚未实现;框架只有 `AudioChunk` 契约和对话占位配置; +- 检测链路已经提供 PyAV H264/H265 解码、双 PPE YOLO 注册/推理、单次解码 fan-out 和重复触发规则;VAD、ASR、LLM、TTS 仍需插件提供; +- UDP/QUIC 只有扩展目录,无 transport/connector; +- execution v1 只支持 `async/inline` 默认单并发;其他 mode 和非默认并发字段会在编译期拒绝,线程/进程/模型 offload 必须由插件显式实现; +- 非空 resources、非 `normal` priority 和显式保留 runtime 字段都会被拒绝;`health_bind` 未启动服务,队列指标也未导出; +- 无热更新、overlay、通用节点级 supervisor/restart、持久化 outbox/spool;相机和 HTTP 的重连/重试是 connector 内部的局部策略; +- 无自动 deadline 丢弃、`max_age_ms`、`put_timeout_ms`; +- 无共享内存池;跨进程 Worker 由插件负责; +- 配置只支持单个 YAML 加环境变量,不支持 include/merge; +- 当前 cmvr-es 相机成功响应通常未填写 `header.timestamp`,因此连接器的 `captured_at_ns` 可能为 `null`;精确采集时延需要后续在帧协议中增加设备采集时钟,不能用客户端接收时间冒充; +- cmvr-es 当前通过 `getLatestEncodedFrame` 取得最新编码数据,可能在 edge-ai 收到之前跳过 inter-frame 参考包;本地连续队列和重连无法补回上游丢失内容,必须验证/改造服务端流语义; +- HTTP Sink 对告警使用 `event_id`、对普通逐帧结果回退使用 `trace_id` 作为幂等键,并 + 提供有限退避和 `raise/log_and_drop` 终态策略,但没有持久投递;`log_and_drop` 的 + WARNING 即表示该报告已丢失,进程崩溃或断电也可能丢失内存中的输出; +- 内置 YOLO adapter 不产生 `track_id`,所以 `scope: track` 需要外部 tracker 及人员/PPE 关联节点; +- PPE `.pt` 只能来自可信制品源;商业部署还需核对权重说明、Ultralytics runtime/checkpoint 的 AGPL/商业许可,以及训练数据和权重分发权利; +- AGV `set_velocity` 默认禁用;unsafe override 和正常 shutdown stop 都不具备崩溃安全性,不能替代 cmvr-es server-side lease/deadman、机器人本体限位、急停和功能安全系统。 + +### 9.2 推荐演进顺序 + +1. **真实流与模型验收**:用录制数据和目标边缘设备验证 cmvr-es 编码连续性、PyAV 长时间恢复、PPE 精度/FPS/内存/显存和端到端告警; +2. **可靠告警投递**:在现有 event ID 和有限重试之上增加有界持久 outbox、确认、恢复发送和容量/保留策略; +3. **同人违规语义**:增加 tracker 与 Worker/PPE 空间关联,验证 ID switch 后再启用 `scope: track`; +4. **补齐可观测性**:导出 health、队列水位/丢弃、重连/重试、节点延迟和模型资源指标; +5. **控制安全闭环**:先在 cmvr-es 实现速度 lease/server-side deadman,再补齐设备状态输入、机器人型号限值、优先级与审计,然后才启用真实 AGV/机械臂动作; +6. **接入音频双向流**:proto 落地后实现麦克风 Source/扬声器 Sink,严格处理 chunk 顺序、背压和 discontinuity; +7. **按测量结果优化并发**:先使用现有显式 thread offload,再按测量结果增加常驻 model/process worker;确认复制瓶颈后才加入共享内存; +8. **扩展协议**:用相同内部契约实现 QUIC/UDP connector,不修改算法插件。 + +每一步都应先通过 `validate`、smoke、录制数据 replay 和资源峰值检查,再接入真实设备。 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7f2c28d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,91 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "cmvr-edge-ai" +version = "0.1.0" +description = "Configuration-driven AI pipeline runtime for CMVR robot edge devices" +readme = "README.md" +requires-python = ">=3.10,<3.13" +dependencies = [ + "pydantic>=2.8,<3", + "PyYAML>=6,<7", +] + +[project.optional-dependencies] +grpc = [ + "grpcio>=1.76,<2", + "protobuf>=6.31.1,<7", +] +http = [ + "httpx>=0.27,<1", +] +video = [ + "av>=12,<17", +] +image = [ + "Pillow>=10,<13", +] +yolo = [ + "numpy>=1.24,<3", + "ultralytics==8.4.31", +] +yolo-cpu = [ + "numpy>=1.24,<3", + "torch==2.7.0", + "torchvision==0.22.0", + "ultralytics==8.4.31", +] + +[dependency-groups] +test = [ + "pytest>=7.4,<9", + "pytest-asyncio>=0.23,<1", +] +codegen = [ + "grpcio-tools>=1.76,<2", +] +dev = [ + { include-group = "test" }, + { include-group = "codegen" }, +] + +[project.scripts] +cmvr-edge-ai = "cmvr_edge_ai.cli:main" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.uv] +required-version = ">=0.11.16" +default-groups = [] +conflicts = [ + [ + { extra = "yolo" }, + { extra = "yolo-cpu" }, + ], +] +required-environments = [ + "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10'", +] + +[tool.uv.sources] +torch = [ + { index = "pytorch-cpu", extra = "yolo-cpu", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'" }, +] +torchvision = [ + { index = "pytorch-cpu", extra = "yolo-cpu", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'" }, +] + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-q" diff --git a/scripts/__pycache__/generate_cmvr_stubs.cpython-312.pyc b/scripts/__pycache__/generate_cmvr_stubs.cpython-312.pyc new file mode 100644 index 0000000..79eabaa Binary files /dev/null and b/scripts/__pycache__/generate_cmvr_stubs.cpython-312.pyc differ diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..960aee7 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" + +profile="detection-cpu" +cmvr_es_root="${PROJECT_ROOT}/../cmvr-es" +skip_codegen=false +skip_check=false + +usage() { + cat <<'EOF' +Usage: bash scripts/bootstrap.sh [options] + +Create the locked uv environment, optionally generate cmvr-es Python bindings, +and run basic health checks. + +Options: + --profile PROFILE core, detection-cpu (default), or dev + --cmvr-es-root PATH cmvr-es checkout (default: sibling ../cmvr-es) + --skip-codegen do not generate cmvr-es protobuf/gRPC bindings + --skip-check install only; skip validation and tests + -h, --help show this help + +Profiles: + core framework and simulated talk/smoke pipeline only + detection-cpu gRPC + HTTP + PyAV + locked CPU YOLO runtime + dev detection-cpu plus tests and portable protobuf codegen tools +EOF +} + +while (($#)); do + case "$1" in + --profile) + if (($# < 2)); then + echo "error: --profile requires a value" >&2 + exit 2 + fi + profile="$2" + shift 2 + ;; + --cmvr-es-root) + if (($# < 2)); then + echo "error: --cmvr-es-root requires a path" >&2 + exit 2 + fi + cmvr_es_root="$2" + shift 2 + ;; + --skip-codegen) + skip_codegen=true + shift + ;; + --skip-check) + skip_check=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "error: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +case "${profile}" in + core) + sync_args=(--locked --no-default-groups) + needs_cmvr_bindings=false + ;; + detection-cpu) + sync_args=( + --locked + --no-default-groups + --extra grpc + --extra http + --extra video + --extra image + --extra yolo-cpu + ) + needs_cmvr_bindings=true + ;; + dev) + sync_args=( + --locked + --no-default-groups + --group dev + --extra grpc + --extra http + --extra video + --extra image + --extra yolo-cpu + ) + needs_cmvr_bindings=true + ;; + *) + echo "error: unsupported profile '${profile}'" >&2 + echo "expected one of: core, detection-cpu, dev" >&2 + exit 2 + ;; +esac + +if ! command -v uv >/dev/null 2>&1; then + echo "error: uv is not installed or not available on PATH" >&2 + echo "see https://docs.astral.sh/uv/getting-started/installation/" >&2 + exit 1 +fi + +cd "${PROJECT_ROOT}" +echo "==> project: ${PROJECT_ROOT}" +echo "==> profile: ${profile}" + +if [[ "${needs_cmvr_bindings}" == true && "${skip_codegen}" == false ]]; then + if [[ ! -d "${cmvr_es_root}/protos/cmvr" ]]; then + echo "error: cmvr-es protos were not found under: ${cmvr_es_root}" >&2 + echo "pass --cmvr-es-root PATH or use --skip-codegen with existing bindings" >&2 + exit 1 + fi + + echo "==> installing the locked protobuf codegen environment" + uv sync --locked --only-group codegen + echo "==> generating cmvr-es Python bindings from ${cmvr_es_root}" + .venv/bin/python scripts/generate_cmvr_stubs.py \ + --cmvr-es-root "${cmvr_es_root}" \ + --portable +fi + +echo "==> installing the locked ${profile} environment" +uv sync "${sync_args[@]}" + +if [[ "${skip_check}" == true ]]; then + echo "==> installation complete (checks skipped)" + exit 0 +fi + +echo "==> validating the smoke pipeline" +.venv/bin/cmvr-edge-ai validate --config configs/smoke.yaml + +if [[ "${needs_cmvr_bindings}" == true ]]; then + echo "==> checking detection runtime imports" + .venv/bin/python -c \ + "import av, grpc, httpx, PIL, torch, ultralytics; import cmvr.api.camera_service_pb2_grpc; print(f'torch={torch.__version__} cuda={torch.cuda.is_available()} ultralytics={ultralytics.__version__} pillow={PIL.__version__}')" + echo "==> validating the PPE detection pipeline" + .venv/bin/cmvr-edge-ai validate \ + --config detect_server/pipeline.yaml \ + --pipeline detection + .venv/bin/cmvr-edge-ai models +fi + +if [[ "${profile}" == dev ]]; then + echo "==> running tests" + .venv/bin/pytest +fi + +echo "==> bootstrap complete" +echo "run commands with: uv run --no-sync cmvr-edge-ai ..." diff --git a/scripts/generate_cmvr_stubs.py b/scripts/generate_cmvr_stubs.py new file mode 100644 index 0000000..927f609 --- /dev/null +++ b/scripts/generate_cmvr_stubs.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Generate Python protobuf/gRPC bindings from the cmvr-es source tree.""" + +from __future__ import annotations + +import argparse +import platform +import subprocess +import sys +from pathlib import Path + + +DEFAULT_CMVR_ES_ROOT = Path("/home/xtkuang/Projects/cmvr/cmvr-es") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--cmvr-es-root", + type=Path, + default=DEFAULT_CMVR_ES_ROOT, + help="cmvr-es checkout containing protos/ and output/bin/", + ) + parser.add_argument( + "--output", + type=Path, + default=Path("src"), + help="output root; generated imports require a top-level cmvr package", + ) + parser.add_argument( + "--portable", + action="store_true", + help="use python -m grpc_tools.protoc instead of cmvr-es bundled tools", + ) + return parser.parse_args() + + +def build_command(args: argparse.Namespace, proto_files: list[Path]) -> list[str]: + proto_root = args.cmvr_es_root.resolve() / "protos" + output = args.output.resolve() + relative_files = [str(path.relative_to(proto_root)) for path in proto_files] + + if args.portable: + try: + import grpc_tools + except ImportError as exc: + raise RuntimeError( + "portable generation requires grpcio-tools; install the dev extra" + ) from exc + well_known = Path(grpc_tools.__file__).resolve().parent / "_proto" + return [ + sys.executable, + "-m", + "grpc_tools.protoc", + f"-I{proto_root}", + f"-I{well_known}", + f"--python_out={output}", + f"--pyi_out={output}", + f"--grpc_python_out={output}", + *relative_files, + ] + + protoc = args.cmvr_es_root.resolve() / "output/bin/protoc" + grpc_plugin = args.cmvr_es_root.resolve() / "output/bin/grpc_python_plugin" + if not protoc.is_file() or not grpc_plugin.is_file(): + raise FileNotFoundError( + "cmvr-es protobuf tools are missing; build cmvr-es first or pass --portable" + ) + well_known = _bundled_well_known_proto_root(args.cmvr_es_root.resolve()) + return [ + str(protoc), + f"-I{proto_root}", + f"-I{well_known}", + f"--python_out={output}", + f"--pyi_out={output}", + f"--grpc_python_out={output}", + f"--plugin=protoc-gen-grpc_python={grpc_plugin}", + *relative_files, + ] + + +def _bundled_well_known_proto_root(cmvr_es_root: Path) -> Path: + architecture = ( + "arm" if platform.machine().lower() in {"aarch64", "arm64"} else "x86" + ) + candidates = ( + cmvr_es_root / "dependency" / architecture / "third_party/grpc/v1.76.0/include", + cmvr_es_root / "dependency/x86/third_party/grpc/v1.76.0/include", + cmvr_es_root / "dependency/arm/third_party/grpc/v1.76.0/include", + ) + for candidate in candidates: + if (candidate / "google/protobuf/timestamp.proto").is_file(): + return candidate + raise FileNotFoundError( + "google/protobuf/timestamp.proto was not found in the cmvr-es toolchain" + ) + + +def main() -> int: + args = parse_args() + proto_root = args.cmvr_es_root.resolve() / "protos" + if not proto_root.is_dir(): + raise FileNotFoundError(f"proto root does not exist: {proto_root}") + + proto_files = sorted((proto_root / "cmvr").rglob("*.proto")) + if not proto_files: + raise FileNotFoundError(f"no cmvr proto files found under {proto_root}") + + args.output.mkdir(parents=True, exist_ok=True) + command = build_command(args, proto_files) + subprocess.run(command, cwd=proto_root, check=True) + print(f"generated {len(proto_files)} proto files into {args.output.resolve()}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/cmvr/api/__pycache__/agv_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/agv_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..c143299 Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/agv_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/agv_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..1cc0461 Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..c085bd9 Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..b040e24 Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/agv_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/agv_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..ac621b4 Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/agv_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/agv_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..ecdfaef Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..c79d7f6 Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..2a0e17c Binary files /dev/null and b/src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/arm_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..bd3acf9 Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/arm_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..10f8649 Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..d0522d2 Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..a8cc432 Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/arm_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..b89e720 Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/arm_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..ec0b7a7 Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..67bbc9a Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..b194f8a Binary files /dev/null and b/src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/biohead_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..d2dd5e7 Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/biohead_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..29d2a60 Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..5e9fc2b Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..df3c30c Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/biohead_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..259d4d3 Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/biohead_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..5b26640 Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..42cc923 Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..58302db Binary files /dev/null and b/src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/camera_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..c53da81 Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/camera_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..b2b22bc Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..a029067 Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..9ad9602 Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/camera_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..3deb11d Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/camera_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..03aec9d Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..3215599 Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..6cd552b Binary files /dev/null and b/src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/common_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/common_pb2.cpython-310.pyc new file mode 100644 index 0000000..98a58ab Binary files /dev/null and b/src/cmvr/api/__pycache__/common_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/common_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/common_pb2.cpython-312.pyc new file mode 100644 index 0000000..c515127 Binary files /dev/null and b/src/cmvr/api/__pycache__/common_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/common_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/common_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..279ee94 Binary files /dev/null and b/src/cmvr/api/__pycache__/common_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/common_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/common_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..4acbbd1 Binary files /dev/null and b/src/cmvr/api/__pycache__/common_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..255cc73 Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..18085db Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..778f4cc Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..4d21ba0 Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..f07eeea Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..0cf115b Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..03d62c7 Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..a07fe54 Binary files /dev/null and b/src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/hlc_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..88d2571 Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/hlc_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..a0161e1 Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..9ceacee Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..e7fbb50 Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/hlc_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..b060da1 Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/hlc_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..6312d2b Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..1578e73 Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..5013945 Binary files /dev/null and b/src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/microphone_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..ddc10a1 Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/microphone_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..82612d6 Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..33af068 Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..4b9d77d Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/microphone_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..84dd7a7 Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/microphone_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..5bc63ba Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..684f6cc Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..7e39778 Binary files /dev/null and b/src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/speaker_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..ab48a59 Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/speaker_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..1673f4c Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..d9b4e34 Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..c5fd106 Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/speaker_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..c742f9a Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/speaker_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..ff7ceaa Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..8378014 Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..fff7f40 Binary files /dev/null and b/src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/system_command_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/system_command_pb2.cpython-310.pyc new file mode 100644 index 0000000..8e0a20e Binary files /dev/null and b/src/cmvr/api/__pycache__/system_command_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/system_command_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/system_command_pb2.cpython-312.pyc new file mode 100644 index 0000000..0bd9c0b Binary files /dev/null and b/src/cmvr/api/__pycache__/system_command_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..6884518 Binary files /dev/null and b/src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..dd69cb7 Binary files /dev/null and b/src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/system_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/system_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..a4800f5 Binary files /dev/null and b/src/cmvr/api/__pycache__/system_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/system_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/system_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..07abe3f Binary files /dev/null and b/src/cmvr/api/__pycache__/system_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..d9ed722 Binary files /dev/null and b/src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..8416ecb Binary files /dev/null and b/src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/test_service_pb2.cpython-310.pyc b/src/cmvr/api/__pycache__/test_service_pb2.cpython-310.pyc new file mode 100644 index 0000000..ef9063b Binary files /dev/null and b/src/cmvr/api/__pycache__/test_service_pb2.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/test_service_pb2.cpython-312.pyc b/src/cmvr/api/__pycache__/test_service_pb2.cpython-312.pyc new file mode 100644 index 0000000..6a6acdd Binary files /dev/null and b/src/cmvr/api/__pycache__/test_service_pb2.cpython-312.pyc differ diff --git a/src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-310.pyc b/src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..b21212d Binary files /dev/null and b/src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-312.pyc b/src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..e97eae9 Binary files /dev/null and b/src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/api/agv_command_pb2.py b/src/cmvr/api/agv_command_pb2.py new file mode 100644 index 0000000..59a9584 --- /dev/null +++ b/src/cmvr/api/agv_command_pb2.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/agv_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/agv_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 +from cmvr.msgs import agv_pb2 as cmvr_dot_msgs_dot_agv__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/api/agv_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\x1a\x13\x63mvr/msgs/agv.proto\"\xbd\x01\n\x16\x41gvRuntimeStateCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1ag\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12)\n\x05state\x18\x02 \x01(\x0b\x32\x1a.cmvr.msgs.AgvRuntimeState\"\xc6\x01\n\x1a\x41gvNavigationStatusCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1al\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12.\n\x06status\x18\x02 \x01(\x0b\x32\x1e.cmvr.msgs.AgvNavigationStatus\"\x9c\x02\n\x18\x41gvNavigateToPoseCommand\x1a\xc1\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\"\n\x04pose\x18\x02 \x01(\x0b\x32\x14.cmvr.msgs.AgvPose2d\x12,\n\x07options\x18\x03 \x01(\x0b\x32\x1b.cmvr.msgs.AgvMotionOptions\x12\x33\n\x0e\x61\x64\x61pter_params\x18\x04 \x01(\x0b\x32\x1b.cmvr.msgs.AgvAdapterParams\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8f\x02\n\x1b\x41gvNavigateToStationCommand\x1a\xb1\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\nstation_id\x18\x02 \x01(\t\x12,\n\x07options\x18\x03 \x01(\x0b\x32\x1b.cmvr.msgs.AgvMotionOptions\x12\x33\n\x0e\x61\x64\x61pter_params\x18\x04 \x01(\x0b\x32\x1b.cmvr.msgs.AgvAdapterParams\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xb9\x01\n\x14\x41gvFollowPathCommand\x1a\x63\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\'\n\x04path\x18\x02 \x03(\x0b\x32\x19.cmvr.msgs.AgvPathSegment\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xbb\x01\n\x15\x41gvSetVelocityCommand\x1a\x64\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12(\n\x08velocity\x18\x02 \x01(\x0b\x32\x16.cmvr.msgs.AgvVelocity\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x9c\x01\n\x12\x41gvListMapsCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1aJ\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x0c\n\x04maps\x18\x02 \x03(\t\"\xbb\x01\n\x16\x41gvListStationsCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x65\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\'\n\x08stations\x18\x02 \x03(\x0b\x32\x15.cmvr.msgs.AgvStation\"\xbd\x01\n\rAgvMapCommand\x1a]\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x10\n\x08map_name\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\t\x1aM\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\"\xfb\x01\n\x16\x41gvStartMappingCommand\x1a\x8e\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12-\n\tdimension\x18\x02 \x01(\x0e\x32\x1a.cmvr.msgs.AgvMapDimension\x12\x10\n\x08map_name\x18\x03 \x01(\t\x12\x11\n\treal_time\x18\x04 \x01(\x08\x1aP\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x12\n\nsession_id\x18\x02 \x01(\t\"\xd7\x02\n\x13\x41gvMapStreamCommand\x1a\xd1\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12-\n\tdimension\x18\x02 \x01(\x0e\x32\x1a.cmvr.msgs.AgvMapDimension\x12\x10\n\x08map_name\x18\x03 \x01(\t\x12\x14\n\x0cresume_token\x18\x04 \x01(\t\x12\x10\n\x08snapshot\x18\x05 \x01(\x08\x12\x13\n\x0bincremental\x18\x06 \x01(\x08\x12\x17\n\x0fmax_chunk_bytes\x18\x07 \x01(\x05\x1al\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12.\n\x06update\x18\x02 \x01(\x0b\x32\x1e.cmvr.msgs.AgvUnifiedMapUpdateb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.agv_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_AGVRUNTIMESTATECOMMAND']._serialized_start=85 + _globals['_AGVRUNTIMESTATECOMMAND']._serialized_end=274 + _globals['_AGVRUNTIMESTATECOMMAND_REQUEST']._serialized_start=111 + _globals['_AGVRUNTIMESTATECOMMAND_REQUEST']._serialized_end=169 + _globals['_AGVRUNTIMESTATECOMMAND_FEEDBACK']._serialized_start=171 + _globals['_AGVRUNTIMESTATECOMMAND_FEEDBACK']._serialized_end=274 + _globals['_AGVNAVIGATIONSTATUSCOMMAND']._serialized_start=277 + _globals['_AGVNAVIGATIONSTATUSCOMMAND']._serialized_end=475 + _globals['_AGVNAVIGATIONSTATUSCOMMAND_REQUEST']._serialized_start=111 + _globals['_AGVNAVIGATIONSTATUSCOMMAND_REQUEST']._serialized_end=169 + _globals['_AGVNAVIGATIONSTATUSCOMMAND_FEEDBACK']._serialized_start=367 + _globals['_AGVNAVIGATIONSTATUSCOMMAND_FEEDBACK']._serialized_end=475 + _globals['_AGVNAVIGATETOPOSECOMMAND']._serialized_start=478 + _globals['_AGVNAVIGATETOPOSECOMMAND']._serialized_end=762 + _globals['_AGVNAVIGATETOPOSECOMMAND_REQUEST']._serialized_start=507 + _globals['_AGVNAVIGATETOPOSECOMMAND_REQUEST']._serialized_end=700 + _globals['_AGVNAVIGATETOPOSECOMMAND_FEEDBACK']._serialized_start=171 + _globals['_AGVNAVIGATETOPOSECOMMAND_FEEDBACK']._serialized_end=231 + _globals['_AGVNAVIGATETOSTATIONCOMMAND']._serialized_start=765 + _globals['_AGVNAVIGATETOSTATIONCOMMAND']._serialized_end=1036 + _globals['_AGVNAVIGATETOSTATIONCOMMAND_REQUEST']._serialized_start=797 + _globals['_AGVNAVIGATETOSTATIONCOMMAND_REQUEST']._serialized_end=974 + _globals['_AGVNAVIGATETOSTATIONCOMMAND_FEEDBACK']._serialized_start=171 + _globals['_AGVNAVIGATETOSTATIONCOMMAND_FEEDBACK']._serialized_end=231 + _globals['_AGVFOLLOWPATHCOMMAND']._serialized_start=1039 + _globals['_AGVFOLLOWPATHCOMMAND']._serialized_end=1224 + _globals['_AGVFOLLOWPATHCOMMAND_REQUEST']._serialized_start=1063 + _globals['_AGVFOLLOWPATHCOMMAND_REQUEST']._serialized_end=1162 + _globals['_AGVFOLLOWPATHCOMMAND_FEEDBACK']._serialized_start=171 + _globals['_AGVFOLLOWPATHCOMMAND_FEEDBACK']._serialized_end=231 + _globals['_AGVSETVELOCITYCOMMAND']._serialized_start=1227 + _globals['_AGVSETVELOCITYCOMMAND']._serialized_end=1414 + _globals['_AGVSETVELOCITYCOMMAND_REQUEST']._serialized_start=1252 + _globals['_AGVSETVELOCITYCOMMAND_REQUEST']._serialized_end=1352 + _globals['_AGVSETVELOCITYCOMMAND_FEEDBACK']._serialized_start=171 + _globals['_AGVSETVELOCITYCOMMAND_FEEDBACK']._serialized_end=231 + _globals['_AGVLISTMAPSCOMMAND']._serialized_start=1417 + _globals['_AGVLISTMAPSCOMMAND']._serialized_end=1573 + _globals['_AGVLISTMAPSCOMMAND_REQUEST']._serialized_start=111 + _globals['_AGVLISTMAPSCOMMAND_REQUEST']._serialized_end=169 + _globals['_AGVLISTMAPSCOMMAND_FEEDBACK']._serialized_start=1499 + _globals['_AGVLISTMAPSCOMMAND_FEEDBACK']._serialized_end=1573 + _globals['_AGVLISTSTATIONSCOMMAND']._serialized_start=1576 + _globals['_AGVLISTSTATIONSCOMMAND']._serialized_end=1763 + _globals['_AGVLISTSTATIONSCOMMAND_REQUEST']._serialized_start=111 + _globals['_AGVLISTSTATIONSCOMMAND_REQUEST']._serialized_end=169 + _globals['_AGVLISTSTATIONSCOMMAND_FEEDBACK']._serialized_start=1662 + _globals['_AGVLISTSTATIONSCOMMAND_FEEDBACK']._serialized_end=1763 + _globals['_AGVMAPCOMMAND']._serialized_start=1766 + _globals['_AGVMAPCOMMAND']._serialized_end=1955 + _globals['_AGVMAPCOMMAND_REQUEST']._serialized_start=1783 + _globals['_AGVMAPCOMMAND_REQUEST']._serialized_end=1876 + _globals['_AGVMAPCOMMAND_FEEDBACK']._serialized_start=1878 + _globals['_AGVMAPCOMMAND_FEEDBACK']._serialized_end=1955 + _globals['_AGVSTARTMAPPINGCOMMAND']._serialized_start=1958 + _globals['_AGVSTARTMAPPINGCOMMAND']._serialized_end=2209 + _globals['_AGVSTARTMAPPINGCOMMAND_REQUEST']._serialized_start=1985 + _globals['_AGVSTARTMAPPINGCOMMAND_REQUEST']._serialized_end=2127 + _globals['_AGVSTARTMAPPINGCOMMAND_FEEDBACK']._serialized_start=2129 + _globals['_AGVSTARTMAPPINGCOMMAND_FEEDBACK']._serialized_end=2209 + _globals['_AGVMAPSTREAMCOMMAND']._serialized_start=2212 + _globals['_AGVMAPSTREAMCOMMAND']._serialized_end=2555 + _globals['_AGVMAPSTREAMCOMMAND_REQUEST']._serialized_start=2236 + _globals['_AGVMAPSTREAMCOMMAND_REQUEST']._serialized_end=2445 + _globals['_AGVMAPSTREAMCOMMAND_FEEDBACK']._serialized_start=2447 + _globals['_AGVMAPSTREAMCOMMAND_FEEDBACK']._serialized_end=2555 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/agv_command_pb2.pyi b/src/cmvr/api/agv_command_pb2.pyi new file mode 100644 index 0000000..6e3bff9 --- /dev/null +++ b/src/cmvr/api/agv_command_pb2.pyi @@ -0,0 +1,215 @@ +from cmvr.api import common_pb2 as _common_pb2 +from cmvr.msgs import agv_pb2 as _agv_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class AgvRuntimeStateCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "state") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + state: _agv_pb2.AgvRuntimeState + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., state: _Optional[_Union[_agv_pb2.AgvRuntimeState, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvNavigationStatusCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "status") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + status: _agv_pb2.AgvNavigationStatus + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., status: _Optional[_Union[_agv_pb2.AgvNavigationStatus, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvNavigateToPoseCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "pose", "options", "adapter_params") + HEADER_FIELD_NUMBER: _ClassVar[int] + POSE_FIELD_NUMBER: _ClassVar[int] + OPTIONS_FIELD_NUMBER: _ClassVar[int] + ADAPTER_PARAMS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + pose: _agv_pb2.AgvPose2d + options: _agv_pb2.AgvMotionOptions + adapter_params: _agv_pb2.AgvAdapterParams + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., pose: _Optional[_Union[_agv_pb2.AgvPose2d, _Mapping]] = ..., options: _Optional[_Union[_agv_pb2.AgvMotionOptions, _Mapping]] = ..., adapter_params: _Optional[_Union[_agv_pb2.AgvAdapterParams, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvNavigateToStationCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "station_id", "options", "adapter_params") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATION_ID_FIELD_NUMBER: _ClassVar[int] + OPTIONS_FIELD_NUMBER: _ClassVar[int] + ADAPTER_PARAMS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + station_id: str + options: _agv_pb2.AgvMotionOptions + adapter_params: _agv_pb2.AgvAdapterParams + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., station_id: _Optional[str] = ..., options: _Optional[_Union[_agv_pb2.AgvMotionOptions, _Mapping]] = ..., adapter_params: _Optional[_Union[_agv_pb2.AgvAdapterParams, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvFollowPathCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "path") + HEADER_FIELD_NUMBER: _ClassVar[int] + PATH_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + path: _containers.RepeatedCompositeFieldContainer[_agv_pb2.AgvPathSegment] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., path: _Optional[_Iterable[_Union[_agv_pb2.AgvPathSegment, _Mapping]]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvSetVelocityCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "velocity") + HEADER_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + velocity: _agv_pb2.AgvVelocity + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., velocity: _Optional[_Union[_agv_pb2.AgvVelocity, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvListMapsCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "maps") + HEADER_FIELD_NUMBER: _ClassVar[int] + MAPS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + maps: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., maps: _Optional[_Iterable[str]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvListStationsCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "stations") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATIONS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + stations: _containers.RepeatedCompositeFieldContainer[_agv_pb2.AgvStation] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., stations: _Optional[_Iterable[_Union[_agv_pb2.AgvStation, _Mapping]]] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvMapCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "map_name", "content") + HEADER_FIELD_NUMBER: _ClassVar[int] + MAP_NAME_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + map_name: str + content: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., map_name: _Optional[str] = ..., content: _Optional[str] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "content") + HEADER_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + content: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., content: _Optional[str] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvStartMappingCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "dimension", "map_name", "real_time") + HEADER_FIELD_NUMBER: _ClassVar[int] + DIMENSION_FIELD_NUMBER: _ClassVar[int] + MAP_NAME_FIELD_NUMBER: _ClassVar[int] + REAL_TIME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + dimension: _agv_pb2.AgvMapDimension + map_name: str + real_time: bool + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., dimension: _Optional[_Union[_agv_pb2.AgvMapDimension, str]] = ..., map_name: _Optional[str] = ..., real_time: _Optional[bool] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "session_id") + HEADER_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + session_id: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., session_id: _Optional[str] = ...) -> None: ... + def __init__(self) -> None: ... + +class AgvMapStreamCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "dimension", "map_name", "resume_token", "snapshot", "incremental", "max_chunk_bytes") + HEADER_FIELD_NUMBER: _ClassVar[int] + DIMENSION_FIELD_NUMBER: _ClassVar[int] + MAP_NAME_FIELD_NUMBER: _ClassVar[int] + RESUME_TOKEN_FIELD_NUMBER: _ClassVar[int] + SNAPSHOT_FIELD_NUMBER: _ClassVar[int] + INCREMENTAL_FIELD_NUMBER: _ClassVar[int] + MAX_CHUNK_BYTES_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + dimension: _agv_pb2.AgvMapDimension + map_name: str + resume_token: str + snapshot: bool + incremental: bool + max_chunk_bytes: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., dimension: _Optional[_Union[_agv_pb2.AgvMapDimension, str]] = ..., map_name: _Optional[str] = ..., resume_token: _Optional[str] = ..., snapshot: _Optional[bool] = ..., incremental: _Optional[bool] = ..., max_chunk_bytes: _Optional[int] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "update") + HEADER_FIELD_NUMBER: _ClassVar[int] + UPDATE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + update: _agv_pb2.AgvUnifiedMapUpdate + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., update: _Optional[_Union[_agv_pb2.AgvUnifiedMapUpdate, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/agv_command_pb2_grpc.py b/src/cmvr/api/agv_command_pb2_grpc.py new file mode 100644 index 0000000..80bbe76 --- /dev/null +++ b/src/cmvr/api/agv_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/agv_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/agv_service_pb2.py b/src/cmvr/api/agv_service_pb2.py new file mode 100644 index 0000000..91d1339 --- /dev/null +++ b/src/cmvr/api/agv_service_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/agv_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/agv_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 +from cmvr.api import agv_command_pb2 as cmvr_dot_api_dot_agv__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/api/agv_service.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\x1a\x1a\x63mvr/api/agv_command.proto2\xd0\x0e\n\nAgvService\x12\x66\n\x0fgetRuntimeState\x12(.cmvr.api.AgvRuntimeStateCommand.Request\x1a).cmvr.api.AgvRuntimeStateCommand.Feedback\x12r\n\x13getNavigationStatus\x12,.cmvr.api.AgvNavigationStatusCommand.Request\x1a-.cmvr.api.AgvNavigationStatusCommand.Feedback\x12R\n\remergencyStop\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12O\n\nclearFault\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12i\n\x0enavigateToPose\x12*.cmvr.api.AgvNavigateToPoseCommand.Request\x1a+.cmvr.api.AgvNavigateToPoseCommand.Feedback\x12r\n\x11navigateToStation\x12-.cmvr.api.AgvNavigateToStationCommand.Request\x1a..cmvr.api.AgvNavigateToStationCommand.Feedback\x12]\n\nfollowPath\x12&.cmvr.api.AgvFollowPathCommand.Request\x1a\'.cmvr.api.AgvFollowPathCommand.Feedback\x12T\n\x0fpauseNavigation\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12U\n\x10resumeNavigation\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12U\n\x10\x63\x61ncelNavigation\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12`\n\x0bsetVelocity\x12\'.cmvr.api.AgvSetVelocityCommand.Request\x1a(.cmvr.api.AgvSetVelocityCommand.Feedback\x12X\n\x13stopVelocityControl\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12W\n\x08listMaps\x12$.cmvr.api.AgvListMapsCommand.Request\x1a%.cmvr.api.AgvListMapsCommand.Feedback\x12\x63\n\x0clistStations\x12(.cmvr.api.AgvListStationsCommand.Request\x1a).cmvr.api.AgvListStationsCommand.Feedback\x12N\n\tswitchMap\x12\x1f.cmvr.api.AgvMapCommand.Request\x1a .cmvr.api.AgvMapCommand.Feedback\x12N\n\tuploadMap\x12\x1f.cmvr.api.AgvMapCommand.Request\x1a .cmvr.api.AgvMapCommand.Feedback\x12P\n\x0b\x64ownloadMap\x12\x1f.cmvr.api.AgvMapCommand.Request\x1a .cmvr.api.AgvMapCommand.Feedback\x12\x63\n\x0cstartMapping\x12(.cmvr.api.AgvStartMappingCommand.Request\x1a).cmvr.api.AgvStartMappingCommand.Feedback\x12\\\n\tstreamMap\x12%.cmvr.api.AgvMapStreamCommand.Request\x1a&.cmvr.api.AgvMapStreamCommand.Feedback0\x01\x12P\n\x0bstopMapping\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedbackb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.agv_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_AGVSERVICE']._serialized_start=92 + _globals['_AGVSERVICE']._serialized_end=1964 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/agv_service_pb2.pyi b/src/cmvr/api/agv_service_pb2.pyi new file mode 100644 index 0000000..9861922 --- /dev/null +++ b/src/cmvr/api/agv_service_pb2.pyi @@ -0,0 +1,6 @@ +from cmvr.api import common_pb2 as _common_pb2 +from cmvr.api import agv_command_pb2 as _agv_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/agv_service_pb2_grpc.py b/src/cmvr/api/agv_service_pb2_grpc.py new file mode 100644 index 0000000..0070d2f --- /dev/null +++ b/src/cmvr/api/agv_service_pb2_grpc.py @@ -0,0 +1,943 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import agv_command_pb2 as cmvr_dot_api_dot_agv__command__pb2 +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/agv_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class AgvServiceStub: + """AGV 通用服务。该服务只暴露控制器无关的能力, + 厂商协议、地图文件格式和控制器特有参数由具体 AGV 适配器内部处理。 + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.getRuntimeState = channel.unary_unary( + '/cmvr.api.AgvService/getRuntimeState', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvRuntimeStateCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvRuntimeStateCommand.Feedback.FromString, + _registered_method=True) + self.getNavigationStatus = channel.unary_unary( + '/cmvr.api.AgvService/getNavigationStatus', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigationStatusCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigationStatusCommand.Feedback.FromString, + _registered_method=True) + self.emergencyStop = channel.unary_unary( + '/cmvr.api.AgvService/emergencyStop', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.clearFault = channel.unary_unary( + '/cmvr.api.AgvService/clearFault', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.navigateToPose = channel.unary_unary( + '/cmvr.api.AgvService/navigateToPose', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToPoseCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToPoseCommand.Feedback.FromString, + _registered_method=True) + self.navigateToStation = channel.unary_unary( + '/cmvr.api.AgvService/navigateToStation', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToStationCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToStationCommand.Feedback.FromString, + _registered_method=True) + self.followPath = channel.unary_unary( + '/cmvr.api.AgvService/followPath', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvFollowPathCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvFollowPathCommand.Feedback.FromString, + _registered_method=True) + self.pauseNavigation = channel.unary_unary( + '/cmvr.api.AgvService/pauseNavigation', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.resumeNavigation = channel.unary_unary( + '/cmvr.api.AgvService/resumeNavigation', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.cancelNavigation = channel.unary_unary( + '/cmvr.api.AgvService/cancelNavigation', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.setVelocity = channel.unary_unary( + '/cmvr.api.AgvService/setVelocity', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvSetVelocityCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvSetVelocityCommand.Feedback.FromString, + _registered_method=True) + self.stopVelocityControl = channel.unary_unary( + '/cmvr.api.AgvService/stopVelocityControl', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.listMaps = channel.unary_unary( + '/cmvr.api.AgvService/listMaps', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvListMapsCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvListMapsCommand.Feedback.FromString, + _registered_method=True) + self.listStations = channel.unary_unary( + '/cmvr.api.AgvService/listStations', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvListStationsCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvListStationsCommand.Feedback.FromString, + _registered_method=True) + self.switchMap = channel.unary_unary( + '/cmvr.api.AgvService/switchMap', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.FromString, + _registered_method=True) + self.uploadMap = channel.unary_unary( + '/cmvr.api.AgvService/uploadMap', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.FromString, + _registered_method=True) + self.downloadMap = channel.unary_unary( + '/cmvr.api.AgvService/downloadMap', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.FromString, + _registered_method=True) + self.startMapping = channel.unary_unary( + '/cmvr.api.AgvService/startMapping', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvStartMappingCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvStartMappingCommand.Feedback.FromString, + _registered_method=True) + self.streamMap = channel.unary_stream( + '/cmvr.api.AgvService/streamMap', + request_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapStreamCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapStreamCommand.Feedback.FromString, + _registered_method=True) + self.stopMapping = channel.unary_unary( + '/cmvr.api.AgvService/stopMapping', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + + +class AgvServiceServicer: + """AGV 通用服务。该服务只暴露控制器无关的能力, + 厂商协议、地图文件格式和控制器特有参数由具体 AGV 适配器内部处理。 + """ + + def getRuntimeState(self, request, context): + """获取 AGV 当前运行状态快照。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getNavigationStatus(self, request, context): + """获取当前导航任务状态。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def emergencyStop(self, request, context): + """执行急停或等效安全停止动作。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def clearFault(self, request, context): + """清除可恢复故障或告警。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def navigateToPose(self, request, context): + """导航到指定地图位姿。目标位姿 x/y 单位为米,theta 单位为弧度。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def navigateToStation(self, request, context): + """导航到指定地图站点。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def followPath(self, request, context): + """按显式站点路径执行导航。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def pauseNavigation(self, request, context): + """暂停当前导航任务。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def resumeNavigation(self, request, context): + """恢复已暂停的导航任务。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def cancelNavigation(self, request, context): + """取消当前导航任务。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def setVelocity(self, request, context): + """下发底盘速度控制指令。vx/vy 单位为米/秒,wz 单位为弧度/秒。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def stopVelocityControl(self, request, context): + """停止底盘速度控制。该接口不等价于取消导航任务。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def listMaps(self, request, context): + """查询 AGV 控制器可用地图名称列表。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def listStations(self, request, context): + """查询当前地图中的站点列表。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def switchMap(self, request, context): + """切换当前使用地图。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def uploadMap(self, request, context): + """上传地图内容到 AGV 控制器。地图内容字段由适配器解释。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def downloadMap(self, request, context): + """下载指定地图内容。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def startMapping(self, request, context): + """开始建图/扫图会话。请求只选择 2D、3D 或二者都要, + 控制器特有地图格式由 AGV 适配器内部转换。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def streamMap(self, request, context): + """以服务端流方式发送统一地图。resume_token 为空时应先发送全量快照; + 后续是否发送增量由 AGV 适配器能力决定,并通过 update_type 标记。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def stopMapping(self, request, context): + """停止当前建图/扫图会话。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AgvServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'getRuntimeState': grpc.unary_unary_rpc_method_handler( + servicer.getRuntimeState, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvRuntimeStateCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvRuntimeStateCommand.Feedback.SerializeToString, + ), + 'getNavigationStatus': grpc.unary_unary_rpc_method_handler( + servicer.getNavigationStatus, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigationStatusCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigationStatusCommand.Feedback.SerializeToString, + ), + 'emergencyStop': grpc.unary_unary_rpc_method_handler( + servicer.emergencyStop, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'clearFault': grpc.unary_unary_rpc_method_handler( + servicer.clearFault, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'navigateToPose': grpc.unary_unary_rpc_method_handler( + servicer.navigateToPose, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToPoseCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToPoseCommand.Feedback.SerializeToString, + ), + 'navigateToStation': grpc.unary_unary_rpc_method_handler( + servicer.navigateToStation, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToStationCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToStationCommand.Feedback.SerializeToString, + ), + 'followPath': grpc.unary_unary_rpc_method_handler( + servicer.followPath, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvFollowPathCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvFollowPathCommand.Feedback.SerializeToString, + ), + 'pauseNavigation': grpc.unary_unary_rpc_method_handler( + servicer.pauseNavigation, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'resumeNavigation': grpc.unary_unary_rpc_method_handler( + servicer.resumeNavigation, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'cancelNavigation': grpc.unary_unary_rpc_method_handler( + servicer.cancelNavigation, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'setVelocity': grpc.unary_unary_rpc_method_handler( + servicer.setVelocity, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvSetVelocityCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvSetVelocityCommand.Feedback.SerializeToString, + ), + 'stopVelocityControl': grpc.unary_unary_rpc_method_handler( + servicer.stopVelocityControl, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'listMaps': grpc.unary_unary_rpc_method_handler( + servicer.listMaps, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvListMapsCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvListMapsCommand.Feedback.SerializeToString, + ), + 'listStations': grpc.unary_unary_rpc_method_handler( + servicer.listStations, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvListStationsCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvListStationsCommand.Feedback.SerializeToString, + ), + 'switchMap': grpc.unary_unary_rpc_method_handler( + servicer.switchMap, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.SerializeToString, + ), + 'uploadMap': grpc.unary_unary_rpc_method_handler( + servicer.uploadMap, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.SerializeToString, + ), + 'downloadMap': grpc.unary_unary_rpc_method_handler( + servicer.downloadMap, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.SerializeToString, + ), + 'startMapping': grpc.unary_unary_rpc_method_handler( + servicer.startMapping, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvStartMappingCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvStartMappingCommand.Feedback.SerializeToString, + ), + 'streamMap': grpc.unary_stream_rpc_method_handler( + servicer.streamMap, + request_deserializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapStreamCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_agv__command__pb2.AgvMapStreamCommand.Feedback.SerializeToString, + ), + 'stopMapping': grpc.unary_unary_rpc_method_handler( + servicer.stopMapping, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.AgvService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.AgvService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class AgvService: + """AGV 通用服务。该服务只暴露控制器无关的能力, + 厂商协议、地图文件格式和控制器特有参数由具体 AGV 适配器内部处理。 + """ + + @staticmethod + def getRuntimeState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/getRuntimeState', + cmvr_dot_api_dot_agv__command__pb2.AgvRuntimeStateCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvRuntimeStateCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def getNavigationStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/getNavigationStatus', + cmvr_dot_api_dot_agv__command__pb2.AgvNavigationStatusCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvNavigationStatusCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def emergencyStop(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/emergencyStop', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def clearFault(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/clearFault', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def navigateToPose(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/navigateToPose', + cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToPoseCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToPoseCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def navigateToStation(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/navigateToStation', + cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToStationCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvNavigateToStationCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def followPath(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/followPath', + cmvr_dot_api_dot_agv__command__pb2.AgvFollowPathCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvFollowPathCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def pauseNavigation(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/pauseNavigation', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def resumeNavigation(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/resumeNavigation', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def cancelNavigation(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/cancelNavigation', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def setVelocity(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/setVelocity', + cmvr_dot_api_dot_agv__command__pb2.AgvSetVelocityCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvSetVelocityCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def stopVelocityControl(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/stopVelocityControl', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def listMaps(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/listMaps', + cmvr_dot_api_dot_agv__command__pb2.AgvListMapsCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvListMapsCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def listStations(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/listStations', + cmvr_dot_api_dot_agv__command__pb2.AgvListStationsCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvListStationsCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def switchMap(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/switchMap', + cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def uploadMap(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/uploadMap', + cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def downloadMap(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/downloadMap', + cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvMapCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def startMapping(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/startMapping', + cmvr_dot_api_dot_agv__command__pb2.AgvStartMappingCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvStartMappingCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def streamMap(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/cmvr.api.AgvService/streamMap', + cmvr_dot_api_dot_agv__command__pb2.AgvMapStreamCommand.Request.SerializeToString, + cmvr_dot_api_dot_agv__command__pb2.AgvMapStreamCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def stopMapping(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.AgvService/stopMapping', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/arm_command_pb2.py b/src/cmvr/api/arm_command_pb2.py new file mode 100644 index 0000000..be8e45e --- /dev/null +++ b/src/cmvr/api/arm_command_pb2.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/arm_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/arm_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/api/arm_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"(\n\x14JointPositionCommand\x12\x10\n\x08position\x18\x01 \x03(\x01\"(\n\x14JointVelocityCommand\x12\x10\n\x08velocity\x18\x01 \x03(\x01\"\x90\x01\n\rMotionOptions\x12\x10\n\x08velocity\x18\x01 \x01(\x01\x12\x14\n\x0c\x61\x63\x63\x65leration\x18\x02 \x01(\x01\x12\x14\n\x0c\x62lend_radius\x18\x03 \x01(\x01\x12\x0c\n\x04jerk\x18\x04 \x01(\x01\x12\x1d\n\x15joint_velocity_limits\x18\x05 \x03(\x01\x12\x14\n\x0c\x61synchronous\x18\x06 \x01(\x08\"T\n\rCartesianPose\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\t\n\x01z\x18\x03 \x01(\x01\x12\n\n\x02rx\x18\x04 \x01(\x01\x12\n\n\x02ry\x18\x05 \x01(\x01\x12\n\n\x02rz\x18\x06 \x01(\x01\"[\n\x11\x43\x61rtesianVelocity\x12\n\n\x02vx\x18\x01 \x01(\x01\x12\n\n\x02vy\x18\x02 \x01(\x01\x12\n\n\x02vz\x18\x03 \x01(\x01\x12\n\n\x02wx\x18\x04 \x01(\x01\x12\n\n\x02wy\x18\x05 \x01(\x01\x12\n\n\x02wz\x18\x06 \x01(\x01\"\xe4\x01\n\x12TransformMatrix4x4\x12\x0b\n\x03m00\x18\x01 \x01(\x01\x12\x0b\n\x03m01\x18\x02 \x01(\x01\x12\x0b\n\x03m02\x18\x03 \x01(\x01\x12\x0b\n\x03m03\x18\x04 \x01(\x01\x12\x0b\n\x03m10\x18\x05 \x01(\x01\x12\x0b\n\x03m11\x18\x06 \x01(\x01\x12\x0b\n\x03m12\x18\x07 \x01(\x01\x12\x0b\n\x03m13\x18\x08 \x01(\x01\x12\x0b\n\x03m20\x18\t \x01(\x01\x12\x0b\n\x03m21\x18\n \x01(\x01\x12\x0b\n\x03m22\x18\x0b \x01(\x01\x12\x0b\n\x03m23\x18\x0c \x01(\x01\x12\x0b\n\x03m30\x18\r \x01(\x01\x12\x0b\n\x03m31\x18\x0e \x01(\x01\x12\x0b\n\x03m32\x18\x0f \x01(\x01\x12\x0b\n\x03m33\x18\x10 \x01(\x01\"\xdc\x01\n\x05MoveJ\x1a\x94\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12.\n\x06target\x18\x02 \x01(\x0b\x32\x1e.cmvr.api.JointPositionCommand\x12(\n\x07options\x18\x03 \x01(\x0b\x32\x17.cmvr.api.MotionOptions\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xfc\x01\n\x05MoveL\x1a\xb4\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\'\n\x06target\x18\x02 \x01(\x0b\x32\x17.cmvr.api.CartesianPose\x12(\n\x07options\x18\x03 \x01(\x0b\x32\x17.cmvr.api.MotionOptions\x12%\n\x05\x66rame\x18\x04 \x01(\x0e\x32\x16.cmvr.api.ArmFrameType\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xdd\x01\n\x06SpeedJ\x1a\x94\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x30\n\x08velocity\x18\x02 \x01(\x0b\x32\x1e.cmvr.api.JointVelocityCommand\x12\x14\n\x0c\x61\x63\x63\x65leration\x18\x03 \x01(\x01\x12\x10\n\x08\x64uration\x18\x04 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x81\x02\n\x06SpeedL\x1a\xb8\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12-\n\x08velocity\x18\x02 \x01(\x0b\x32\x1b.cmvr.api.CartesianVelocity\x12\x14\n\x0c\x61\x63\x63\x65leration\x18\x03 \x01(\x01\x12\x10\n\x08\x64uration\x18\x04 \x01(\x01\x12%\n\x05\x66rame\x18\x05 \x01(\x0e\x32\x16.cmvr.api.ArmFrameType\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xb2\x01\n\x06ServoJ\x1aj\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12.\n\x06target\x18\x02 \x01(\x0b\x32\x1e.cmvr.api.JointPositionCommand\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"a\n\nJointState\x12\x0c\n\x04name\x18\x01 \x03(\t\x12\x10\n\x08position\x18\x02 \x03(\x01\x12\x10\n\x08velocity\x18\x03 \x03(\x01\x12\x0e\n\x06\x65\x66\x66ort\x18\x04 \x03(\x01\x12\x11\n\ttimestamp\x18\x05 \x01(\x01\"?\n\x0cJointRequest\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\"f\n\rJointResponse\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12#\n\x05state\x18\x02 \x01(\x0b\x32\x14.cmvr.api.JointState\"\xce\x01\n\x07GetPose\x1a^\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tbase_link\x18\x02 \x01(\t\x12\x0f\n\x07\x65\x65_link\x18\x03 \x01(\t\x1a\x63\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12%\n\x04pose\x18\x02 \x01(\x0b\x32\x17.cmvr.api.CartesianPose\"\x9e\x01\n\x0e\x43\x61librateZeroQ\x1aN\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\njoint_name\x18\x02 \x01(\t\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xdb\x01\n\rGetPoseMatrix\x1a^\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tbase_link\x18\x02 \x01(\t\x12\x0f\n\x07\x65\x65_link\x18\x03 \x01(\t\x1aj\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12,\n\x06matrix\x18\x02 \x01(\x0b\x32\x1c.cmvr.api.TransformMatrix4x4\"\x97\x02\n\x18\x43omputeForwardKinematics\x1a\x8e\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tbase_link\x18\x02 \x01(\t\x12\x0f\n\x07\x65\x65_link\x18\x03 \x01(\t\x12.\n\x06joints\x18\x04 \x01(\x0b\x32\x1e.cmvr.api.JointPositionCommand\x1aj\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12,\n\x06matrix\x18\x02 \x01(\x0b\x32\x1c.cmvr.api.TransformMatrix4x4*_\n\x0c\x41rmFrameType\x12\x12\n\x0e\x41RM_FRAME_BASE\x10\x00\x12\x12\n\x0e\x41RM_FRAME_TOOL\x10\x01\x12\x13\n\x0f\x41RM_FRAME_WORLD\x10\x02\x12\x12\n\x0e\x41RM_FRAME_USER\x10\x03\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.arm_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_ARMFRAMETYPE']._serialized_start=2989 + _globals['_ARMFRAMETYPE']._serialized_end=3084 + _globals['_JOINTPOSITIONCOMMAND']._serialized_start=63 + _globals['_JOINTPOSITIONCOMMAND']._serialized_end=103 + _globals['_JOINTVELOCITYCOMMAND']._serialized_start=105 + _globals['_JOINTVELOCITYCOMMAND']._serialized_end=145 + _globals['_MOTIONOPTIONS']._serialized_start=148 + _globals['_MOTIONOPTIONS']._serialized_end=292 + _globals['_CARTESIANPOSE']._serialized_start=294 + _globals['_CARTESIANPOSE']._serialized_end=378 + _globals['_CARTESIANVELOCITY']._serialized_start=380 + _globals['_CARTESIANVELOCITY']._serialized_end=471 + _globals['_TRANSFORMMATRIX4X4']._serialized_start=474 + _globals['_TRANSFORMMATRIX4X4']._serialized_end=702 + _globals['_MOVEJ']._serialized_start=705 + _globals['_MOVEJ']._serialized_end=925 + _globals['_MOVEJ_REQUEST']._serialized_start=715 + _globals['_MOVEJ_REQUEST']._serialized_end=863 + _globals['_MOVEJ_RESPONSE']._serialized_start=865 + _globals['_MOVEJ_RESPONSE']._serialized_end=925 + _globals['_MOVEL']._serialized_start=928 + _globals['_MOVEL']._serialized_end=1180 + _globals['_MOVEL_REQUEST']._serialized_start=938 + _globals['_MOVEL_REQUEST']._serialized_end=1118 + _globals['_MOVEL_RESPONSE']._serialized_start=865 + _globals['_MOVEL_RESPONSE']._serialized_end=925 + _globals['_SPEEDJ']._serialized_start=1183 + _globals['_SPEEDJ']._serialized_end=1404 + _globals['_SPEEDJ_REQUEST']._serialized_start=1194 + _globals['_SPEEDJ_REQUEST']._serialized_end=1342 + _globals['_SPEEDJ_RESPONSE']._serialized_start=865 + _globals['_SPEEDJ_RESPONSE']._serialized_end=925 + _globals['_SPEEDL']._serialized_start=1407 + _globals['_SPEEDL']._serialized_end=1664 + _globals['_SPEEDL_REQUEST']._serialized_start=1418 + _globals['_SPEEDL_REQUEST']._serialized_end=1602 + _globals['_SPEEDL_RESPONSE']._serialized_start=865 + _globals['_SPEEDL_RESPONSE']._serialized_end=925 + _globals['_SERVOJ']._serialized_start=1667 + _globals['_SERVOJ']._serialized_end=1845 + _globals['_SERVOJ_REQUEST']._serialized_start=715 + _globals['_SERVOJ_REQUEST']._serialized_end=821 + _globals['_SERVOJ_RESPONSE']._serialized_start=865 + _globals['_SERVOJ_RESPONSE']._serialized_end=925 + _globals['_JOINTSTATE']._serialized_start=1847 + _globals['_JOINTSTATE']._serialized_end=1944 + _globals['_JOINTREQUEST']._serialized_start=1946 + _globals['_JOINTREQUEST']._serialized_end=2009 + _globals['_JOINTRESPONSE']._serialized_start=2011 + _globals['_JOINTRESPONSE']._serialized_end=2113 + _globals['_GETPOSE']._serialized_start=2116 + _globals['_GETPOSE']._serialized_end=2322 + _globals['_GETPOSE_REQUEST']._serialized_start=2127 + _globals['_GETPOSE_REQUEST']._serialized_end=2221 + _globals['_GETPOSE_RESPONSE']._serialized_start=2223 + _globals['_GETPOSE_RESPONSE']._serialized_end=2322 + _globals['_CALIBRATEZEROQ']._serialized_start=2325 + _globals['_CALIBRATEZEROQ']._serialized_end=2483 + _globals['_CALIBRATEZEROQ_REQUEST']._serialized_start=2343 + _globals['_CALIBRATEZEROQ_REQUEST']._serialized_end=2421 + _globals['_CALIBRATEZEROQ_RESPONSE']._serialized_start=865 + _globals['_CALIBRATEZEROQ_RESPONSE']._serialized_end=925 + _globals['_GETPOSEMATRIX']._serialized_start=2486 + _globals['_GETPOSEMATRIX']._serialized_end=2705 + _globals['_GETPOSEMATRIX_REQUEST']._serialized_start=2127 + _globals['_GETPOSEMATRIX_REQUEST']._serialized_end=2221 + _globals['_GETPOSEMATRIX_RESPONSE']._serialized_start=2599 + _globals['_GETPOSEMATRIX_RESPONSE']._serialized_end=2705 + _globals['_COMPUTEFORWARDKINEMATICS']._serialized_start=2708 + _globals['_COMPUTEFORWARDKINEMATICS']._serialized_end=2987 + _globals['_COMPUTEFORWARDKINEMATICS_REQUEST']._serialized_start=2737 + _globals['_COMPUTEFORWARDKINEMATICS_REQUEST']._serialized_end=2879 + _globals['_COMPUTEFORWARDKINEMATICS_RESPONSE']._serialized_start=2599 + _globals['_COMPUTEFORWARDKINEMATICS_RESPONSE']._serialized_end=2705 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/arm_command_pb2.pyi b/src/cmvr/api/arm_command_pb2.pyi new file mode 100644 index 0000000..aed0966 --- /dev/null +++ b/src/cmvr/api/arm_command_pb2.pyi @@ -0,0 +1,318 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ArmFrameType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ARM_FRAME_BASE: _ClassVar[ArmFrameType] + ARM_FRAME_TOOL: _ClassVar[ArmFrameType] + ARM_FRAME_WORLD: _ClassVar[ArmFrameType] + ARM_FRAME_USER: _ClassVar[ArmFrameType] +ARM_FRAME_BASE: ArmFrameType +ARM_FRAME_TOOL: ArmFrameType +ARM_FRAME_WORLD: ArmFrameType +ARM_FRAME_USER: ArmFrameType + +class JointPositionCommand(_message.Message): + __slots__ = ("position",) + POSITION_FIELD_NUMBER: _ClassVar[int] + position: _containers.RepeatedScalarFieldContainer[float] + def __init__(self, position: _Optional[_Iterable[float]] = ...) -> None: ... + +class JointVelocityCommand(_message.Message): + __slots__ = ("velocity",) + VELOCITY_FIELD_NUMBER: _ClassVar[int] + velocity: _containers.RepeatedScalarFieldContainer[float] + def __init__(self, velocity: _Optional[_Iterable[float]] = ...) -> None: ... + +class MotionOptions(_message.Message): + __slots__ = ("velocity", "acceleration", "blend_radius", "jerk", "joint_velocity_limits", "asynchronous") + VELOCITY_FIELD_NUMBER: _ClassVar[int] + ACCELERATION_FIELD_NUMBER: _ClassVar[int] + BLEND_RADIUS_FIELD_NUMBER: _ClassVar[int] + JERK_FIELD_NUMBER: _ClassVar[int] + JOINT_VELOCITY_LIMITS_FIELD_NUMBER: _ClassVar[int] + ASYNCHRONOUS_FIELD_NUMBER: _ClassVar[int] + velocity: float + acceleration: float + blend_radius: float + jerk: float + joint_velocity_limits: _containers.RepeatedScalarFieldContainer[float] + asynchronous: bool + def __init__(self, velocity: _Optional[float] = ..., acceleration: _Optional[float] = ..., blend_radius: _Optional[float] = ..., jerk: _Optional[float] = ..., joint_velocity_limits: _Optional[_Iterable[float]] = ..., asynchronous: _Optional[bool] = ...) -> None: ... + +class CartesianPose(_message.Message): + __slots__ = ("x", "y", "z", "rx", "ry", "rz") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + RX_FIELD_NUMBER: _ClassVar[int] + RY_FIELD_NUMBER: _ClassVar[int] + RZ_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + z: float + rx: float + ry: float + rz: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ..., rx: _Optional[float] = ..., ry: _Optional[float] = ..., rz: _Optional[float] = ...) -> None: ... + +class CartesianVelocity(_message.Message): + __slots__ = ("vx", "vy", "vz", "wx", "wy", "wz") + VX_FIELD_NUMBER: _ClassVar[int] + VY_FIELD_NUMBER: _ClassVar[int] + VZ_FIELD_NUMBER: _ClassVar[int] + WX_FIELD_NUMBER: _ClassVar[int] + WY_FIELD_NUMBER: _ClassVar[int] + WZ_FIELD_NUMBER: _ClassVar[int] + vx: float + vy: float + vz: float + wx: float + wy: float + wz: float + def __init__(self, vx: _Optional[float] = ..., vy: _Optional[float] = ..., vz: _Optional[float] = ..., wx: _Optional[float] = ..., wy: _Optional[float] = ..., wz: _Optional[float] = ...) -> None: ... + +class TransformMatrix4x4(_message.Message): + __slots__ = ("m00", "m01", "m02", "m03", "m10", "m11", "m12", "m13", "m20", "m21", "m22", "m23", "m30", "m31", "m32", "m33") + M00_FIELD_NUMBER: _ClassVar[int] + M01_FIELD_NUMBER: _ClassVar[int] + M02_FIELD_NUMBER: _ClassVar[int] + M03_FIELD_NUMBER: _ClassVar[int] + M10_FIELD_NUMBER: _ClassVar[int] + M11_FIELD_NUMBER: _ClassVar[int] + M12_FIELD_NUMBER: _ClassVar[int] + M13_FIELD_NUMBER: _ClassVar[int] + M20_FIELD_NUMBER: _ClassVar[int] + M21_FIELD_NUMBER: _ClassVar[int] + M22_FIELD_NUMBER: _ClassVar[int] + M23_FIELD_NUMBER: _ClassVar[int] + M30_FIELD_NUMBER: _ClassVar[int] + M31_FIELD_NUMBER: _ClassVar[int] + M32_FIELD_NUMBER: _ClassVar[int] + M33_FIELD_NUMBER: _ClassVar[int] + m00: float + m01: float + m02: float + m03: float + m10: float + m11: float + m12: float + m13: float + m20: float + m21: float + m22: float + m23: float + m30: float + m31: float + m32: float + m33: float + def __init__(self, m00: _Optional[float] = ..., m01: _Optional[float] = ..., m02: _Optional[float] = ..., m03: _Optional[float] = ..., m10: _Optional[float] = ..., m11: _Optional[float] = ..., m12: _Optional[float] = ..., m13: _Optional[float] = ..., m20: _Optional[float] = ..., m21: _Optional[float] = ..., m22: _Optional[float] = ..., m23: _Optional[float] = ..., m30: _Optional[float] = ..., m31: _Optional[float] = ..., m32: _Optional[float] = ..., m33: _Optional[float] = ...) -> None: ... + +class MoveJ(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "target", "options") + HEADER_FIELD_NUMBER: _ClassVar[int] + TARGET_FIELD_NUMBER: _ClassVar[int] + OPTIONS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + target: JointPositionCommand + options: MotionOptions + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., target: _Optional[_Union[JointPositionCommand, _Mapping]] = ..., options: _Optional[_Union[MotionOptions, _Mapping]] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class MoveL(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "target", "options", "frame") + HEADER_FIELD_NUMBER: _ClassVar[int] + TARGET_FIELD_NUMBER: _ClassVar[int] + OPTIONS_FIELD_NUMBER: _ClassVar[int] + FRAME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + target: CartesianPose + options: MotionOptions + frame: ArmFrameType + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., target: _Optional[_Union[CartesianPose, _Mapping]] = ..., options: _Optional[_Union[MotionOptions, _Mapping]] = ..., frame: _Optional[_Union[ArmFrameType, str]] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SpeedJ(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "velocity", "acceleration", "duration") + HEADER_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + ACCELERATION_FIELD_NUMBER: _ClassVar[int] + DURATION_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + velocity: JointVelocityCommand + acceleration: float + duration: float + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., velocity: _Optional[_Union[JointVelocityCommand, _Mapping]] = ..., acceleration: _Optional[float] = ..., duration: _Optional[float] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SpeedL(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "velocity", "acceleration", "duration", "frame") + HEADER_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + ACCELERATION_FIELD_NUMBER: _ClassVar[int] + DURATION_FIELD_NUMBER: _ClassVar[int] + FRAME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + velocity: CartesianVelocity + acceleration: float + duration: float + frame: ArmFrameType + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., velocity: _Optional[_Union[CartesianVelocity, _Mapping]] = ..., acceleration: _Optional[float] = ..., duration: _Optional[float] = ..., frame: _Optional[_Union[ArmFrameType, str]] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ServoJ(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "target") + HEADER_FIELD_NUMBER: _ClassVar[int] + TARGET_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + target: JointPositionCommand + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., target: _Optional[_Union[JointPositionCommand, _Mapping]] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class JointState(_message.Message): + __slots__ = ("name", "position", "velocity", "effort", "timestamp") + NAME_FIELD_NUMBER: _ClassVar[int] + POSITION_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + EFFORT_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + name: _containers.RepeatedScalarFieldContainer[str] + position: _containers.RepeatedScalarFieldContainer[float] + velocity: _containers.RepeatedScalarFieldContainer[float] + effort: _containers.RepeatedScalarFieldContainer[float] + timestamp: float + def __init__(self, name: _Optional[_Iterable[str]] = ..., position: _Optional[_Iterable[float]] = ..., velocity: _Optional[_Iterable[float]] = ..., effort: _Optional[_Iterable[float]] = ..., timestamp: _Optional[float] = ...) -> None: ... + +class JointRequest(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + +class JointResponse(_message.Message): + __slots__ = ("header", "state") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + state: JointState + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., state: _Optional[_Union[JointState, _Mapping]] = ...) -> None: ... + +class GetPose(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "base_link", "ee_link") + HEADER_FIELD_NUMBER: _ClassVar[int] + BASE_LINK_FIELD_NUMBER: _ClassVar[int] + EE_LINK_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + base_link: str + ee_link: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., base_link: _Optional[str] = ..., ee_link: _Optional[str] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header", "pose") + HEADER_FIELD_NUMBER: _ClassVar[int] + POSE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + pose: CartesianPose + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., pose: _Optional[_Union[CartesianPose, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class CalibrateZeroQ(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "joint_name") + HEADER_FIELD_NUMBER: _ClassVar[int] + JOINT_NAME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + joint_name: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., joint_name: _Optional[str] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetPoseMatrix(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "base_link", "ee_link") + HEADER_FIELD_NUMBER: _ClassVar[int] + BASE_LINK_FIELD_NUMBER: _ClassVar[int] + EE_LINK_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + base_link: str + ee_link: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., base_link: _Optional[str] = ..., ee_link: _Optional[str] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header", "matrix") + HEADER_FIELD_NUMBER: _ClassVar[int] + MATRIX_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + matrix: TransformMatrix4x4 + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., matrix: _Optional[_Union[TransformMatrix4x4, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ComputeForwardKinematics(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "base_link", "ee_link", "joints") + HEADER_FIELD_NUMBER: _ClassVar[int] + BASE_LINK_FIELD_NUMBER: _ClassVar[int] + EE_LINK_FIELD_NUMBER: _ClassVar[int] + JOINTS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + base_link: str + ee_link: str + joints: JointPositionCommand + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., base_link: _Optional[str] = ..., ee_link: _Optional[str] = ..., joints: _Optional[_Union[JointPositionCommand, _Mapping]] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header", "matrix") + HEADER_FIELD_NUMBER: _ClassVar[int] + MATRIX_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + matrix: TransformMatrix4x4 + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., matrix: _Optional[_Union[TransformMatrix4x4, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/arm_command_pb2_grpc.py b/src/cmvr/api/arm_command_pb2_grpc.py new file mode 100644 index 0000000..3accff3 --- /dev/null +++ b/src/cmvr/api/arm_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/arm_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/arm_service_pb2.py b/src/cmvr/api/arm_service_pb2.py new file mode 100644 index 0000000..2419ca9 --- /dev/null +++ b/src/cmvr/api/arm_service_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/arm_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/arm_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 +from cmvr.api import arm_command_pb2 as cmvr_dot_api_dot_arm__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/api/arm_service.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\x1a\x1a\x63mvr/api/arm_command.proto2\xd5\x07\n\nArmService\x12N\n\ttorqueOff\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12M\n\x08torqueOn\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12:\n\x05moveJ\x12\x17.cmvr.api.MoveJ.Request\x1a\x18.cmvr.api.MoveJ.Response\x12:\n\x05moveL\x12\x17.cmvr.api.MoveL.Request\x1a\x18.cmvr.api.MoveL.Response\x12=\n\x06speedJ\x12\x18.cmvr.api.SpeedJ.Request\x1a\x19.cmvr.api.SpeedJ.Response\x12=\n\x06speedL\x12\x18.cmvr.api.SpeedL.Request\x1a\x19.cmvr.api.SpeedL.Response\x12=\n\x06servoJ\x12\x18.cmvr.api.ServoJ.Request\x1a\x19.cmvr.api.ServoJ.Response\x12O\n\nstopMotion\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12@\n\rgetJointState\x12\x16.cmvr.api.JointRequest\x1a\x17.cmvr.api.JointResponse\x12@\n\x07getPose\x12\x19.cmvr.api.GetPose.Request\x1a\x1a.cmvr.api.GetPose.Response\x12U\n\x0e\x63\x61librateZeroQ\x12 .cmvr.api.CalibrateZeroQ.Request\x1a!.cmvr.api.CalibrateZeroQ.Response\x12R\n\rgetPoseMatrix\x12\x1f.cmvr.api.GetPoseMatrix.Request\x1a .cmvr.api.GetPoseMatrix.Response\x12s\n\x18\x63omputeForwardKinematics\x12*.cmvr.api.ComputeForwardKinematics.Request\x1a+.cmvr.api.ComputeForwardKinematics.Responseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.arm_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_ARMSERVICE']._serialized_start=92 + _globals['_ARMSERVICE']._serialized_end=1073 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/arm_service_pb2.pyi b/src/cmvr/api/arm_service_pb2.pyi new file mode 100644 index 0000000..9d65316 --- /dev/null +++ b/src/cmvr/api/arm_service_pb2.pyi @@ -0,0 +1,6 @@ +from cmvr.api import common_pb2 as _common_pb2 +from cmvr.api import arm_command_pb2 as _arm_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/arm_service_pb2_grpc.py b/src/cmvr/api/arm_service_pb2_grpc.py new file mode 100644 index 0000000..7f1b320 --- /dev/null +++ b/src/cmvr/api/arm_service_pb2_grpc.py @@ -0,0 +1,614 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import arm_command_pb2 as cmvr_dot_api_dot_arm__command__pb2 +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/arm_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class ArmServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.torqueOff = channel.unary_unary( + '/cmvr.api.ArmService/torqueOff', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.torqueOn = channel.unary_unary( + '/cmvr.api.ArmService/torqueOn', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.moveJ = channel.unary_unary( + '/cmvr.api.ArmService/moveJ', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.MoveJ.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.MoveJ.Response.FromString, + _registered_method=True) + self.moveL = channel.unary_unary( + '/cmvr.api.ArmService/moveL', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.MoveL.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.MoveL.Response.FromString, + _registered_method=True) + self.speedJ = channel.unary_unary( + '/cmvr.api.ArmService/speedJ', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.SpeedJ.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.SpeedJ.Response.FromString, + _registered_method=True) + self.speedL = channel.unary_unary( + '/cmvr.api.ArmService/speedL', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.SpeedL.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.SpeedL.Response.FromString, + _registered_method=True) + self.servoJ = channel.unary_unary( + '/cmvr.api.ArmService/servoJ', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.ServoJ.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.ServoJ.Response.FromString, + _registered_method=True) + self.stopMotion = channel.unary_unary( + '/cmvr.api.ArmService/stopMotion', + request_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + _registered_method=True) + self.getJointState = channel.unary_unary( + '/cmvr.api.ArmService/getJointState', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.JointRequest.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.JointResponse.FromString, + _registered_method=True) + self.getPose = channel.unary_unary( + '/cmvr.api.ArmService/getPose', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.GetPose.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.GetPose.Response.FromString, + _registered_method=True) + self.calibrateZeroQ = channel.unary_unary( + '/cmvr.api.ArmService/calibrateZeroQ', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.CalibrateZeroQ.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.CalibrateZeroQ.Response.FromString, + _registered_method=True) + self.getPoseMatrix = channel.unary_unary( + '/cmvr.api.ArmService/getPoseMatrix', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.GetPoseMatrix.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.GetPoseMatrix.Response.FromString, + _registered_method=True) + self.computeForwardKinematics = channel.unary_unary( + '/cmvr.api.ArmService/computeForwardKinematics', + request_serializer=cmvr_dot_api_dot_arm__command__pb2.ComputeForwardKinematics.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_arm__command__pb2.ComputeForwardKinematics.Response.FromString, + _registered_method=True) + + +class ArmServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def torqueOff(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def torqueOn(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def moveJ(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def moveL(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def speedJ(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def speedL(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def servoJ(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def stopMotion(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getJointState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getPose(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def calibrateZeroQ(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getPoseMatrix(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def computeForwardKinematics(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ArmServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'torqueOff': grpc.unary_unary_rpc_method_handler( + servicer.torqueOff, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'torqueOn': grpc.unary_unary_rpc_method_handler( + servicer.torqueOn, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'moveJ': grpc.unary_unary_rpc_method_handler( + servicer.moveJ, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.MoveJ.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.MoveJ.Response.SerializeToString, + ), + 'moveL': grpc.unary_unary_rpc_method_handler( + servicer.moveL, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.MoveL.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.MoveL.Response.SerializeToString, + ), + 'speedJ': grpc.unary_unary_rpc_method_handler( + servicer.speedJ, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.SpeedJ.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.SpeedJ.Response.SerializeToString, + ), + 'speedL': grpc.unary_unary_rpc_method_handler( + servicer.speedL, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.SpeedL.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.SpeedL.Response.SerializeToString, + ), + 'servoJ': grpc.unary_unary_rpc_method_handler( + servicer.servoJ, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.ServoJ.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.ServoJ.Response.SerializeToString, + ), + 'stopMotion': grpc.unary_unary_rpc_method_handler( + servicer.stopMotion, + request_deserializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Request.FromString, + response_serializer=cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.SerializeToString, + ), + 'getJointState': grpc.unary_unary_rpc_method_handler( + servicer.getJointState, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.JointRequest.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.JointResponse.SerializeToString, + ), + 'getPose': grpc.unary_unary_rpc_method_handler( + servicer.getPose, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.GetPose.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.GetPose.Response.SerializeToString, + ), + 'calibrateZeroQ': grpc.unary_unary_rpc_method_handler( + servicer.calibrateZeroQ, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.CalibrateZeroQ.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.CalibrateZeroQ.Response.SerializeToString, + ), + 'getPoseMatrix': grpc.unary_unary_rpc_method_handler( + servicer.getPoseMatrix, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.GetPoseMatrix.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.GetPoseMatrix.Response.SerializeToString, + ), + 'computeForwardKinematics': grpc.unary_unary_rpc_method_handler( + servicer.computeForwardKinematics, + request_deserializer=cmvr_dot_api_dot_arm__command__pb2.ComputeForwardKinematics.Request.FromString, + response_serializer=cmvr_dot_api_dot_arm__command__pb2.ComputeForwardKinematics.Response.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.ArmService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.ArmService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class ArmService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def torqueOff(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/torqueOff', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def torqueOn(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/torqueOn', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def moveJ(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/moveJ', + cmvr_dot_api_dot_arm__command__pb2.MoveJ.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.MoveJ.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def moveL(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/moveL', + cmvr_dot_api_dot_arm__command__pb2.MoveL.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.MoveL.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def speedJ(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/speedJ', + cmvr_dot_api_dot_arm__command__pb2.SpeedJ.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.SpeedJ.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def speedL(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/speedL', + cmvr_dot_api_dot_arm__command__pb2.SpeedL.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.SpeedL.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def servoJ(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/servoJ', + cmvr_dot_api_dot_arm__command__pb2.ServoJ.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.ServoJ.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def stopMotion(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/stopMotion', + cmvr_dot_api_dot_common__pb2.CommandHeader.Request.SerializeToString, + cmvr_dot_api_dot_common__pb2.CommandHeader.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def getJointState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/getJointState', + cmvr_dot_api_dot_arm__command__pb2.JointRequest.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.JointResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def getPose(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/getPose', + cmvr_dot_api_dot_arm__command__pb2.GetPose.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.GetPose.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def calibrateZeroQ(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/calibrateZeroQ', + cmvr_dot_api_dot_arm__command__pb2.CalibrateZeroQ.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.CalibrateZeroQ.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def getPoseMatrix(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/getPoseMatrix', + cmvr_dot_api_dot_arm__command__pb2.GetPoseMatrix.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.GetPoseMatrix.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def computeForwardKinematics(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.ArmService/computeForwardKinematics', + cmvr_dot_api_dot_arm__command__pb2.ComputeForwardKinematics.Request.SerializeToString, + cmvr_dot_api_dot_arm__command__pb2.ComputeForwardKinematics.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/biohead_command_pb2.py b/src/cmvr/api/biohead_command_pb2.py new file mode 100644 index 0000000..6e9bfa1 --- /dev/null +++ b/src/cmvr/api/biohead_command_pb2.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/biohead_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/biohead_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/biohead_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"\xdd\x08\n\x10\x46\x61\x63ialExpression\x12\x33\n\x07\x65yebrow\x18\x03 \x01(\x0b\x32\".cmvr.api.FacialExpression.Eyebrow\x12\x31\n\x06\x65yelid\x18\x04 \x01(\x0b\x32!.cmvr.api.FacialExpression.Eyelid\x12\x33\n\x07\x65yeball\x18\x05 \x01(\x0b\x32\".cmvr.api.FacialExpression.Eyeball\x12-\n\x04nose\x18\x06 \x01(\x0b\x32\x1f.cmvr.api.FacialExpression.Nose\x12/\n\x05mouth\x18\x07 \x01(\x0b\x32 .cmvr.api.FacialExpression.Mouth\x12+\n\x03jaw\x18\x08 \x01(\x0b\x32\x1e.cmvr.api.FacialExpression.Jaw\x1ai\n\x07\x45yebrow\x12\x16\n\x0eleft_outside_y\x18\x01 \x01(\x02\x12\x15\n\rleft_inside_y\x18\x02 \x01(\x02\x12\x17\n\x0fright_outside_y\x18\x03 \x01(\x02\x12\x16\n\x0eright_inside_y\x18\x04 \x01(\x02\x1a\x62\n\x06\x45yelid\x12\x14\n\x0cleft_upper_y\x18\x01 \x01(\x02\x12\x14\n\x0cleft_lower_y\x18\x02 \x01(\x02\x12\x15\n\rright_upper_y\x18\x03 \x01(\x02\x12\x15\n\rright_lower_y\x18\x04 \x01(\x02\x1aK\n\x07\x45yeball\x12\x0e\n\x06left_x\x18\x01 \x01(\x02\x12\x0e\n\x06left_y\x18\x02 \x01(\x02\x12\x0f\n\x07right_x\x18\x03 \x01(\x02\x12\x0f\n\x07right_y\x18\x04 \x01(\x02\x1a\'\n\x04Nose\x12\x0e\n\x06left_y\x18\x01 \x01(\x02\x12\x0f\n\x07right_y\x18\x02 \x01(\x02\x1a\xbc\x03\n\x05Mouth\x12\x13\n\x0bupper_lip_y\x18\x01 \x01(\x02\x12\x13\n\x0bupper_lip_z\x18\x02 \x01(\x02\x12\x13\n\x0blower_lip_y\x18\x03 \x01(\x02\x12\x13\n\x0blower_lip_z\x18\x04 \x01(\x02\x12:\n\x08left_lip\x18\x05 \x01(\x0b\x32(.cmvr.api.FacialExpression.Mouth.LeftLip\x12<\n\tright_lip\x18\x06 \x01(\x0b\x32).cmvr.api.FacialExpression.Mouth.RightLip\x1aq\n\x07LeftLip\x12\x0f\n\x07upper_x\x18\x01 \x01(\x02\x12\x0f\n\x07upper_y\x18\x02 \x01(\x02\x12\x10\n\x08\x63orner_x\x18\x03 \x01(\x02\x12\x10\n\x08\x63orner_y\x18\x04 \x01(\x02\x12\x0f\n\x07lower_x\x18\x05 \x01(\x02\x12\x0f\n\x07lower_y\x18\x06 \x01(\x02\x1ar\n\x08RightLip\x12\x0f\n\x07upper_x\x18\x01 \x01(\x02\x12\x0f\n\x07upper_y\x18\x02 \x01(\x02\x12\x10\n\x08\x63orner_x\x18\x03 \x01(\x02\x12\x10\n\x08\x63orner_y\x18\x04 \x01(\x02\x12\x0f\n\x07lower_x\x18\x05 \x01(\x02\x12\x0f\n\x07lower_y\x18\x06 \x01(\x02\x1a\x1b\n\x03Jaw\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"\xf0\x01\n\x13SetFacialExpression\x1aj\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12.\n\nexpression\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\x1am\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x14\n\x0c\x65xecution_id\x18\x02 \x01(\t\x12\x19\n\x11\x65xecution_time_ms\x18\x03 \x01(\x02\"\xf8\x01\n\x16StreamFacialExpression\x1aq\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12(\n\x04\x65xpr\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\x12\x0b\n\x03\x65of\x18\x03 \x01(\x08\x1ak\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12-\n\texpr_diff\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\"\x84\x02\n\tGetStatus\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\xba\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x11\n\tis_moving\x18\x02 \x01(\x08\x12\x17\n\x0flast_request_id\x18\x03 \x01(\t\x12\x19\n\x11\x63urrent_positions\x18\x04 \x03(\x02\x12\x18\n\x10\x63\x61mera_recording\x18\x05 \x01(\x08\x12\x1b\n\x13\x61\x63tive_recording_id\x18\x06 \x01(\t\"\xa4\x01\n\rEmergencyStop\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1aW\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x19\n\x11stopped_processes\x18\x02 \x01(\t\"\x86\x01\n\nSpeakStart\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x85\x01\n\tSpeakStop\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x81\x01\n\x05Happy\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x84\x01\n\x08Surprise\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8b\x01\n\x0f\x45xpressionTired\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8b\x01\n\x0f\x45xpressionAngry\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8d\x01\n\x11\x45xpressionSadness\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8a\x01\n\x0e\x45xpressionYawn\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedbackb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.biohead_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_FACIALEXPRESSION']._serialized_start=68 + _globals['_FACIALEXPRESSION']._serialized_end=1185 + _globals['_FACIALEXPRESSION_EYEBROW']._serialized_start=386 + _globals['_FACIALEXPRESSION_EYEBROW']._serialized_end=491 + _globals['_FACIALEXPRESSION_EYELID']._serialized_start=493 + _globals['_FACIALEXPRESSION_EYELID']._serialized_end=591 + _globals['_FACIALEXPRESSION_EYEBALL']._serialized_start=593 + _globals['_FACIALEXPRESSION_EYEBALL']._serialized_end=668 + _globals['_FACIALEXPRESSION_NOSE']._serialized_start=670 + _globals['_FACIALEXPRESSION_NOSE']._serialized_end=709 + _globals['_FACIALEXPRESSION_MOUTH']._serialized_start=712 + _globals['_FACIALEXPRESSION_MOUTH']._serialized_end=1156 + _globals['_FACIALEXPRESSION_MOUTH_LEFTLIP']._serialized_start=927 + _globals['_FACIALEXPRESSION_MOUTH_LEFTLIP']._serialized_end=1040 + _globals['_FACIALEXPRESSION_MOUTH_RIGHTLIP']._serialized_start=1042 + _globals['_FACIALEXPRESSION_MOUTH_RIGHTLIP']._serialized_end=1156 + _globals['_FACIALEXPRESSION_JAW']._serialized_start=1158 + _globals['_FACIALEXPRESSION_JAW']._serialized_end=1185 + _globals['_SETFACIALEXPRESSION']._serialized_start=1188 + _globals['_SETFACIALEXPRESSION']._serialized_end=1428 + _globals['_SETFACIALEXPRESSION_REQUEST']._serialized_start=1211 + _globals['_SETFACIALEXPRESSION_REQUEST']._serialized_end=1317 + _globals['_SETFACIALEXPRESSION_FEEDBACK']._serialized_start=1319 + _globals['_SETFACIALEXPRESSION_FEEDBACK']._serialized_end=1428 + _globals['_STREAMFACIALEXPRESSION']._serialized_start=1431 + _globals['_STREAMFACIALEXPRESSION']._serialized_end=1679 + _globals['_STREAMFACIALEXPRESSION_REQUEST']._serialized_start=1457 + _globals['_STREAMFACIALEXPRESSION_REQUEST']._serialized_end=1570 + _globals['_STREAMFACIALEXPRESSION_FEEDBACK']._serialized_start=1572 + _globals['_STREAMFACIALEXPRESSION_FEEDBACK']._serialized_end=1679 + _globals['_GETSTATUS']._serialized_start=1682 + _globals['_GETSTATUS']._serialized_end=1942 + _globals['_GETSTATUS_REQUEST']._serialized_start=1211 + _globals['_GETSTATUS_REQUEST']._serialized_end=1269 + _globals['_GETSTATUS_FEEDBACK']._serialized_start=1756 + _globals['_GETSTATUS_FEEDBACK']._serialized_end=1942 + _globals['_EMERGENCYSTOP']._serialized_start=1945 + _globals['_EMERGENCYSTOP']._serialized_end=2109 + _globals['_EMERGENCYSTOP_REQUEST']._serialized_start=1211 + _globals['_EMERGENCYSTOP_REQUEST']._serialized_end=1269 + _globals['_EMERGENCYSTOP_FEEDBACK']._serialized_start=2022 + _globals['_EMERGENCYSTOP_FEEDBACK']._serialized_end=2109 + _globals['_SPEAKSTART']._serialized_start=2112 + _globals['_SPEAKSTART']._serialized_end=2246 + _globals['_SPEAKSTART_REQUEST']._serialized_start=1211 + _globals['_SPEAKSTART_REQUEST']._serialized_end=1269 + _globals['_SPEAKSTART_FEEDBACK']._serialized_start=1319 + _globals['_SPEAKSTART_FEEDBACK']._serialized_end=1379 + _globals['_SPEAKSTOP']._serialized_start=2249 + _globals['_SPEAKSTOP']._serialized_end=2382 + _globals['_SPEAKSTOP_REQUEST']._serialized_start=1211 + _globals['_SPEAKSTOP_REQUEST']._serialized_end=1269 + _globals['_SPEAKSTOP_FEEDBACK']._serialized_start=1319 + _globals['_SPEAKSTOP_FEEDBACK']._serialized_end=1379 + _globals['_HAPPY']._serialized_start=2385 + _globals['_HAPPY']._serialized_end=2514 + _globals['_HAPPY_REQUEST']._serialized_start=1211 + _globals['_HAPPY_REQUEST']._serialized_end=1269 + _globals['_HAPPY_FEEDBACK']._serialized_start=1319 + _globals['_HAPPY_FEEDBACK']._serialized_end=1379 + _globals['_SURPRISE']._serialized_start=2517 + _globals['_SURPRISE']._serialized_end=2649 + _globals['_SURPRISE_REQUEST']._serialized_start=1211 + _globals['_SURPRISE_REQUEST']._serialized_end=1269 + _globals['_SURPRISE_FEEDBACK']._serialized_start=1319 + _globals['_SURPRISE_FEEDBACK']._serialized_end=1379 + _globals['_EXPRESSIONTIRED']._serialized_start=2652 + _globals['_EXPRESSIONTIRED']._serialized_end=2791 + _globals['_EXPRESSIONTIRED_REQUEST']._serialized_start=1211 + _globals['_EXPRESSIONTIRED_REQUEST']._serialized_end=1269 + _globals['_EXPRESSIONTIRED_FEEDBACK']._serialized_start=1319 + _globals['_EXPRESSIONTIRED_FEEDBACK']._serialized_end=1379 + _globals['_EXPRESSIONANGRY']._serialized_start=2794 + _globals['_EXPRESSIONANGRY']._serialized_end=2933 + _globals['_EXPRESSIONANGRY_REQUEST']._serialized_start=1211 + _globals['_EXPRESSIONANGRY_REQUEST']._serialized_end=1269 + _globals['_EXPRESSIONANGRY_FEEDBACK']._serialized_start=1319 + _globals['_EXPRESSIONANGRY_FEEDBACK']._serialized_end=1379 + _globals['_EXPRESSIONSADNESS']._serialized_start=2936 + _globals['_EXPRESSIONSADNESS']._serialized_end=3077 + _globals['_EXPRESSIONSADNESS_REQUEST']._serialized_start=1211 + _globals['_EXPRESSIONSADNESS_REQUEST']._serialized_end=1269 + _globals['_EXPRESSIONSADNESS_FEEDBACK']._serialized_start=1319 + _globals['_EXPRESSIONSADNESS_FEEDBACK']._serialized_end=1379 + _globals['_EXPRESSIONYAWN']._serialized_start=3080 + _globals['_EXPRESSIONYAWN']._serialized_end=3218 + _globals['_EXPRESSIONYAWN_REQUEST']._serialized_start=1211 + _globals['_EXPRESSIONYAWN_REQUEST']._serialized_end=1269 + _globals['_EXPRESSIONYAWN_FEEDBACK']._serialized_start=1319 + _globals['_EXPRESSIONYAWN_FEEDBACK']._serialized_end=1379 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/biohead_command_pb2.pyi b/src/cmvr/api/biohead_command_pb2.pyi new file mode 100644 index 0000000..a53f237 --- /dev/null +++ b/src/cmvr/api/biohead_command_pb2.pyi @@ -0,0 +1,308 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class FacialExpression(_message.Message): + __slots__ = ("eyebrow", "eyelid", "eyeball", "nose", "mouth", "jaw") + class Eyebrow(_message.Message): + __slots__ = ("left_outside_y", "left_inside_y", "right_outside_y", "right_inside_y") + LEFT_OUTSIDE_Y_FIELD_NUMBER: _ClassVar[int] + LEFT_INSIDE_Y_FIELD_NUMBER: _ClassVar[int] + RIGHT_OUTSIDE_Y_FIELD_NUMBER: _ClassVar[int] + RIGHT_INSIDE_Y_FIELD_NUMBER: _ClassVar[int] + left_outside_y: float + left_inside_y: float + right_outside_y: float + right_inside_y: float + def __init__(self, left_outside_y: _Optional[float] = ..., left_inside_y: _Optional[float] = ..., right_outside_y: _Optional[float] = ..., right_inside_y: _Optional[float] = ...) -> None: ... + class Eyelid(_message.Message): + __slots__ = ("left_upper_y", "left_lower_y", "right_upper_y", "right_lower_y") + LEFT_UPPER_Y_FIELD_NUMBER: _ClassVar[int] + LEFT_LOWER_Y_FIELD_NUMBER: _ClassVar[int] + RIGHT_UPPER_Y_FIELD_NUMBER: _ClassVar[int] + RIGHT_LOWER_Y_FIELD_NUMBER: _ClassVar[int] + left_upper_y: float + left_lower_y: float + right_upper_y: float + right_lower_y: float + def __init__(self, left_upper_y: _Optional[float] = ..., left_lower_y: _Optional[float] = ..., right_upper_y: _Optional[float] = ..., right_lower_y: _Optional[float] = ...) -> None: ... + class Eyeball(_message.Message): + __slots__ = ("left_x", "left_y", "right_x", "right_y") + LEFT_X_FIELD_NUMBER: _ClassVar[int] + LEFT_Y_FIELD_NUMBER: _ClassVar[int] + RIGHT_X_FIELD_NUMBER: _ClassVar[int] + RIGHT_Y_FIELD_NUMBER: _ClassVar[int] + left_x: float + left_y: float + right_x: float + right_y: float + def __init__(self, left_x: _Optional[float] = ..., left_y: _Optional[float] = ..., right_x: _Optional[float] = ..., right_y: _Optional[float] = ...) -> None: ... + class Nose(_message.Message): + __slots__ = ("left_y", "right_y") + LEFT_Y_FIELD_NUMBER: _ClassVar[int] + RIGHT_Y_FIELD_NUMBER: _ClassVar[int] + left_y: float + right_y: float + def __init__(self, left_y: _Optional[float] = ..., right_y: _Optional[float] = ...) -> None: ... + class Mouth(_message.Message): + __slots__ = ("upper_lip_y", "upper_lip_z", "lower_lip_y", "lower_lip_z", "left_lip", "right_lip") + class LeftLip(_message.Message): + __slots__ = ("upper_x", "upper_y", "corner_x", "corner_y", "lower_x", "lower_y") + UPPER_X_FIELD_NUMBER: _ClassVar[int] + UPPER_Y_FIELD_NUMBER: _ClassVar[int] + CORNER_X_FIELD_NUMBER: _ClassVar[int] + CORNER_Y_FIELD_NUMBER: _ClassVar[int] + LOWER_X_FIELD_NUMBER: _ClassVar[int] + LOWER_Y_FIELD_NUMBER: _ClassVar[int] + upper_x: float + upper_y: float + corner_x: float + corner_y: float + lower_x: float + lower_y: float + def __init__(self, upper_x: _Optional[float] = ..., upper_y: _Optional[float] = ..., corner_x: _Optional[float] = ..., corner_y: _Optional[float] = ..., lower_x: _Optional[float] = ..., lower_y: _Optional[float] = ...) -> None: ... + class RightLip(_message.Message): + __slots__ = ("upper_x", "upper_y", "corner_x", "corner_y", "lower_x", "lower_y") + UPPER_X_FIELD_NUMBER: _ClassVar[int] + UPPER_Y_FIELD_NUMBER: _ClassVar[int] + CORNER_X_FIELD_NUMBER: _ClassVar[int] + CORNER_Y_FIELD_NUMBER: _ClassVar[int] + LOWER_X_FIELD_NUMBER: _ClassVar[int] + LOWER_Y_FIELD_NUMBER: _ClassVar[int] + upper_x: float + upper_y: float + corner_x: float + corner_y: float + lower_x: float + lower_y: float + def __init__(self, upper_x: _Optional[float] = ..., upper_y: _Optional[float] = ..., corner_x: _Optional[float] = ..., corner_y: _Optional[float] = ..., lower_x: _Optional[float] = ..., lower_y: _Optional[float] = ...) -> None: ... + UPPER_LIP_Y_FIELD_NUMBER: _ClassVar[int] + UPPER_LIP_Z_FIELD_NUMBER: _ClassVar[int] + LOWER_LIP_Y_FIELD_NUMBER: _ClassVar[int] + LOWER_LIP_Z_FIELD_NUMBER: _ClassVar[int] + LEFT_LIP_FIELD_NUMBER: _ClassVar[int] + RIGHT_LIP_FIELD_NUMBER: _ClassVar[int] + upper_lip_y: float + upper_lip_z: float + lower_lip_y: float + lower_lip_z: float + left_lip: FacialExpression.Mouth.LeftLip + right_lip: FacialExpression.Mouth.RightLip + def __init__(self, upper_lip_y: _Optional[float] = ..., upper_lip_z: _Optional[float] = ..., lower_lip_y: _Optional[float] = ..., lower_lip_z: _Optional[float] = ..., left_lip: _Optional[_Union[FacialExpression.Mouth.LeftLip, _Mapping]] = ..., right_lip: _Optional[_Union[FacialExpression.Mouth.RightLip, _Mapping]] = ...) -> None: ... + class Jaw(_message.Message): + __slots__ = ("x", "y") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ...) -> None: ... + EYEBROW_FIELD_NUMBER: _ClassVar[int] + EYELID_FIELD_NUMBER: _ClassVar[int] + EYEBALL_FIELD_NUMBER: _ClassVar[int] + NOSE_FIELD_NUMBER: _ClassVar[int] + MOUTH_FIELD_NUMBER: _ClassVar[int] + JAW_FIELD_NUMBER: _ClassVar[int] + eyebrow: FacialExpression.Eyebrow + eyelid: FacialExpression.Eyelid + eyeball: FacialExpression.Eyeball + nose: FacialExpression.Nose + mouth: FacialExpression.Mouth + jaw: FacialExpression.Jaw + def __init__(self, eyebrow: _Optional[_Union[FacialExpression.Eyebrow, _Mapping]] = ..., eyelid: _Optional[_Union[FacialExpression.Eyelid, _Mapping]] = ..., eyeball: _Optional[_Union[FacialExpression.Eyeball, _Mapping]] = ..., nose: _Optional[_Union[FacialExpression.Nose, _Mapping]] = ..., mouth: _Optional[_Union[FacialExpression.Mouth, _Mapping]] = ..., jaw: _Optional[_Union[FacialExpression.Jaw, _Mapping]] = ...) -> None: ... + +class SetFacialExpression(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "expression") + HEADER_FIELD_NUMBER: _ClassVar[int] + EXPRESSION_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + expression: FacialExpression + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., expression: _Optional[_Union[FacialExpression, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "execution_id", "execution_time_ms") + HEADER_FIELD_NUMBER: _ClassVar[int] + EXECUTION_ID_FIELD_NUMBER: _ClassVar[int] + EXECUTION_TIME_MS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + execution_id: str + execution_time_ms: float + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., execution_id: _Optional[str] = ..., execution_time_ms: _Optional[float] = ...) -> None: ... + def __init__(self) -> None: ... + +class StreamFacialExpression(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "expr", "eof") + HEADER_FIELD_NUMBER: _ClassVar[int] + EXPR_FIELD_NUMBER: _ClassVar[int] + EOF_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + expr: FacialExpression + eof: bool + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., expr: _Optional[_Union[FacialExpression, _Mapping]] = ..., eof: _Optional[bool] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "expr_diff") + HEADER_FIELD_NUMBER: _ClassVar[int] + EXPR_DIFF_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + expr_diff: FacialExpression + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., expr_diff: _Optional[_Union[FacialExpression, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetStatus(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "is_moving", "last_request_id", "current_positions", "camera_recording", "active_recording_id") + HEADER_FIELD_NUMBER: _ClassVar[int] + IS_MOVING_FIELD_NUMBER: _ClassVar[int] + LAST_REQUEST_ID_FIELD_NUMBER: _ClassVar[int] + CURRENT_POSITIONS_FIELD_NUMBER: _ClassVar[int] + CAMERA_RECORDING_FIELD_NUMBER: _ClassVar[int] + ACTIVE_RECORDING_ID_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + is_moving: bool + last_request_id: str + current_positions: _containers.RepeatedScalarFieldContainer[float] + camera_recording: bool + active_recording_id: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., is_moving: _Optional[bool] = ..., last_request_id: _Optional[str] = ..., current_positions: _Optional[_Iterable[float]] = ..., camera_recording: _Optional[bool] = ..., active_recording_id: _Optional[str] = ...) -> None: ... + def __init__(self) -> None: ... + +class EmergencyStop(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "stopped_processes") + HEADER_FIELD_NUMBER: _ClassVar[int] + STOPPED_PROCESSES_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + stopped_processes: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., stopped_processes: _Optional[str] = ...) -> None: ... + def __init__(self) -> None: ... + +class SpeakStart(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SpeakStop(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class Happy(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class Surprise(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ExpressionTired(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ExpressionAngry(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ExpressionSadness(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ExpressionYawn(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/biohead_command_pb2_grpc.py b/src/cmvr/api/biohead_command_pb2_grpc.py new file mode 100644 index 0000000..dec6b6d --- /dev/null +++ b/src/cmvr/api/biohead_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/biohead_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/biohead_service_pb2.py b/src/cmvr/api/biohead_service_pb2.py new file mode 100644 index 0000000..9d96131 --- /dev/null +++ b/src/cmvr/api/biohead_service_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/biohead_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/biohead_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import biohead_command_pb2 as cmvr_dot_api_dot_biohead__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/biohead_service.proto\x12\x08\x63mvr.api\x1a\x1e\x63mvr/api/biohead_command.proto2\x94\x08\n\x0e\x42ioHeadService\x12`\n\rSetExpression\x12%.cmvr.api.SetFacialExpression.Request\x1a&.cmvr.api.SetFacialExpression.Feedback\"\x00\x12k\n\x10StreamExpression\x12(.cmvr.api.StreamFacialExpression.Request\x1a).cmvr.api.StreamFacialExpression.Feedback(\x01\x30\x01\x12N\n\x0fGetSystemStatus\x12\x1b.cmvr.api.GetStatus.Request\x1a\x1c.cmvr.api.GetStatus.Feedback\"\x00\x12T\n\rEmergencyStop\x12\x1f.cmvr.api.EmergencyStop.Request\x1a .cmvr.api.EmergencyStop.Feedback\"\x00\x12K\n\nSpeakStart\x12\x1c.cmvr.api.SpeakStart.Request\x1a\x1d.cmvr.api.SpeakStart.Feedback\"\x00\x12H\n\tSpeakStop\x12\x1b.cmvr.api.SpeakStop.Request\x1a\x1c.cmvr.api.SpeakStop.Feedback\"\x00\x12<\n\x05Happy\x12\x17.cmvr.api.Happy.Request\x1a\x18.cmvr.api.Happy.Feedback\"\x00\x12\x45\n\x08Surprise\x12\x1a.cmvr.api.Surprise.Request\x1a\x1b.cmvr.api.Surprise.Feedback\"\x00\x12Z\n\x0f\x45xpressionTired\x12!.cmvr.api.ExpressionTired.Request\x1a\".cmvr.api.ExpressionTired.Feedback\"\x00\x12Z\n\x0f\x45xpressionAngry\x12!.cmvr.api.ExpressionAngry.Request\x1a\".cmvr.api.ExpressionAngry.Feedback\"\x00\x12`\n\x11\x45xpressionSadness\x12#.cmvr.api.ExpressionSadness.Request\x1a$.cmvr.api.ExpressionSadness.Feedback\"\x00\x12W\n\x0e\x45xpressionYawn\x12 .cmvr.api.ExpressionYawn.Request\x1a!.cmvr.api.ExpressionYawn.Feedback\"\x00\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.biohead_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_BIOHEADSERVICE']._serialized_start=77 + _globals['_BIOHEADSERVICE']._serialized_end=1121 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/biohead_service_pb2.pyi b/src/cmvr/api/biohead_service_pb2.pyi new file mode 100644 index 0000000..b20ed1e --- /dev/null +++ b/src/cmvr/api/biohead_service_pb2.pyi @@ -0,0 +1,5 @@ +from cmvr.api import biohead_command_pb2 as _biohead_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/biohead_service_pb2_grpc.py b/src/cmvr/api/biohead_service_pb2_grpc.py new file mode 100644 index 0000000..41adafe --- /dev/null +++ b/src/cmvr/api/biohead_service_pb2_grpc.py @@ -0,0 +1,581 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import biohead_command_pb2 as cmvr_dot_api_dot_biohead__command__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/biohead_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class BioHeadServiceStub: + """生物头部机器人服务接口 + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.SetExpression = channel.unary_unary( + '/cmvr.api.BioHeadService/SetExpression', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.SetFacialExpression.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SetFacialExpression.Feedback.FromString, + _registered_method=True) + self.StreamExpression = channel.stream_stream( + '/cmvr.api.BioHeadService/StreamExpression', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.StreamFacialExpression.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.StreamFacialExpression.Feedback.FromString, + _registered_method=True) + self.GetSystemStatus = channel.unary_unary( + '/cmvr.api.BioHeadService/GetSystemStatus', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.GetStatus.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.GetStatus.Feedback.FromString, + _registered_method=True) + self.EmergencyStop = channel.unary_unary( + '/cmvr.api.BioHeadService/EmergencyStop', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Feedback.FromString, + _registered_method=True) + self.SpeakStart = channel.unary_unary( + '/cmvr.api.BioHeadService/SpeakStart', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Feedback.FromString, + _registered_method=True) + self.SpeakStop = channel.unary_unary( + '/cmvr.api.BioHeadService/SpeakStop', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Feedback.FromString, + _registered_method=True) + self.Happy = channel.unary_unary( + '/cmvr.api.BioHeadService/Happy', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Feedback.FromString, + _registered_method=True) + self.Surprise = channel.unary_unary( + '/cmvr.api.BioHeadService/Surprise', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Feedback.FromString, + _registered_method=True) + self.ExpressionTired = channel.unary_unary( + '/cmvr.api.BioHeadService/ExpressionTired', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Feedback.FromString, + _registered_method=True) + self.ExpressionAngry = channel.unary_unary( + '/cmvr.api.BioHeadService/ExpressionAngry', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Feedback.FromString, + _registered_method=True) + self.ExpressionSadness = channel.unary_unary( + '/cmvr.api.BioHeadService/ExpressionSadness', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Feedback.FromString, + _registered_method=True) + self.ExpressionYawn = channel.unary_unary( + '/cmvr.api.BioHeadService/ExpressionYawn', + request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Feedback.FromString, + _registered_method=True) + + +class BioHeadServiceServicer: + """生物头部机器人服务接口 + """ + + def SetExpression(self, request, context): + """设置面部表情 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StreamExpression(self, request_iterator, context): + """流式表情控制 + rpc StreamExpression(StreamFacialExpression.Request) returns (StreamFacialExpression.Feedback){}; + + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSystemStatus(self, request, context): + """获取状态 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EmergencyStop(self, request, context): + """紧急停止 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SpeakStart(self, request, context): + """开始说话 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SpeakStop(self, request, context): + """停止说话 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Happy(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Surprise(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExpressionTired(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExpressionAngry(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExpressionSadness(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExpressionYawn(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_BioHeadServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'SetExpression': grpc.unary_unary_rpc_method_handler( + servicer.SetExpression, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SetFacialExpression.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.SetFacialExpression.Feedback.SerializeToString, + ), + 'StreamExpression': grpc.stream_stream_rpc_method_handler( + servicer.StreamExpression, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.StreamFacialExpression.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.StreamFacialExpression.Feedback.SerializeToString, + ), + 'GetSystemStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetSystemStatus, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.GetStatus.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.GetStatus.Feedback.SerializeToString, + ), + 'EmergencyStop': grpc.unary_unary_rpc_method_handler( + servicer.EmergencyStop, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Feedback.SerializeToString, + ), + 'SpeakStart': grpc.unary_unary_rpc_method_handler( + servicer.SpeakStart, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Feedback.SerializeToString, + ), + 'SpeakStop': grpc.unary_unary_rpc_method_handler( + servicer.SpeakStop, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Feedback.SerializeToString, + ), + 'Happy': grpc.unary_unary_rpc_method_handler( + servicer.Happy, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Feedback.SerializeToString, + ), + 'Surprise': grpc.unary_unary_rpc_method_handler( + servicer.Surprise, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Feedback.SerializeToString, + ), + 'ExpressionTired': grpc.unary_unary_rpc_method_handler( + servicer.ExpressionTired, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Feedback.SerializeToString, + ), + 'ExpressionAngry': grpc.unary_unary_rpc_method_handler( + servicer.ExpressionAngry, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Feedback.SerializeToString, + ), + 'ExpressionSadness': grpc.unary_unary_rpc_method_handler( + servicer.ExpressionSadness, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Feedback.SerializeToString, + ), + 'ExpressionYawn': grpc.unary_unary_rpc_method_handler( + servicer.ExpressionYawn, + request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Request.FromString, + response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Feedback.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.BioHeadService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.BioHeadService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class BioHeadService: + """生物头部机器人服务接口 + """ + + @staticmethod + def SetExpression(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/SetExpression', + cmvr_dot_api_dot_biohead__command__pb2.SetFacialExpression.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.SetFacialExpression.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StreamExpression(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream( + request_iterator, + target, + '/cmvr.api.BioHeadService/StreamExpression', + cmvr_dot_api_dot_biohead__command__pb2.StreamFacialExpression.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.StreamFacialExpression.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetSystemStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/GetSystemStatus', + cmvr_dot_api_dot_biohead__command__pb2.GetStatus.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.GetStatus.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def EmergencyStop(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/EmergencyStop', + cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SpeakStart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/SpeakStart', + cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SpeakStop(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/SpeakStop', + cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Happy(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/Happy', + cmvr_dot_api_dot_biohead__command__pb2.Happy.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.Happy.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Surprise(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/Surprise', + cmvr_dot_api_dot_biohead__command__pb2.Surprise.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.Surprise.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ExpressionTired(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/ExpressionTired', + cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ExpressionAngry(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/ExpressionAngry', + cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ExpressionSadness(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/ExpressionSadness', + cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ExpressionYawn(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.BioHeadService/ExpressionYawn', + cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Request.SerializeToString, + cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/camera_command_pb2.py b/src/cmvr/api/camera_command_pb2.py new file mode 100644 index 0000000..2b194f2 --- /dev/null +++ b/src/cmvr/api/camera_command_pb2.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/camera_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/camera_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/camera_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"\xd7\x01\n\tFrameData\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\r\n\x05width\x18\x02 \x01(\x05\x12\x0e\n\x06height\x18\x03 \x01(\x05\x12+\n\x04type\x18\x04 \x01(\x0e\x32\x1d.cmvr.api.FrameData.FrameType\x12\r\n\x05\x63odec\x18\x05 \x01(\t\x12\x14\n\x0cis_key_frame\x18\x06 \x01(\x08\"K\n\tFrameType\x12\x08\n\x04U8C1\x10\x00\x12\t\n\x05U16C1\x10\x01\x12\x08\n\x04U8C3\x10\x02\x12\t\n\x05U16C3\x10\x03\x12\t\n\x05\x46\x31\x36\x43\x31\x10\x04\x12\t\n\x05\x46\x33\x32\x43\x31\x10\x05\"V\n\x10\x43\x61meraIntrinsics\x12\n\n\x02\x63x\x18\x01 \x01(\x02\x12\n\n\x02\x63y\x18\x02 \x01(\x02\x12\n\n\x02\x66x\x18\x03 \x01(\x02\x12\n\n\x02\x66y\x18\x04 \x01(\x02\x12\x12\n\x06\x63oeffs\x18\x05 \x03(\x02\x42\x02\x10\x01\"\xb9\x01\n\x0b\x43\x61meraState\x12\x16\n\x0eis_initialized\x18\x01 \x01(\x08\x12\x11\n\tis_opened\x18\x02 \x01(\x08\x12\x14\n\x0cis_streaming\x18\x03 \x01(\x08\x12\x14\n\x0cis_recording\x18\x04 \x01(\x08\x12\x10\n\x08is_error\x18\x05 \x01(\x08\x12\x15\n\rerror_message\x18\x06 \x01(\t\x12\x0b\n\x03\x66ps\x18\x07 \x01(\x05\x12\r\n\x05width\x18\x08 \x01(\x05\x12\x0e\n\x06height\x18\t \x01(\x05\"\xb7\x01\n\x15GetCameraStateCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x62\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12$\n\x05state\x18\x02 \x01(\x0b\x32\x15.cmvr.api.CameraState\"\x8e\x01\n\x12StartCameraCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8d\x01\n\x11StopCameraCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xe9\x01\n\x12GetRGBImageCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x96\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12(\n\x0b\x63olor_frame\x18\x02 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12.\n\nintrinsics\x18\x03 \x01(\x0b\x32\x1a.cmvr.api.CameraIntrinsics\"\xeb\x01\n\x14GetDepthImageCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x96\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12(\n\x0b\x64\x65pth_frame\x18\x02 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12.\n\nintrinsics\x18\x03 \x01(\x0b\x32\x1a.cmvr.api.CameraIntrinsics\"\x95\x02\n\x14GetRGBDImagesCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\xc0\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12(\n\x0b\x63olor_frame\x18\x02 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12(\n\x0b\x64\x65pth_frame\x18\x03 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12.\n\nintrinsics\x18\x04 \x01(\x0b\x32\x1a.cmvr.api.CameraIntrinsics\"\xab\x01\n\x1bStartCameraRecordingCommand\x1aN\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\nvideo_path\x18\x02 \x01(\t\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x96\x01\n\x1aStopCameraRecordingCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8c\x02\n\x18GetRGBImageStreamCommand\x1aG\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0b\n\x03\x65of\x18\x02 \x01(\x08\x1a\xa6\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12(\n\x0b\x63olor_frame\x18\x02 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12.\n\nintrinsics\x18\x03 \x01(\x0b\x32\x1a.cmvr.api.CameraIntrinsics\x12\x0e\n\x06seq_no\x18\x04 \x01(\x05\"\x8e\x02\n\x1aGetDepthImageStreamCommand\x1aG\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0b\n\x03\x65of\x18\x02 \x01(\x08\x1a\xa6\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12(\n\x0b\x64\x65pth_frame\x18\x02 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12.\n\nintrinsics\x18\x03 \x01(\x0b\x32\x1a.cmvr.api.CameraIntrinsics\x12\x0e\n\x06seq_no\x18\x04 \x01(\x05\"\xb8\x02\n\x1aGetRGBDImagesStreamCommand\x1aG\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0b\n\x03\x65of\x18\x02 \x01(\x08\x1a\xd0\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12(\n\x0b\x63olor_frame\x18\x02 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12(\n\x0b\x64\x65pth_frame\x18\x03 \x01(\x0b\x32\x13.cmvr.api.FrameData\x12.\n\nintrinsics\x18\x04 \x01(\x0b\x32\x1a.cmvr.api.CameraIntrinsics\x12\x0e\n\x06seq_no\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.camera_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_CAMERAINTRINSICS'].fields_by_name['coeffs']._loaded_options = None + _globals['_CAMERAINTRINSICS'].fields_by_name['coeffs']._serialized_options = b'\020\001' + _globals['_FRAMEDATA']._serialized_start=67 + _globals['_FRAMEDATA']._serialized_end=282 + _globals['_FRAMEDATA_FRAMETYPE']._serialized_start=207 + _globals['_FRAMEDATA_FRAMETYPE']._serialized_end=282 + _globals['_CAMERAINTRINSICS']._serialized_start=284 + _globals['_CAMERAINTRINSICS']._serialized_end=370 + _globals['_CAMERASTATE']._serialized_start=373 + _globals['_CAMERASTATE']._serialized_end=558 + _globals['_GETCAMERASTATECOMMAND']._serialized_start=561 + _globals['_GETCAMERASTATECOMMAND']._serialized_end=744 + _globals['_GETCAMERASTATECOMMAND_REQUEST']._serialized_start=586 + _globals['_GETCAMERASTATECOMMAND_REQUEST']._serialized_end=644 + _globals['_GETCAMERASTATECOMMAND_FEEDBACK']._serialized_start=646 + _globals['_GETCAMERASTATECOMMAND_FEEDBACK']._serialized_end=744 + _globals['_STARTCAMERACOMMAND']._serialized_start=747 + _globals['_STARTCAMERACOMMAND']._serialized_end=889 + _globals['_STARTCAMERACOMMAND_REQUEST']._serialized_start=586 + _globals['_STARTCAMERACOMMAND_REQUEST']._serialized_end=644 + _globals['_STARTCAMERACOMMAND_FEEDBACK']._serialized_start=646 + _globals['_STARTCAMERACOMMAND_FEEDBACK']._serialized_end=706 + _globals['_STOPCAMERACOMMAND']._serialized_start=892 + _globals['_STOPCAMERACOMMAND']._serialized_end=1033 + _globals['_STOPCAMERACOMMAND_REQUEST']._serialized_start=586 + _globals['_STOPCAMERACOMMAND_REQUEST']._serialized_end=644 + _globals['_STOPCAMERACOMMAND_FEEDBACK']._serialized_start=646 + _globals['_STOPCAMERACOMMAND_FEEDBACK']._serialized_end=706 + _globals['_GETRGBIMAGECOMMAND']._serialized_start=1036 + _globals['_GETRGBIMAGECOMMAND']._serialized_end=1269 + _globals['_GETRGBIMAGECOMMAND_REQUEST']._serialized_start=586 + _globals['_GETRGBIMAGECOMMAND_REQUEST']._serialized_end=644 + _globals['_GETRGBIMAGECOMMAND_FEEDBACK']._serialized_start=1119 + _globals['_GETRGBIMAGECOMMAND_FEEDBACK']._serialized_end=1269 + _globals['_GETDEPTHIMAGECOMMAND']._serialized_start=1272 + _globals['_GETDEPTHIMAGECOMMAND']._serialized_end=1507 + _globals['_GETDEPTHIMAGECOMMAND_REQUEST']._serialized_start=586 + _globals['_GETDEPTHIMAGECOMMAND_REQUEST']._serialized_end=644 + _globals['_GETDEPTHIMAGECOMMAND_FEEDBACK']._serialized_start=1357 + _globals['_GETDEPTHIMAGECOMMAND_FEEDBACK']._serialized_end=1507 + _globals['_GETRGBDIMAGESCOMMAND']._serialized_start=1510 + _globals['_GETRGBDIMAGESCOMMAND']._serialized_end=1787 + _globals['_GETRGBDIMAGESCOMMAND_REQUEST']._serialized_start=586 + _globals['_GETRGBDIMAGESCOMMAND_REQUEST']._serialized_end=644 + _globals['_GETRGBDIMAGESCOMMAND_FEEDBACK']._serialized_start=1595 + _globals['_GETRGBDIMAGESCOMMAND_FEEDBACK']._serialized_end=1787 + _globals['_STARTCAMERARECORDINGCOMMAND']._serialized_start=1790 + _globals['_STARTCAMERARECORDINGCOMMAND']._serialized_end=1961 + _globals['_STARTCAMERARECORDINGCOMMAND_REQUEST']._serialized_start=1821 + _globals['_STARTCAMERARECORDINGCOMMAND_REQUEST']._serialized_end=1899 + _globals['_STARTCAMERARECORDINGCOMMAND_FEEDBACK']._serialized_start=646 + _globals['_STARTCAMERARECORDINGCOMMAND_FEEDBACK']._serialized_end=706 + _globals['_STOPCAMERARECORDINGCOMMAND']._serialized_start=1964 + _globals['_STOPCAMERARECORDINGCOMMAND']._serialized_end=2114 + _globals['_STOPCAMERARECORDINGCOMMAND_REQUEST']._serialized_start=586 + _globals['_STOPCAMERARECORDINGCOMMAND_REQUEST']._serialized_end=644 + _globals['_STOPCAMERARECORDINGCOMMAND_FEEDBACK']._serialized_start=646 + _globals['_STOPCAMERARECORDINGCOMMAND_FEEDBACK']._serialized_end=706 + _globals['_GETRGBIMAGESTREAMCOMMAND']._serialized_start=2117 + _globals['_GETRGBIMAGESTREAMCOMMAND']._serialized_end=2385 + _globals['_GETRGBIMAGESTREAMCOMMAND_REQUEST']._serialized_start=2145 + _globals['_GETRGBIMAGESTREAMCOMMAND_REQUEST']._serialized_end=2216 + _globals['_GETRGBIMAGESTREAMCOMMAND_FEEDBACK']._serialized_start=2219 + _globals['_GETRGBIMAGESTREAMCOMMAND_FEEDBACK']._serialized_end=2385 + _globals['_GETDEPTHIMAGESTREAMCOMMAND']._serialized_start=2388 + _globals['_GETDEPTHIMAGESTREAMCOMMAND']._serialized_end=2658 + _globals['_GETDEPTHIMAGESTREAMCOMMAND_REQUEST']._serialized_start=2145 + _globals['_GETDEPTHIMAGESTREAMCOMMAND_REQUEST']._serialized_end=2216 + _globals['_GETDEPTHIMAGESTREAMCOMMAND_FEEDBACK']._serialized_start=2492 + _globals['_GETDEPTHIMAGESTREAMCOMMAND_FEEDBACK']._serialized_end=2658 + _globals['_GETRGBDIMAGESSTREAMCOMMAND']._serialized_start=2661 + _globals['_GETRGBDIMAGESSTREAMCOMMAND']._serialized_end=2973 + _globals['_GETRGBDIMAGESSTREAMCOMMAND_REQUEST']._serialized_start=2145 + _globals['_GETRGBDIMAGESSTREAMCOMMAND_REQUEST']._serialized_end=2216 + _globals['_GETRGBDIMAGESSTREAMCOMMAND_FEEDBACK']._serialized_start=2765 + _globals['_GETRGBDIMAGESSTREAMCOMMAND_FEEDBACK']._serialized_end=2973 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/camera_command_pb2.pyi b/src/cmvr/api/camera_command_pb2.pyi new file mode 100644 index 0000000..2a3bc1f --- /dev/null +++ b/src/cmvr/api/camera_command_pb2.pyi @@ -0,0 +1,273 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class FrameData(_message.Message): + __slots__ = ("data", "width", "height", "type", "codec", "is_key_frame") + class FrameType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + U8C1: _ClassVar[FrameData.FrameType] + U16C1: _ClassVar[FrameData.FrameType] + U8C3: _ClassVar[FrameData.FrameType] + U16C3: _ClassVar[FrameData.FrameType] + F16C1: _ClassVar[FrameData.FrameType] + F32C1: _ClassVar[FrameData.FrameType] + U8C1: FrameData.FrameType + U16C1: FrameData.FrameType + U8C3: FrameData.FrameType + U16C3: FrameData.FrameType + F16C1: FrameData.FrameType + F32C1: FrameData.FrameType + DATA_FIELD_NUMBER: _ClassVar[int] + WIDTH_FIELD_NUMBER: _ClassVar[int] + HEIGHT_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + CODEC_FIELD_NUMBER: _ClassVar[int] + IS_KEY_FRAME_FIELD_NUMBER: _ClassVar[int] + data: bytes + width: int + height: int + type: FrameData.FrameType + codec: str + is_key_frame: bool + def __init__(self, data: _Optional[bytes] = ..., width: _Optional[int] = ..., height: _Optional[int] = ..., type: _Optional[_Union[FrameData.FrameType, str]] = ..., codec: _Optional[str] = ..., is_key_frame: _Optional[bool] = ...) -> None: ... + +class CameraIntrinsics(_message.Message): + __slots__ = ("cx", "cy", "fx", "fy", "coeffs") + CX_FIELD_NUMBER: _ClassVar[int] + CY_FIELD_NUMBER: _ClassVar[int] + FX_FIELD_NUMBER: _ClassVar[int] + FY_FIELD_NUMBER: _ClassVar[int] + COEFFS_FIELD_NUMBER: _ClassVar[int] + cx: float + cy: float + fx: float + fy: float + coeffs: _containers.RepeatedScalarFieldContainer[float] + def __init__(self, cx: _Optional[float] = ..., cy: _Optional[float] = ..., fx: _Optional[float] = ..., fy: _Optional[float] = ..., coeffs: _Optional[_Iterable[float]] = ...) -> None: ... + +class CameraState(_message.Message): + __slots__ = ("is_initialized", "is_opened", "is_streaming", "is_recording", "is_error", "error_message", "fps", "width", "height") + IS_INITIALIZED_FIELD_NUMBER: _ClassVar[int] + IS_OPENED_FIELD_NUMBER: _ClassVar[int] + IS_STREAMING_FIELD_NUMBER: _ClassVar[int] + IS_RECORDING_FIELD_NUMBER: _ClassVar[int] + IS_ERROR_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + FPS_FIELD_NUMBER: _ClassVar[int] + WIDTH_FIELD_NUMBER: _ClassVar[int] + HEIGHT_FIELD_NUMBER: _ClassVar[int] + is_initialized: bool + is_opened: bool + is_streaming: bool + is_recording: bool + is_error: bool + error_message: str + fps: int + width: int + height: int + def __init__(self, is_initialized: _Optional[bool] = ..., is_opened: _Optional[bool] = ..., is_streaming: _Optional[bool] = ..., is_recording: _Optional[bool] = ..., is_error: _Optional[bool] = ..., error_message: _Optional[str] = ..., fps: _Optional[int] = ..., width: _Optional[int] = ..., height: _Optional[int] = ...) -> None: ... + +class GetCameraStateCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "state") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + state: CameraState + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., state: _Optional[_Union[CameraState, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StartCameraCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StopCameraCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetRGBImageCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "color_frame", "intrinsics") + HEADER_FIELD_NUMBER: _ClassVar[int] + COLOR_FRAME_FIELD_NUMBER: _ClassVar[int] + INTRINSICS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + color_frame: FrameData + intrinsics: CameraIntrinsics + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., color_frame: _Optional[_Union[FrameData, _Mapping]] = ..., intrinsics: _Optional[_Union[CameraIntrinsics, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetDepthImageCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "depth_frame", "intrinsics") + HEADER_FIELD_NUMBER: _ClassVar[int] + DEPTH_FRAME_FIELD_NUMBER: _ClassVar[int] + INTRINSICS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + depth_frame: FrameData + intrinsics: CameraIntrinsics + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., depth_frame: _Optional[_Union[FrameData, _Mapping]] = ..., intrinsics: _Optional[_Union[CameraIntrinsics, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetRGBDImagesCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "color_frame", "depth_frame", "intrinsics") + HEADER_FIELD_NUMBER: _ClassVar[int] + COLOR_FRAME_FIELD_NUMBER: _ClassVar[int] + DEPTH_FRAME_FIELD_NUMBER: _ClassVar[int] + INTRINSICS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + color_frame: FrameData + depth_frame: FrameData + intrinsics: CameraIntrinsics + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., color_frame: _Optional[_Union[FrameData, _Mapping]] = ..., depth_frame: _Optional[_Union[FrameData, _Mapping]] = ..., intrinsics: _Optional[_Union[CameraIntrinsics, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StartCameraRecordingCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "video_path") + HEADER_FIELD_NUMBER: _ClassVar[int] + VIDEO_PATH_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + video_path: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., video_path: _Optional[str] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StopCameraRecordingCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetRGBImageStreamCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "eof") + HEADER_FIELD_NUMBER: _ClassVar[int] + EOF_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + eof: bool + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., eof: _Optional[bool] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "color_frame", "intrinsics", "seq_no") + HEADER_FIELD_NUMBER: _ClassVar[int] + COLOR_FRAME_FIELD_NUMBER: _ClassVar[int] + INTRINSICS_FIELD_NUMBER: _ClassVar[int] + SEQ_NO_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + color_frame: FrameData + intrinsics: CameraIntrinsics + seq_no: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., color_frame: _Optional[_Union[FrameData, _Mapping]] = ..., intrinsics: _Optional[_Union[CameraIntrinsics, _Mapping]] = ..., seq_no: _Optional[int] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetDepthImageStreamCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "eof") + HEADER_FIELD_NUMBER: _ClassVar[int] + EOF_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + eof: bool + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., eof: _Optional[bool] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "depth_frame", "intrinsics", "seq_no") + HEADER_FIELD_NUMBER: _ClassVar[int] + DEPTH_FRAME_FIELD_NUMBER: _ClassVar[int] + INTRINSICS_FIELD_NUMBER: _ClassVar[int] + SEQ_NO_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + depth_frame: FrameData + intrinsics: CameraIntrinsics + seq_no: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., depth_frame: _Optional[_Union[FrameData, _Mapping]] = ..., intrinsics: _Optional[_Union[CameraIntrinsics, _Mapping]] = ..., seq_no: _Optional[int] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetRGBDImagesStreamCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "eof") + HEADER_FIELD_NUMBER: _ClassVar[int] + EOF_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + eof: bool + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., eof: _Optional[bool] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "color_frame", "depth_frame", "intrinsics", "seq_no") + HEADER_FIELD_NUMBER: _ClassVar[int] + COLOR_FRAME_FIELD_NUMBER: _ClassVar[int] + DEPTH_FRAME_FIELD_NUMBER: _ClassVar[int] + INTRINSICS_FIELD_NUMBER: _ClassVar[int] + SEQ_NO_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + color_frame: FrameData + depth_frame: FrameData + intrinsics: CameraIntrinsics + seq_no: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., color_frame: _Optional[_Union[FrameData, _Mapping]] = ..., depth_frame: _Optional[_Union[FrameData, _Mapping]] = ..., intrinsics: _Optional[_Union[CameraIntrinsics, _Mapping]] = ..., seq_no: _Optional[int] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/camera_command_pb2_grpc.py b/src/cmvr/api/camera_command_pb2_grpc.py new file mode 100644 index 0000000..3be0c2d --- /dev/null +++ b/src/cmvr/api/camera_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/camera_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/camera_service_pb2.py b/src/cmvr/api/camera_service_pb2.py new file mode 100644 index 0000000..39b45c2 --- /dev/null +++ b/src/cmvr/api/camera_service_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/camera_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/camera_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import camera_command_pb2 as cmvr_dot_api_dot_camera__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/camera_service.proto\x12\x08\x63mvr.api\x1a\x1d\x63mvr/api/camera_command.proto2\x9b\t\n\rCameraService\x12`\n\tGetStatus\x12\'.cmvr.api.GetCameraStateCommand.Request\x1a(.cmvr.api.GetCameraStateCommand.Feedback\"\x00\x12\\\n\x0bStartCamera\x12$.cmvr.api.StartCameraCommand.Request\x1a%.cmvr.api.StartCameraCommand.Feedback\"\x00\x12Y\n\nStopCamera\x12#.cmvr.api.StopCameraCommand.Request\x1a$.cmvr.api.StopCameraCommand.Feedback\"\x00\x12\\\n\x0bGetRGBImage\x12$.cmvr.api.GetRGBImageCommand.Request\x1a%.cmvr.api.GetRGBImageCommand.Feedback\"\x00\x12\x62\n\rGetDepthImage\x12&.cmvr.api.GetDepthImageCommand.Request\x1a\'.cmvr.api.GetDepthImageCommand.Feedback\"\x00\x12\x62\n\rGetRGBDImages\x12&.cmvr.api.GetRGBDImagesCommand.Request\x1a\'.cmvr.api.GetRGBDImagesCommand.Feedback\"\x00\x12q\n\x0eStartRecording\x12-.cmvr.api.StartCameraRecordingCommand.Request\x1a..cmvr.api.StartCameraRecordingCommand.Feedback\"\x00\x12n\n\rStopRecording\x12,.cmvr.api.StopCameraRecordingCommand.Request\x1a-.cmvr.api.StopCameraRecordingCommand.Feedback\"\x00\x12r\n\x11GetRGBImageStream\x12*.cmvr.api.GetRGBImageStreamCommand.Request\x1a+.cmvr.api.GetRGBImageStreamCommand.Feedback\"\x00(\x01\x30\x01\x12x\n\x13GetDepthImageStream\x12,.cmvr.api.GetDepthImageStreamCommand.Request\x1a-.cmvr.api.GetDepthImageStreamCommand.Feedback\"\x00(\x01\x30\x01\x12x\n\x13GetRGBDImagesStream\x12,.cmvr.api.GetRGBDImagesStreamCommand.Request\x1a-.cmvr.api.GetRGBDImagesStreamCommand.Feedback\"\x00(\x01\x30\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.camera_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_CAMERASERVICE']._serialized_start=75 + _globals['_CAMERASERVICE']._serialized_end=1254 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/camera_service_pb2.pyi b/src/cmvr/api/camera_service_pb2.pyi new file mode 100644 index 0000000..5496655 --- /dev/null +++ b/src/cmvr/api/camera_service_pb2.pyi @@ -0,0 +1,5 @@ +from cmvr.api import camera_command_pb2 as _camera_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/camera_service_pb2_grpc.py b/src/cmvr/api/camera_service_pb2_grpc.py new file mode 100644 index 0000000..cb89e35 --- /dev/null +++ b/src/cmvr/api/camera_service_pb2_grpc.py @@ -0,0 +1,527 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import camera_command_pb2 as cmvr_dot_api_dot_camera__command__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/camera_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class CameraServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetStatus = channel.unary_unary( + '/cmvr.api.CameraService/GetStatus', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.GetCameraStateCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetCameraStateCommand.Feedback.FromString, + _registered_method=True) + self.StartCamera = channel.unary_unary( + '/cmvr.api.CameraService/StartCamera', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraCommand.Feedback.FromString, + _registered_method=True) + self.StopCamera = channel.unary_unary( + '/cmvr.api.CameraService/StopCamera', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraCommand.Feedback.FromString, + _registered_method=True) + self.GetRGBImage = channel.unary_unary( + '/cmvr.api.CameraService/GetRGBImage', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageCommand.Feedback.FromString, + _registered_method=True) + self.GetDepthImage = channel.unary_unary( + '/cmvr.api.CameraService/GetDepthImage', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageCommand.Feedback.FromString, + _registered_method=True) + self.GetRGBDImages = channel.unary_unary( + '/cmvr.api.CameraService/GetRGBDImages', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesCommand.Feedback.FromString, + _registered_method=True) + self.StartRecording = channel.unary_unary( + '/cmvr.api.CameraService/StartRecording', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraRecordingCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraRecordingCommand.Feedback.FromString, + _registered_method=True) + self.StopRecording = channel.unary_unary( + '/cmvr.api.CameraService/StopRecording', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraRecordingCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraRecordingCommand.Feedback.FromString, + _registered_method=True) + self.GetRGBImageStream = channel.stream_stream( + '/cmvr.api.CameraService/GetRGBImageStream', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageStreamCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageStreamCommand.Feedback.FromString, + _registered_method=True) + self.GetDepthImageStream = channel.stream_stream( + '/cmvr.api.CameraService/GetDepthImageStream', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageStreamCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageStreamCommand.Feedback.FromString, + _registered_method=True) + self.GetRGBDImagesStream = channel.stream_stream( + '/cmvr.api.CameraService/GetRGBDImagesStream', + request_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesStreamCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesStreamCommand.Feedback.FromString, + _registered_method=True) + + +class CameraServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def GetStatus(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StartCamera(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StopCamera(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetRGBImage(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetDepthImage(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetRGBDImages(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StartRecording(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StopRecording(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetRGBImageStream(self, request_iterator, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetDepthImageStream(self, request_iterator, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetRGBDImagesStream(self, request_iterator, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CameraServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetStatus, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetCameraStateCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.GetCameraStateCommand.Feedback.SerializeToString, + ), + 'StartCamera': grpc.unary_unary_rpc_method_handler( + servicer.StartCamera, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraCommand.Feedback.SerializeToString, + ), + 'StopCamera': grpc.unary_unary_rpc_method_handler( + servicer.StopCamera, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraCommand.Feedback.SerializeToString, + ), + 'GetRGBImage': grpc.unary_unary_rpc_method_handler( + servicer.GetRGBImage, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageCommand.Feedback.SerializeToString, + ), + 'GetDepthImage': grpc.unary_unary_rpc_method_handler( + servicer.GetDepthImage, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageCommand.Feedback.SerializeToString, + ), + 'GetRGBDImages': grpc.unary_unary_rpc_method_handler( + servicer.GetRGBDImages, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesCommand.Feedback.SerializeToString, + ), + 'StartRecording': grpc.unary_unary_rpc_method_handler( + servicer.StartRecording, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraRecordingCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.StartCameraRecordingCommand.Feedback.SerializeToString, + ), + 'StopRecording': grpc.unary_unary_rpc_method_handler( + servicer.StopRecording, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraRecordingCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.StopCameraRecordingCommand.Feedback.SerializeToString, + ), + 'GetRGBImageStream': grpc.stream_stream_rpc_method_handler( + servicer.GetRGBImageStream, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageStreamCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBImageStreamCommand.Feedback.SerializeToString, + ), + 'GetDepthImageStream': grpc.stream_stream_rpc_method_handler( + servicer.GetDepthImageStream, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageStreamCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.GetDepthImageStreamCommand.Feedback.SerializeToString, + ), + 'GetRGBDImagesStream': grpc.stream_stream_rpc_method_handler( + servicer.GetRGBDImagesStream, + request_deserializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesStreamCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesStreamCommand.Feedback.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.CameraService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.CameraService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class CameraService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/GetStatus', + cmvr_dot_api_dot_camera__command__pb2.GetCameraStateCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.GetCameraStateCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StartCamera(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/StartCamera', + cmvr_dot_api_dot_camera__command__pb2.StartCameraCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.StartCameraCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StopCamera(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/StopCamera', + cmvr_dot_api_dot_camera__command__pb2.StopCameraCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.StopCameraCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetRGBImage(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/GetRGBImage', + cmvr_dot_api_dot_camera__command__pb2.GetRGBImageCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.GetRGBImageCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetDepthImage(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/GetDepthImage', + cmvr_dot_api_dot_camera__command__pb2.GetDepthImageCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.GetDepthImageCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetRGBDImages(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/GetRGBDImages', + cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StartRecording(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/StartRecording', + cmvr_dot_api_dot_camera__command__pb2.StartCameraRecordingCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.StartCameraRecordingCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StopRecording(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.CameraService/StopRecording', + cmvr_dot_api_dot_camera__command__pb2.StopCameraRecordingCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.StopCameraRecordingCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetRGBImageStream(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream( + request_iterator, + target, + '/cmvr.api.CameraService/GetRGBImageStream', + cmvr_dot_api_dot_camera__command__pb2.GetRGBImageStreamCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.GetRGBImageStreamCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetDepthImageStream(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream( + request_iterator, + target, + '/cmvr.api.CameraService/GetDepthImageStream', + cmvr_dot_api_dot_camera__command__pb2.GetDepthImageStreamCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.GetDepthImageStreamCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetRGBDImagesStream(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream( + request_iterator, + target, + '/cmvr.api.CameraService/GetRGBDImagesStream', + cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesStreamCommand.Request.SerializeToString, + cmvr_dot_api_dot_camera__command__pb2.GetRGBDImagesStreamCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/common_pb2.py b/src/cmvr/api/common_pb2.py new file mode 100644 index 0000000..261a618 --- /dev/null +++ b/src/cmvr/api/common_pb2.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/common.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/common.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x63mvr/api/common.proto\x12\x08\x63mvr.api\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb8\x01\n\x0f\x44\x65viceLifecycle\x12\x32\n\x05state\x18\x01 \x01(\x0e\x32#.cmvr.api.DeviceLifecycle.Lifecycle\"q\n\tLifecycle\x12\x0e\n\nSTATE_INIT\x10\x00\x12\x0f\n\x0bSTATE_READY\x10\x01\x12\x11\n\rSTATE_RUNNING\x10\x02\x12\x0f\n\x0bSTATE_ERROR\x10\x03\x12\x0f\n\x0bSTATE_ESTOP\x10\x04\x12\x0e\n\nSTATE_STOP\x10\x05\"\xbf\x01\n\rCommandHeader\x1aK\n\x07Request\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12-\n\ttimestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a\x61\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12-\n\ttimestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa2\x01\n\x0b\x43onfigParam\x12\x12\n\nparam_name\x18\x01 \x01(\t\x12\x13\n\tint_value\x18\x02 \x01(\x05H\x00\x12\x16\n\x0c\x64ouble_value\x18\x03 \x01(\x01H\x00\x12\x16\n\x0cstring_value\x18\x04 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x05 \x01(\x08H\x00\x12\x15\n\x0b\x62ytes_value\x18\x06 \x01(\x0cH\x00\x42\r\n\x0bparam_valueb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.common_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_DEVICELIFECYCLE']._serialized_start=69 + _globals['_DEVICELIFECYCLE']._serialized_end=253 + _globals['_DEVICELIFECYCLE_LIFECYCLE']._serialized_start=140 + _globals['_DEVICELIFECYCLE_LIFECYCLE']._serialized_end=253 + _globals['_COMMANDHEADER']._serialized_start=256 + _globals['_COMMANDHEADER']._serialized_end=447 + _globals['_COMMANDHEADER_REQUEST']._serialized_start=273 + _globals['_COMMANDHEADER_REQUEST']._serialized_end=348 + _globals['_COMMANDHEADER_FEEDBACK']._serialized_start=350 + _globals['_COMMANDHEADER_FEEDBACK']._serialized_end=447 + _globals['_CONFIGPARAM']._serialized_start=450 + _globals['_CONFIGPARAM']._serialized_end=612 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/common_pb2.pyi b/src/cmvr/api/common_pb2.pyi new file mode 100644 index 0000000..183eec5 --- /dev/null +++ b/src/cmvr/api/common_pb2.pyi @@ -0,0 +1,66 @@ +import datetime + +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class DeviceLifecycle(_message.Message): + __slots__ = ("state",) + class Lifecycle(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + STATE_INIT: _ClassVar[DeviceLifecycle.Lifecycle] + STATE_READY: _ClassVar[DeviceLifecycle.Lifecycle] + STATE_RUNNING: _ClassVar[DeviceLifecycle.Lifecycle] + STATE_ERROR: _ClassVar[DeviceLifecycle.Lifecycle] + STATE_ESTOP: _ClassVar[DeviceLifecycle.Lifecycle] + STATE_STOP: _ClassVar[DeviceLifecycle.Lifecycle] + STATE_INIT: DeviceLifecycle.Lifecycle + STATE_READY: DeviceLifecycle.Lifecycle + STATE_RUNNING: DeviceLifecycle.Lifecycle + STATE_ERROR: DeviceLifecycle.Lifecycle + STATE_ESTOP: DeviceLifecycle.Lifecycle + STATE_STOP: DeviceLifecycle.Lifecycle + STATE_FIELD_NUMBER: _ClassVar[int] + state: DeviceLifecycle.Lifecycle + def __init__(self, state: _Optional[_Union[DeviceLifecycle.Lifecycle, str]] = ...) -> None: ... + +class CommandHeader(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("device_id", "timestamp") + DEVICE_ID_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + device_id: str + timestamp: _timestamp_pb2.Timestamp + def __init__(self, device_id: _Optional[str] = ..., timestamp: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("success", "error_message", "timestamp") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + success: bool + error_message: str + timestamp: _timestamp_pb2.Timestamp + def __init__(self, success: _Optional[bool] = ..., error_message: _Optional[str] = ..., timestamp: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ConfigParam(_message.Message): + __slots__ = ("param_name", "int_value", "double_value", "string_value", "bool_value", "bytes_value") + PARAM_NAME_FIELD_NUMBER: _ClassVar[int] + INT_VALUE_FIELD_NUMBER: _ClassVar[int] + DOUBLE_VALUE_FIELD_NUMBER: _ClassVar[int] + STRING_VALUE_FIELD_NUMBER: _ClassVar[int] + BOOL_VALUE_FIELD_NUMBER: _ClassVar[int] + BYTES_VALUE_FIELD_NUMBER: _ClassVar[int] + param_name: str + int_value: int + double_value: float + string_value: str + bool_value: bool + bytes_value: bytes + def __init__(self, param_name: _Optional[str] = ..., int_value: _Optional[int] = ..., double_value: _Optional[float] = ..., string_value: _Optional[str] = ..., bool_value: _Optional[bool] = ..., bytes_value: _Optional[bytes] = ...) -> None: ... diff --git a/src/cmvr/api/common_pb2_grpc.py b/src/cmvr/api/common_pb2_grpc.py new file mode 100644 index 0000000..8c1ed82 --- /dev/null +++ b/src/cmvr/api/common_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/common_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/dexhand_command_pb2.py b/src/cmvr/api/dexhand_command_pb2.py new file mode 100644 index 0000000..67c3e88 --- /dev/null +++ b/src/cmvr/api/dexhand_command_pb2.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/dexhand_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/dexhand_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/dexhand_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\")\n\x0c\x46reedomValue\x12\n\n\x02id\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x02\"\xa9\x01\n\x0c\x46reedomState\x12\x0e\n\x06\x64of_id\x18\x01 \x01(\x05\x12\r\n\x05\x61ngle\x18\x02 \x01(\x05\x12\r\n\x05speed\x18\x03 \x01(\x05\x12\r\n\x05\x66orce\x18\x04 \x01(\x05\x12\x10\n\x08position\x18\x05 \x01(\x05\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x05\x12\x13\n\x0btemperature\x18\x07 \x01(\x05\x12\r\n\x05\x65rror\x18\x08 \x01(\x05\x12\x15\n\rerror_message\x18\t \x03(\t\"\x90\x03\n\nSensorData\x12\x34\n\x0b\x66inger_type\x18\x04 \x01(\x0e\x32\x1f.cmvr.api.SensorData.FingerType\x12\x30\n\tpart_type\x18\x05 \x01(\x0e\x32\x1d.cmvr.api.SensorData.PartType\x12\x13\n\x0bsensor_name\x18\x06 \x01(\t\x12*\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x1c.cmvr.api.SensorData.RowData\x12\x0c\n\x04rows\x18\x02 \x01(\x05\x12\x0c\n\x04\x63ols\x18\x03 \x01(\x05\x1a\x1d\n\x07RowData\x12\x12\n\x06values\x18\x01 \x03(\x05\x42\x02\x10\x01\"T\n\nFingerType\x12\t\n\x05PINKY\x10\x00\x12\x08\n\x04RING\x10\x01\x12\x11\n\rMIDDLE_FINGER\x10\x02\x12\t\n\x05INDEX\x10\x03\x12\t\n\x05THUMB\x10\x04\x12\x08\n\x04PALM\x10\x05\"H\n\x08PartType\x12\x07\n\x03TIP\x10\x00\x12\n\n\x06\x46INGER\x10\x01\x12\x07\n\x03PAD\x10\x02\x12\x10\n\x0cTHUMB_MIDDLE\x10\x03\x12\x0c\n\x08PALM_PAD\x10\x04\"M\n\x0c\x44\x65xHandState\x12\x16\n\x0eis_initialized\x18\x01 \x01(\x08\x12%\n\x05hands\x18\x02 \x03(\x0b\x32\x16.cmvr.api.FreedomState\"\xb9\x01\n\x16GetDexHandStateCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x63\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12%\n\x05state\x18\x02 \x01(\x0b\x32\x16.cmvr.api.DexHandState\"\xbe\x01\n\x1aSetDexHandPositionsCommand\x1a\x62\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12&\n\x06values\x18\x02 \x03(\x0b\x32\x16.cmvr.api.FreedomValue\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xbb\x01\n\x17SetDexHandAnglesCommand\x1a\x62\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12&\n\x06values\x18\x02 \x03(\x0b\x32\x16.cmvr.api.FreedomValue\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xba\x01\n\x16SetDexHandForceCommand\x1a\x62\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12&\n\x06values\x18\x02 \x03(\x0b\x32\x16.cmvr.api.FreedomValue\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xba\x01\n\x16SetDexHandSpeedCommand\x1a\x62\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12&\n\x06values\x18\x02 \x03(\x0b\x32\x16.cmvr.api.FreedomValue\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xab\x01\n\x1aSetDexHandPresetActCommand\x1aO\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x13\n\x0bpresetActId\x18\x02 \x01(\x05\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xb6\x01\n\x14GetSensorDataCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x62\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12$\n\x06sensor\x18\x02 \x03(\x0b\x32\x14.cmvr.api.SensorData\"\xbc\x01\n\x1aGetSensorDataStreamCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x62\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12$\n\x06sensor\x18\x02 \x03(\x0b\x32\x14.cmvr.api.SensorDatab\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.dexhand_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_SENSORDATA_ROWDATA'].fields_by_name['values']._loaded_options = None + _globals['_SENSORDATA_ROWDATA'].fields_by_name['values']._serialized_options = b'\020\001' + _globals['_FREEDOMVALUE']._serialized_start=67 + _globals['_FREEDOMVALUE']._serialized_end=108 + _globals['_FREEDOMSTATE']._serialized_start=111 + _globals['_FREEDOMSTATE']._serialized_end=280 + _globals['_SENSORDATA']._serialized_start=283 + _globals['_SENSORDATA']._serialized_end=683 + _globals['_SENSORDATA_ROWDATA']._serialized_start=494 + _globals['_SENSORDATA_ROWDATA']._serialized_end=523 + _globals['_SENSORDATA_FINGERTYPE']._serialized_start=525 + _globals['_SENSORDATA_FINGERTYPE']._serialized_end=609 + _globals['_SENSORDATA_PARTTYPE']._serialized_start=611 + _globals['_SENSORDATA_PARTTYPE']._serialized_end=683 + _globals['_DEXHANDSTATE']._serialized_start=685 + _globals['_DEXHANDSTATE']._serialized_end=762 + _globals['_GETDEXHANDSTATECOMMAND']._serialized_start=765 + _globals['_GETDEXHANDSTATECOMMAND']._serialized_end=950 + _globals['_GETDEXHANDSTATECOMMAND_REQUEST']._serialized_start=791 + _globals['_GETDEXHANDSTATECOMMAND_REQUEST']._serialized_end=849 + _globals['_GETDEXHANDSTATECOMMAND_FEEDBACK']._serialized_start=851 + _globals['_GETDEXHANDSTATECOMMAND_FEEDBACK']._serialized_end=950 + _globals['_SETDEXHANDPOSITIONSCOMMAND']._serialized_start=953 + _globals['_SETDEXHANDPOSITIONSCOMMAND']._serialized_end=1143 + _globals['_SETDEXHANDPOSITIONSCOMMAND_REQUEST']._serialized_start=983 + _globals['_SETDEXHANDPOSITIONSCOMMAND_REQUEST']._serialized_end=1081 + _globals['_SETDEXHANDPOSITIONSCOMMAND_FEEDBACK']._serialized_start=851 + _globals['_SETDEXHANDPOSITIONSCOMMAND_FEEDBACK']._serialized_end=911 + _globals['_SETDEXHANDANGLESCOMMAND']._serialized_start=1146 + _globals['_SETDEXHANDANGLESCOMMAND']._serialized_end=1333 + _globals['_SETDEXHANDANGLESCOMMAND_REQUEST']._serialized_start=983 + _globals['_SETDEXHANDANGLESCOMMAND_REQUEST']._serialized_end=1081 + _globals['_SETDEXHANDANGLESCOMMAND_FEEDBACK']._serialized_start=851 + _globals['_SETDEXHANDANGLESCOMMAND_FEEDBACK']._serialized_end=911 + _globals['_SETDEXHANDFORCECOMMAND']._serialized_start=1336 + _globals['_SETDEXHANDFORCECOMMAND']._serialized_end=1522 + _globals['_SETDEXHANDFORCECOMMAND_REQUEST']._serialized_start=983 + _globals['_SETDEXHANDFORCECOMMAND_REQUEST']._serialized_end=1081 + _globals['_SETDEXHANDFORCECOMMAND_FEEDBACK']._serialized_start=851 + _globals['_SETDEXHANDFORCECOMMAND_FEEDBACK']._serialized_end=911 + _globals['_SETDEXHANDSPEEDCOMMAND']._serialized_start=1525 + _globals['_SETDEXHANDSPEEDCOMMAND']._serialized_end=1711 + _globals['_SETDEXHANDSPEEDCOMMAND_REQUEST']._serialized_start=983 + _globals['_SETDEXHANDSPEEDCOMMAND_REQUEST']._serialized_end=1081 + _globals['_SETDEXHANDSPEEDCOMMAND_FEEDBACK']._serialized_start=851 + _globals['_SETDEXHANDSPEEDCOMMAND_FEEDBACK']._serialized_end=911 + _globals['_SETDEXHANDPRESETACTCOMMAND']._serialized_start=1714 + _globals['_SETDEXHANDPRESETACTCOMMAND']._serialized_end=1885 + _globals['_SETDEXHANDPRESETACTCOMMAND_REQUEST']._serialized_start=1744 + _globals['_SETDEXHANDPRESETACTCOMMAND_REQUEST']._serialized_end=1823 + _globals['_SETDEXHANDPRESETACTCOMMAND_FEEDBACK']._serialized_start=851 + _globals['_SETDEXHANDPRESETACTCOMMAND_FEEDBACK']._serialized_end=911 + _globals['_GETSENSORDATACOMMAND']._serialized_start=1888 + _globals['_GETSENSORDATACOMMAND']._serialized_end=2070 + _globals['_GETSENSORDATACOMMAND_REQUEST']._serialized_start=791 + _globals['_GETSENSORDATACOMMAND_REQUEST']._serialized_end=849 + _globals['_GETSENSORDATACOMMAND_FEEDBACK']._serialized_start=1972 + _globals['_GETSENSORDATACOMMAND_FEEDBACK']._serialized_end=2070 + _globals['_GETSENSORDATASTREAMCOMMAND']._serialized_start=2073 + _globals['_GETSENSORDATASTREAMCOMMAND']._serialized_end=2261 + _globals['_GETSENSORDATASTREAMCOMMAND_REQUEST']._serialized_start=791 + _globals['_GETSENSORDATASTREAMCOMMAND_REQUEST']._serialized_end=849 + _globals['_GETSENSORDATASTREAMCOMMAND_FEEDBACK']._serialized_start=1972 + _globals['_GETSENSORDATASTREAMCOMMAND_FEEDBACK']._serialized_end=2070 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/dexhand_command_pb2.pyi b/src/cmvr/api/dexhand_command_pb2.pyi new file mode 100644 index 0000000..3490141 --- /dev/null +++ b/src/cmvr/api/dexhand_command_pb2.pyi @@ -0,0 +1,222 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class FreedomValue(_message.Message): + __slots__ = ("id", "value") + ID_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + id: int + value: float + def __init__(self, id: _Optional[int] = ..., value: _Optional[float] = ...) -> None: ... + +class FreedomState(_message.Message): + __slots__ = ("dof_id", "angle", "speed", "force", "position", "current", "temperature", "error", "error_message") + DOF_ID_FIELD_NUMBER: _ClassVar[int] + ANGLE_FIELD_NUMBER: _ClassVar[int] + SPEED_FIELD_NUMBER: _ClassVar[int] + FORCE_FIELD_NUMBER: _ClassVar[int] + POSITION_FIELD_NUMBER: _ClassVar[int] + CURRENT_FIELD_NUMBER: _ClassVar[int] + TEMPERATURE_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + dof_id: int + angle: int + speed: int + force: int + position: int + current: int + temperature: int + error: int + error_message: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, dof_id: _Optional[int] = ..., angle: _Optional[int] = ..., speed: _Optional[int] = ..., force: _Optional[int] = ..., position: _Optional[int] = ..., current: _Optional[int] = ..., temperature: _Optional[int] = ..., error: _Optional[int] = ..., error_message: _Optional[_Iterable[str]] = ...) -> None: ... + +class SensorData(_message.Message): + __slots__ = ("finger_type", "part_type", "sensor_name", "data", "rows", "cols") + class FingerType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PINKY: _ClassVar[SensorData.FingerType] + RING: _ClassVar[SensorData.FingerType] + MIDDLE_FINGER: _ClassVar[SensorData.FingerType] + INDEX: _ClassVar[SensorData.FingerType] + THUMB: _ClassVar[SensorData.FingerType] + PALM: _ClassVar[SensorData.FingerType] + PINKY: SensorData.FingerType + RING: SensorData.FingerType + MIDDLE_FINGER: SensorData.FingerType + INDEX: SensorData.FingerType + THUMB: SensorData.FingerType + PALM: SensorData.FingerType + class PartType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TIP: _ClassVar[SensorData.PartType] + FINGER: _ClassVar[SensorData.PartType] + PAD: _ClassVar[SensorData.PartType] + THUMB_MIDDLE: _ClassVar[SensorData.PartType] + PALM_PAD: _ClassVar[SensorData.PartType] + TIP: SensorData.PartType + FINGER: SensorData.PartType + PAD: SensorData.PartType + THUMB_MIDDLE: SensorData.PartType + PALM_PAD: SensorData.PartType + class RowData(_message.Message): + __slots__ = ("values",) + VALUES_FIELD_NUMBER: _ClassVar[int] + values: _containers.RepeatedScalarFieldContainer[int] + def __init__(self, values: _Optional[_Iterable[int]] = ...) -> None: ... + FINGER_TYPE_FIELD_NUMBER: _ClassVar[int] + PART_TYPE_FIELD_NUMBER: _ClassVar[int] + SENSOR_NAME_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + ROWS_FIELD_NUMBER: _ClassVar[int] + COLS_FIELD_NUMBER: _ClassVar[int] + finger_type: SensorData.FingerType + part_type: SensorData.PartType + sensor_name: str + data: _containers.RepeatedCompositeFieldContainer[SensorData.RowData] + rows: int + cols: int + def __init__(self, finger_type: _Optional[_Union[SensorData.FingerType, str]] = ..., part_type: _Optional[_Union[SensorData.PartType, str]] = ..., sensor_name: _Optional[str] = ..., data: _Optional[_Iterable[_Union[SensorData.RowData, _Mapping]]] = ..., rows: _Optional[int] = ..., cols: _Optional[int] = ...) -> None: ... + +class DexHandState(_message.Message): + __slots__ = ("is_initialized", "hands") + IS_INITIALIZED_FIELD_NUMBER: _ClassVar[int] + HANDS_FIELD_NUMBER: _ClassVar[int] + is_initialized: bool + hands: _containers.RepeatedCompositeFieldContainer[FreedomState] + def __init__(self, is_initialized: _Optional[bool] = ..., hands: _Optional[_Iterable[_Union[FreedomState, _Mapping]]] = ...) -> None: ... + +class GetDexHandStateCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "state") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + state: DexHandState + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., state: _Optional[_Union[DexHandState, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SetDexHandPositionsCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "values") + HEADER_FIELD_NUMBER: _ClassVar[int] + VALUES_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + values: _containers.RepeatedCompositeFieldContainer[FreedomValue] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., values: _Optional[_Iterable[_Union[FreedomValue, _Mapping]]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SetDexHandAnglesCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "values") + HEADER_FIELD_NUMBER: _ClassVar[int] + VALUES_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + values: _containers.RepeatedCompositeFieldContainer[FreedomValue] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., values: _Optional[_Iterable[_Union[FreedomValue, _Mapping]]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SetDexHandForceCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "values") + HEADER_FIELD_NUMBER: _ClassVar[int] + VALUES_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + values: _containers.RepeatedCompositeFieldContainer[FreedomValue] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., values: _Optional[_Iterable[_Union[FreedomValue, _Mapping]]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SetDexHandSpeedCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "values") + HEADER_FIELD_NUMBER: _ClassVar[int] + VALUES_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + values: _containers.RepeatedCompositeFieldContainer[FreedomValue] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., values: _Optional[_Iterable[_Union[FreedomValue, _Mapping]]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SetDexHandPresetActCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "presetActId") + HEADER_FIELD_NUMBER: _ClassVar[int] + PRESETACTID_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + presetActId: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., presetActId: _Optional[int] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetSensorDataCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "sensor") + HEADER_FIELD_NUMBER: _ClassVar[int] + SENSOR_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + sensor: _containers.RepeatedCompositeFieldContainer[SensorData] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., sensor: _Optional[_Iterable[_Union[SensorData, _Mapping]]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetSensorDataStreamCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "sensor") + HEADER_FIELD_NUMBER: _ClassVar[int] + SENSOR_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + sensor: _containers.RepeatedCompositeFieldContainer[SensorData] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., sensor: _Optional[_Iterable[_Union[SensorData, _Mapping]]] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/dexhand_command_pb2_grpc.py b/src/cmvr/api/dexhand_command_pb2_grpc.py new file mode 100644 index 0000000..cbcc7b8 --- /dev/null +++ b/src/cmvr/api/dexhand_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/dexhand_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/dexhand_service_pb2.py b/src/cmvr/api/dexhand_service_pb2.py new file mode 100644 index 0000000..4398ead --- /dev/null +++ b/src/cmvr/api/dexhand_service_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/dexhand_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/dexhand_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import dexhand_command_pb2 as cmvr_dot_api_dot_dexhand__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/dexhand_service.proto\x12\x08\x63mvr.api\x1a\x1e\x63mvr/api/dexhand_command.proto2\xe8\x06\n\x0e\x44\x65xHandService\x12`\n\tGetStatus\x12(.cmvr.api.GetDexHandStateCommand.Request\x1a).cmvr.api.GetDexHandStateCommand.Feedback\x12l\n\rSetDexHandPos\x12,.cmvr.api.SetDexHandPositionsCommand.Request\x1a-.cmvr.api.SetDexHandPositionsCommand.Feedback\x12h\n\x0fSetDexHandAngle\x12).cmvr.api.SetDexHandAnglesCommand.Request\x1a*.cmvr.api.SetDexHandAnglesCommand.Feedback\x12\x66\n\x0fSetDexHandForce\x12(.cmvr.api.SetDexHandForceCommand.Request\x1a).cmvr.api.SetDexHandForceCommand.Feedback\x12\x66\n\x0fSetDexHandSpeed\x12(.cmvr.api.SetDexHandSpeedCommand.Request\x1a).cmvr.api.SetDexHandSpeedCommand.Feedback\x12r\n\x13SetDexHandPresetAct\x12,.cmvr.api.SetDexHandPresetActCommand.Request\x1a-.cmvr.api.SetDexHandPresetActCommand.Feedback\x12`\n\rGetSensorData\x12&.cmvr.api.GetSensorDataCommand.Request\x1a\'.cmvr.api.GetSensorDataCommand.Feedback\x12v\n\x13GetSensorDataStream\x12,.cmvr.api.GetSensorDataStreamCommand.Request\x1a-.cmvr.api.GetSensorDataStreamCommand.Feedback(\x01\x30\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.dexhand_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_DEXHANDSERVICE']._serialized_start=77 + _globals['_DEXHANDSERVICE']._serialized_end=949 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/dexhand_service_pb2.pyi b/src/cmvr/api/dexhand_service_pb2.pyi new file mode 100644 index 0000000..4b691ab --- /dev/null +++ b/src/cmvr/api/dexhand_service_pb2.pyi @@ -0,0 +1,5 @@ +from cmvr.api import dexhand_command_pb2 as _dexhand_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/dexhand_service_pb2_grpc.py b/src/cmvr/api/dexhand_service_pb2_grpc.py new file mode 100644 index 0000000..0f19a24 --- /dev/null +++ b/src/cmvr/api/dexhand_service_pb2_grpc.py @@ -0,0 +1,399 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import dexhand_command_pb2 as cmvr_dot_api_dot_dexhand__command__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/dexhand_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class DexHandServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetStatus = channel.unary_unary( + '/cmvr.api.DexHandService/GetStatus', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.GetDexHandStateCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.GetDexHandStateCommand.Feedback.FromString, + _registered_method=True) + self.SetDexHandPos = channel.unary_unary( + '/cmvr.api.DexHandService/SetDexHandPos', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPositionsCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPositionsCommand.Feedback.FromString, + _registered_method=True) + self.SetDexHandAngle = channel.unary_unary( + '/cmvr.api.DexHandService/SetDexHandAngle', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandAnglesCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandAnglesCommand.Feedback.FromString, + _registered_method=True) + self.SetDexHandForce = channel.unary_unary( + '/cmvr.api.DexHandService/SetDexHandForce', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandForceCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandForceCommand.Feedback.FromString, + _registered_method=True) + self.SetDexHandSpeed = channel.unary_unary( + '/cmvr.api.DexHandService/SetDexHandSpeed', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandSpeedCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandSpeedCommand.Feedback.FromString, + _registered_method=True) + self.SetDexHandPresetAct = channel.unary_unary( + '/cmvr.api.DexHandService/SetDexHandPresetAct', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPresetActCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPresetActCommand.Feedback.FromString, + _registered_method=True) + self.GetSensorData = channel.unary_unary( + '/cmvr.api.DexHandService/GetSensorData', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataCommand.Feedback.FromString, + _registered_method=True) + self.GetSensorDataStream = channel.stream_stream( + '/cmvr.api.DexHandService/GetSensorDataStream', + request_serializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataStreamCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataStreamCommand.Feedback.FromString, + _registered_method=True) + + +class DexHandServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def GetStatus(self, request, context): + """基本控制 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetDexHandPos(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetDexHandAngle(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetDexHandForce(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetDexHandSpeed(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetDexHandPresetAct(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSensorData(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSensorDataStream(self, request_iterator, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_DexHandServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetStatus, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.GetDexHandStateCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.GetDexHandStateCommand.Feedback.SerializeToString, + ), + 'SetDexHandPos': grpc.unary_unary_rpc_method_handler( + servicer.SetDexHandPos, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPositionsCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPositionsCommand.Feedback.SerializeToString, + ), + 'SetDexHandAngle': grpc.unary_unary_rpc_method_handler( + servicer.SetDexHandAngle, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandAnglesCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandAnglesCommand.Feedback.SerializeToString, + ), + 'SetDexHandForce': grpc.unary_unary_rpc_method_handler( + servicer.SetDexHandForce, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandForceCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandForceCommand.Feedback.SerializeToString, + ), + 'SetDexHandSpeed': grpc.unary_unary_rpc_method_handler( + servicer.SetDexHandSpeed, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandSpeedCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandSpeedCommand.Feedback.SerializeToString, + ), + 'SetDexHandPresetAct': grpc.unary_unary_rpc_method_handler( + servicer.SetDexHandPresetAct, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPresetActCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPresetActCommand.Feedback.SerializeToString, + ), + 'GetSensorData': grpc.unary_unary_rpc_method_handler( + servicer.GetSensorData, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataCommand.Feedback.SerializeToString, + ), + 'GetSensorDataStream': grpc.stream_stream_rpc_method_handler( + servicer.GetSensorDataStream, + request_deserializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataStreamCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataStreamCommand.Feedback.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.DexHandService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.DexHandService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class DexHandService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.DexHandService/GetStatus', + cmvr_dot_api_dot_dexhand__command__pb2.GetDexHandStateCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.GetDexHandStateCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetDexHandPos(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.DexHandService/SetDexHandPos', + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPositionsCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPositionsCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetDexHandAngle(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.DexHandService/SetDexHandAngle', + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandAnglesCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandAnglesCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetDexHandForce(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.DexHandService/SetDexHandForce', + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandForceCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandForceCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetDexHandSpeed(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.DexHandService/SetDexHandSpeed', + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandSpeedCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandSpeedCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetDexHandPresetAct(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.DexHandService/SetDexHandPresetAct', + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPresetActCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.SetDexHandPresetActCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetSensorData(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.DexHandService/GetSensorData', + cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetSensorDataStream(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream( + request_iterator, + target, + '/cmvr.api.DexHandService/GetSensorDataStream', + cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataStreamCommand.Request.SerializeToString, + cmvr_dot_api_dot_dexhand__command__pb2.GetSensorDataStreamCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/hlc_command_pb2.py b/src/cmvr/api/hlc_command_pb2.py new file mode 100644 index 0000000..a28be26 --- /dev/null +++ b/src/cmvr/api/hlc_command_pb2.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/hlc_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/hlc_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/api/hlc_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"\xaa\x01\n\x05Touch\x1a\x63\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\t\n\x01u\x18\x02 \x01(\x05\x12\t\n\x01v\x18\x03 \x01(\x05\x12\x11\n\tmax_force\x18\x04 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedbackb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.hlc_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TOUCH']._serialized_start=64 + _globals['_TOUCH']._serialized_end=234 + _globals['_TOUCH_REQUEST']._serialized_start=73 + _globals['_TOUCH_REQUEST']._serialized_end=172 + _globals['_TOUCH_RESPONSE']._serialized_start=174 + _globals['_TOUCH_RESPONSE']._serialized_end=234 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/hlc_command_pb2.pyi b/src/cmvr/api/hlc_command_pb2.pyi new file mode 100644 index 0000000..a254639 --- /dev/null +++ b/src/cmvr/api/hlc_command_pb2.pyi @@ -0,0 +1,27 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Touch(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "u", "v", "max_force") + HEADER_FIELD_NUMBER: _ClassVar[int] + U_FIELD_NUMBER: _ClassVar[int] + V_FIELD_NUMBER: _ClassVar[int] + MAX_FORCE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + u: int + v: int + max_force: float + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., u: _Optional[int] = ..., v: _Optional[int] = ..., max_force: _Optional[float] = ...) -> None: ... + class Response(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/hlc_command_pb2_grpc.py b/src/cmvr/api/hlc_command_pb2_grpc.py new file mode 100644 index 0000000..d5672e0 --- /dev/null +++ b/src/cmvr/api/hlc_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/hlc_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/hlc_service_pb2.py b/src/cmvr/api/hlc_service_pb2.py new file mode 100644 index 0000000..c010d37 --- /dev/null +++ b/src/cmvr/api/hlc_service_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/hlc_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/hlc_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import hlc_command_pb2 as cmvr_dot_api_dot_hlc__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/api/hlc_service.proto\x12\x08\x63mvr.api\x1a\x1a\x63mvr/api/hlc_command.proto2H\n\nHlcService\x12:\n\x05touch\x12\x17.cmvr.api.Touch.Request\x1a\x18.cmvr.api.Touch.Responseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.hlc_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_HLCSERVICE']._serialized_start=68 + _globals['_HLCSERVICE']._serialized_end=140 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/hlc_service_pb2.pyi b/src/cmvr/api/hlc_service_pb2.pyi new file mode 100644 index 0000000..db31920 --- /dev/null +++ b/src/cmvr/api/hlc_service_pb2.pyi @@ -0,0 +1,5 @@ +from cmvr.api import hlc_command_pb2 as _hlc_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/hlc_service_pb2_grpc.py b/src/cmvr/api/hlc_service_pb2_grpc.py new file mode 100644 index 0000000..91ab8a1 --- /dev/null +++ b/src/cmvr/api/hlc_service_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import hlc_command_pb2 as cmvr_dot_api_dot_hlc__command__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/hlc_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class HlcServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.touch = channel.unary_unary( + '/cmvr.api.HlcService/touch', + request_serializer=cmvr_dot_api_dot_hlc__command__pb2.Touch.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_hlc__command__pb2.Touch.Response.FromString, + _registered_method=True) + + +class HlcServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def touch(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_HlcServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'touch': grpc.unary_unary_rpc_method_handler( + servicer.touch, + request_deserializer=cmvr_dot_api_dot_hlc__command__pb2.Touch.Request.FromString, + response_serializer=cmvr_dot_api_dot_hlc__command__pb2.Touch.Response.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.HlcService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.HlcService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class HlcService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def touch(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.HlcService/touch', + cmvr_dot_api_dot_hlc__command__pb2.Touch.Request.SerializeToString, + cmvr_dot_api_dot_hlc__command__pb2.Touch.Response.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/microphone_command_pb2.py b/src/cmvr/api/microphone_command_pb2.py new file mode 100644 index 0000000..2b78e0e --- /dev/null +++ b/src/cmvr/api/microphone_command_pb2.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/microphone_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/microphone_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!cmvr/api/microphone_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"s\n\x08MicState\x12\x16\n\x0eis_initialized\x18\x01 \x01(\x08\x12\x12\n\nis_running\x18\x02 \x01(\x08\x12\x14\n\x0cis_recording\x18\x03 \x01(\x08\x12\x0e\n\x06volume\x18\x04 \x01(\x05\x12\x15\n\rerror_message\x18\x05 \x01(\t\"\xb1\x01\n\x12GetMicStateCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a_\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12!\n\x05state\x18\x02 \x01(\x0b\x32\x12.cmvr.api.MicState\"\xa7\x01\n\x18StartMicRecordingCommand\x1aM\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tfile_path\x18\x02 \x01(\t\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x93\x01\n\x17StopMicRecordingCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x94\x01\n\x18PauseMicRecordingCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x95\x01\n\x19ResumeMicRecordingCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xa4\x01\n\x18SetMicPhoneVolumeCommand\x1aJ\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0e\n\x06volume\x18\x02 \x01(\x05\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xa4\x01\n\x18GetMicPhoneVolumeCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1aL\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x0e\n\x06volume\x18\x02 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.microphone_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_MICSTATE']._serialized_start=70 + _globals['_MICSTATE']._serialized_end=185 + _globals['_GETMICSTATECOMMAND']._serialized_start=188 + _globals['_GETMICSTATECOMMAND']._serialized_end=365 + _globals['_GETMICSTATECOMMAND_REQUEST']._serialized_start=210 + _globals['_GETMICSTATECOMMAND_REQUEST']._serialized_end=268 + _globals['_GETMICSTATECOMMAND_FEEDBACK']._serialized_start=270 + _globals['_GETMICSTATECOMMAND_FEEDBACK']._serialized_end=365 + _globals['_STARTMICRECORDINGCOMMAND']._serialized_start=368 + _globals['_STARTMICRECORDINGCOMMAND']._serialized_end=535 + _globals['_STARTMICRECORDINGCOMMAND_REQUEST']._serialized_start=396 + _globals['_STARTMICRECORDINGCOMMAND_REQUEST']._serialized_end=473 + _globals['_STARTMICRECORDINGCOMMAND_FEEDBACK']._serialized_start=270 + _globals['_STARTMICRECORDINGCOMMAND_FEEDBACK']._serialized_end=330 + _globals['_STOPMICRECORDINGCOMMAND']._serialized_start=538 + _globals['_STOPMICRECORDINGCOMMAND']._serialized_end=685 + _globals['_STOPMICRECORDINGCOMMAND_REQUEST']._serialized_start=210 + _globals['_STOPMICRECORDINGCOMMAND_REQUEST']._serialized_end=268 + _globals['_STOPMICRECORDINGCOMMAND_FEEDBACK']._serialized_start=270 + _globals['_STOPMICRECORDINGCOMMAND_FEEDBACK']._serialized_end=330 + _globals['_PAUSEMICRECORDINGCOMMAND']._serialized_start=688 + _globals['_PAUSEMICRECORDINGCOMMAND']._serialized_end=836 + _globals['_PAUSEMICRECORDINGCOMMAND_REQUEST']._serialized_start=210 + _globals['_PAUSEMICRECORDINGCOMMAND_REQUEST']._serialized_end=268 + _globals['_PAUSEMICRECORDINGCOMMAND_FEEDBACK']._serialized_start=270 + _globals['_PAUSEMICRECORDINGCOMMAND_FEEDBACK']._serialized_end=330 + _globals['_RESUMEMICRECORDINGCOMMAND']._serialized_start=839 + _globals['_RESUMEMICRECORDINGCOMMAND']._serialized_end=988 + _globals['_RESUMEMICRECORDINGCOMMAND_REQUEST']._serialized_start=210 + _globals['_RESUMEMICRECORDINGCOMMAND_REQUEST']._serialized_end=268 + _globals['_RESUMEMICRECORDINGCOMMAND_FEEDBACK']._serialized_start=270 + _globals['_RESUMEMICRECORDINGCOMMAND_FEEDBACK']._serialized_end=330 + _globals['_SETMICPHONEVOLUMECOMMAND']._serialized_start=991 + _globals['_SETMICPHONEVOLUMECOMMAND']._serialized_end=1155 + _globals['_SETMICPHONEVOLUMECOMMAND_REQUEST']._serialized_start=1019 + _globals['_SETMICPHONEVOLUMECOMMAND_REQUEST']._serialized_end=1093 + _globals['_SETMICPHONEVOLUMECOMMAND_FEEDBACK']._serialized_start=270 + _globals['_SETMICPHONEVOLUMECOMMAND_FEEDBACK']._serialized_end=330 + _globals['_GETMICPHONEVOLUMECOMMAND']._serialized_start=1158 + _globals['_GETMICPHONEVOLUMECOMMAND']._serialized_end=1322 + _globals['_GETMICPHONEVOLUMECOMMAND_REQUEST']._serialized_start=210 + _globals['_GETMICPHONEVOLUMECOMMAND_REQUEST']._serialized_end=268 + _globals['_GETMICPHONEVOLUMECOMMAND_FEEDBACK']._serialized_start=1246 + _globals['_GETMICPHONEVOLUMECOMMAND_FEEDBACK']._serialized_end=1322 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/microphone_command_pb2.pyi b/src/cmvr/api/microphone_command_pb2.pyi new file mode 100644 index 0000000..1a7ecc2 --- /dev/null +++ b/src/cmvr/api/microphone_command_pb2.pyi @@ -0,0 +1,127 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class MicState(_message.Message): + __slots__ = ("is_initialized", "is_running", "is_recording", "volume", "error_message") + IS_INITIALIZED_FIELD_NUMBER: _ClassVar[int] + IS_RUNNING_FIELD_NUMBER: _ClassVar[int] + IS_RECORDING_FIELD_NUMBER: _ClassVar[int] + VOLUME_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + is_initialized: bool + is_running: bool + is_recording: bool + volume: int + error_message: str + def __init__(self, is_initialized: _Optional[bool] = ..., is_running: _Optional[bool] = ..., is_recording: _Optional[bool] = ..., volume: _Optional[int] = ..., error_message: _Optional[str] = ...) -> None: ... + +class GetMicStateCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "state") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + state: MicState + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., state: _Optional[_Union[MicState, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StartMicRecordingCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "file_path") + HEADER_FIELD_NUMBER: _ClassVar[int] + FILE_PATH_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + file_path: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., file_path: _Optional[str] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StopMicRecordingCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class PauseMicRecordingCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ResumeMicRecordingCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SetMicPhoneVolumeCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "volume") + HEADER_FIELD_NUMBER: _ClassVar[int] + VOLUME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + volume: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., volume: _Optional[int] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetMicPhoneVolumeCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "volume") + HEADER_FIELD_NUMBER: _ClassVar[int] + VOLUME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + volume: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., volume: _Optional[int] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/microphone_command_pb2_grpc.py b/src/cmvr/api/microphone_command_pb2_grpc.py new file mode 100644 index 0000000..97f9f1f --- /dev/null +++ b/src/cmvr/api/microphone_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/microphone_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/microphone_service_pb2.py b/src/cmvr/api/microphone_service_pb2.py new file mode 100644 index 0000000..9f29ca1 --- /dev/null +++ b/src/cmvr/api/microphone_service_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/microphone_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/microphone_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import microphone_command_pb2 as cmvr_dot_api_dot_microphone__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!cmvr/api/microphone_service.proto\x12\x08\x63mvr.api\x1a!cmvr/api/microphone_command.proto2\xd7\x05\n\x0fMicPhoneService\x12X\n\tGetStatus\x12$.cmvr.api.GetMicStateCommand.Request\x1a%.cmvr.api.GetMicStateCommand.Feedback\x12\x66\n\x0bStartRecord\x12*.cmvr.api.StartMicRecordingCommand.Request\x1a+.cmvr.api.StartMicRecordingCommand.Feedback\x12\x63\n\nStopRecord\x12).cmvr.api.StopMicRecordingCommand.Request\x1a*.cmvr.api.StopMicRecordingCommand.Feedback\x12\x66\n\x0bPauseRecord\x12*.cmvr.api.PauseMicRecordingCommand.Request\x1a+.cmvr.api.PauseMicRecordingCommand.Feedback\x12i\n\x0cResumeRecord\x12+.cmvr.api.ResumeMicRecordingCommand.Request\x1a,.cmvr.api.ResumeMicRecordingCommand.Feedback\x12\x64\n\tSetVolume\x12*.cmvr.api.SetMicPhoneVolumeCommand.Request\x1a+.cmvr.api.SetMicPhoneVolumeCommand.Feedback\x12\x64\n\tGetVolume\x12*.cmvr.api.GetMicPhoneVolumeCommand.Request\x1a+.cmvr.api.GetMicPhoneVolumeCommand.Feedbackb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.microphone_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_MICPHONESERVICE']._serialized_start=83 + _globals['_MICPHONESERVICE']._serialized_end=810 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/microphone_service_pb2.pyi b/src/cmvr/api/microphone_service_pb2.pyi new file mode 100644 index 0000000..38e96fd --- /dev/null +++ b/src/cmvr/api/microphone_service_pb2.pyi @@ -0,0 +1,5 @@ +from cmvr.api import microphone_command_pb2 as _microphone_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/microphone_service_pb2_grpc.py b/src/cmvr/api/microphone_service_pb2_grpc.py new file mode 100644 index 0000000..3fde576 --- /dev/null +++ b/src/cmvr/api/microphone_service_pb2_grpc.py @@ -0,0 +1,357 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import microphone_command_pb2 as cmvr_dot_api_dot_microphone__command__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/microphone_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class MicPhoneServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetStatus = channel.unary_unary( + '/cmvr.api.MicPhoneService/GetStatus', + request_serializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicStateCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicStateCommand.Feedback.FromString, + _registered_method=True) + self.StartRecord = channel.unary_unary( + '/cmvr.api.MicPhoneService/StartRecord', + request_serializer=cmvr_dot_api_dot_microphone__command__pb2.StartMicRecordingCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_microphone__command__pb2.StartMicRecordingCommand.Feedback.FromString, + _registered_method=True) + self.StopRecord = channel.unary_unary( + '/cmvr.api.MicPhoneService/StopRecord', + request_serializer=cmvr_dot_api_dot_microphone__command__pb2.StopMicRecordingCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_microphone__command__pb2.StopMicRecordingCommand.Feedback.FromString, + _registered_method=True) + self.PauseRecord = channel.unary_unary( + '/cmvr.api.MicPhoneService/PauseRecord', + request_serializer=cmvr_dot_api_dot_microphone__command__pb2.PauseMicRecordingCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_microphone__command__pb2.PauseMicRecordingCommand.Feedback.FromString, + _registered_method=True) + self.ResumeRecord = channel.unary_unary( + '/cmvr.api.MicPhoneService/ResumeRecord', + request_serializer=cmvr_dot_api_dot_microphone__command__pb2.ResumeMicRecordingCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_microphone__command__pb2.ResumeMicRecordingCommand.Feedback.FromString, + _registered_method=True) + self.SetVolume = channel.unary_unary( + '/cmvr.api.MicPhoneService/SetVolume', + request_serializer=cmvr_dot_api_dot_microphone__command__pb2.SetMicPhoneVolumeCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_microphone__command__pb2.SetMicPhoneVolumeCommand.Feedback.FromString, + _registered_method=True) + self.GetVolume = channel.unary_unary( + '/cmvr.api.MicPhoneService/GetVolume', + request_serializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicPhoneVolumeCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicPhoneVolumeCommand.Feedback.FromString, + _registered_method=True) + + +class MicPhoneServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def GetStatus(self, request, context): + """基本控制 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StartRecord(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StopRecord(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PauseRecord(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ResumeRecord(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetVolume(self, request, context): + """音量控制 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetVolume(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_MicPhoneServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetStatus, + request_deserializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicStateCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicStateCommand.Feedback.SerializeToString, + ), + 'StartRecord': grpc.unary_unary_rpc_method_handler( + servicer.StartRecord, + request_deserializer=cmvr_dot_api_dot_microphone__command__pb2.StartMicRecordingCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_microphone__command__pb2.StartMicRecordingCommand.Feedback.SerializeToString, + ), + 'StopRecord': grpc.unary_unary_rpc_method_handler( + servicer.StopRecord, + request_deserializer=cmvr_dot_api_dot_microphone__command__pb2.StopMicRecordingCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_microphone__command__pb2.StopMicRecordingCommand.Feedback.SerializeToString, + ), + 'PauseRecord': grpc.unary_unary_rpc_method_handler( + servicer.PauseRecord, + request_deserializer=cmvr_dot_api_dot_microphone__command__pb2.PauseMicRecordingCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_microphone__command__pb2.PauseMicRecordingCommand.Feedback.SerializeToString, + ), + 'ResumeRecord': grpc.unary_unary_rpc_method_handler( + servicer.ResumeRecord, + request_deserializer=cmvr_dot_api_dot_microphone__command__pb2.ResumeMicRecordingCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_microphone__command__pb2.ResumeMicRecordingCommand.Feedback.SerializeToString, + ), + 'SetVolume': grpc.unary_unary_rpc_method_handler( + servicer.SetVolume, + request_deserializer=cmvr_dot_api_dot_microphone__command__pb2.SetMicPhoneVolumeCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_microphone__command__pb2.SetMicPhoneVolumeCommand.Feedback.SerializeToString, + ), + 'GetVolume': grpc.unary_unary_rpc_method_handler( + servicer.GetVolume, + request_deserializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicPhoneVolumeCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_microphone__command__pb2.GetMicPhoneVolumeCommand.Feedback.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.MicPhoneService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.MicPhoneService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class MicPhoneService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.MicPhoneService/GetStatus', + cmvr_dot_api_dot_microphone__command__pb2.GetMicStateCommand.Request.SerializeToString, + cmvr_dot_api_dot_microphone__command__pb2.GetMicStateCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StartRecord(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.MicPhoneService/StartRecord', + cmvr_dot_api_dot_microphone__command__pb2.StartMicRecordingCommand.Request.SerializeToString, + cmvr_dot_api_dot_microphone__command__pb2.StartMicRecordingCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StopRecord(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.MicPhoneService/StopRecord', + cmvr_dot_api_dot_microphone__command__pb2.StopMicRecordingCommand.Request.SerializeToString, + cmvr_dot_api_dot_microphone__command__pb2.StopMicRecordingCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def PauseRecord(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.MicPhoneService/PauseRecord', + cmvr_dot_api_dot_microphone__command__pb2.PauseMicRecordingCommand.Request.SerializeToString, + cmvr_dot_api_dot_microphone__command__pb2.PauseMicRecordingCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ResumeRecord(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.MicPhoneService/ResumeRecord', + cmvr_dot_api_dot_microphone__command__pb2.ResumeMicRecordingCommand.Request.SerializeToString, + cmvr_dot_api_dot_microphone__command__pb2.ResumeMicRecordingCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetVolume(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.MicPhoneService/SetVolume', + cmvr_dot_api_dot_microphone__command__pb2.SetMicPhoneVolumeCommand.Request.SerializeToString, + cmvr_dot_api_dot_microphone__command__pb2.SetMicPhoneVolumeCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetVolume(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.MicPhoneService/GetVolume', + cmvr_dot_api_dot_microphone__command__pb2.GetMicPhoneVolumeCommand.Request.SerializeToString, + cmvr_dot_api_dot_microphone__command__pb2.GetMicPhoneVolumeCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/speaker_command_pb2.py b/src/cmvr/api/speaker_command_pb2.py new file mode 100644 index 0000000..e67e0bf --- /dev/null +++ b/src/cmvr/api/speaker_command_pb2.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/speaker_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/speaker_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/speaker_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"\xb3\x01\n\tAudioData\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x13\n\x0bsample_rate\x18\x02 \x01(\x05\x12\x10\n\x08\x63hannels\x18\x03 \x01(\x05\x12/\n\x06\x66ormat\x18\x04 \x01(\x0e\x32\x1f.cmvr.api.AudioData.AudioFormat\x12\r\n\x05\x63odec\x18\x05 \x01(\t\"1\n\x0b\x41udioFormat\x12\x07\n\x03PCM\x10\x00\x12\x07\n\x03MP3\x10\x01\x12\x07\n\x03\x41\x41\x43\x10\x02\x12\x07\n\x03WAV\x10\x03\"\x89\x01\n\x0cSpeakerState\x12\x16\n\x0eis_initialized\x18\x01 \x01(\x08\x12\x12\n\nis_running\x18\x02 \x01(\x08\x12\x13\n\x0bis_decoding\x18\x03 \x01(\x08\x12\x11\n\tis_paused\x18\x05 \x01(\x08\x12\x0e\n\x06volume\x18\x06 \x01(\x05\x12\x15\n\rerror_message\x18\x07 \x01(\t\"\xb9\x01\n\x16GetSpeakerStateCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\x63\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12%\n\x05state\x18\x02 \x01(\x0b\x32\x16.cmvr.api.SpeakerState\"\xa0\x01\n\x10PlayAudioCommand\x1aN\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\naudio_path\x18\x02 \x01(\t\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8e\x01\n\x12StopSpeakerCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8f\x01\n\x13PauseSpeakerCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x90\x01\n\x14ResumeSpeakerCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xa3\x01\n\x17SetSpeakerVolumeCommand\x1aJ\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0e\n\x06volume\x18\x02 \x01(\x05\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xa3\x01\n\x17GetSpeakerVolumeCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1aL\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x0e\n\x06volume\x18\x02 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.speaker_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_AUDIODATA']._serialized_start=68 + _globals['_AUDIODATA']._serialized_end=247 + _globals['_AUDIODATA_AUDIOFORMAT']._serialized_start=198 + _globals['_AUDIODATA_AUDIOFORMAT']._serialized_end=247 + _globals['_SPEAKERSTATE']._serialized_start=250 + _globals['_SPEAKERSTATE']._serialized_end=387 + _globals['_GETSPEAKERSTATECOMMAND']._serialized_start=390 + _globals['_GETSPEAKERSTATECOMMAND']._serialized_end=575 + _globals['_GETSPEAKERSTATECOMMAND_REQUEST']._serialized_start=416 + _globals['_GETSPEAKERSTATECOMMAND_REQUEST']._serialized_end=474 + _globals['_GETSPEAKERSTATECOMMAND_FEEDBACK']._serialized_start=476 + _globals['_GETSPEAKERSTATECOMMAND_FEEDBACK']._serialized_end=575 + _globals['_PLAYAUDIOCOMMAND']._serialized_start=578 + _globals['_PLAYAUDIOCOMMAND']._serialized_end=738 + _globals['_PLAYAUDIOCOMMAND_REQUEST']._serialized_start=598 + _globals['_PLAYAUDIOCOMMAND_REQUEST']._serialized_end=676 + _globals['_PLAYAUDIOCOMMAND_FEEDBACK']._serialized_start=476 + _globals['_PLAYAUDIOCOMMAND_FEEDBACK']._serialized_end=536 + _globals['_STOPSPEAKERCOMMAND']._serialized_start=741 + _globals['_STOPSPEAKERCOMMAND']._serialized_end=883 + _globals['_STOPSPEAKERCOMMAND_REQUEST']._serialized_start=416 + _globals['_STOPSPEAKERCOMMAND_REQUEST']._serialized_end=474 + _globals['_STOPSPEAKERCOMMAND_FEEDBACK']._serialized_start=476 + _globals['_STOPSPEAKERCOMMAND_FEEDBACK']._serialized_end=536 + _globals['_PAUSESPEAKERCOMMAND']._serialized_start=886 + _globals['_PAUSESPEAKERCOMMAND']._serialized_end=1029 + _globals['_PAUSESPEAKERCOMMAND_REQUEST']._serialized_start=416 + _globals['_PAUSESPEAKERCOMMAND_REQUEST']._serialized_end=474 + _globals['_PAUSESPEAKERCOMMAND_FEEDBACK']._serialized_start=476 + _globals['_PAUSESPEAKERCOMMAND_FEEDBACK']._serialized_end=536 + _globals['_RESUMESPEAKERCOMMAND']._serialized_start=1032 + _globals['_RESUMESPEAKERCOMMAND']._serialized_end=1176 + _globals['_RESUMESPEAKERCOMMAND_REQUEST']._serialized_start=416 + _globals['_RESUMESPEAKERCOMMAND_REQUEST']._serialized_end=474 + _globals['_RESUMESPEAKERCOMMAND_FEEDBACK']._serialized_start=476 + _globals['_RESUMESPEAKERCOMMAND_FEEDBACK']._serialized_end=536 + _globals['_SETSPEAKERVOLUMECOMMAND']._serialized_start=1179 + _globals['_SETSPEAKERVOLUMECOMMAND']._serialized_end=1342 + _globals['_SETSPEAKERVOLUMECOMMAND_REQUEST']._serialized_start=1206 + _globals['_SETSPEAKERVOLUMECOMMAND_REQUEST']._serialized_end=1280 + _globals['_SETSPEAKERVOLUMECOMMAND_FEEDBACK']._serialized_start=476 + _globals['_SETSPEAKERVOLUMECOMMAND_FEEDBACK']._serialized_end=536 + _globals['_GETSPEAKERVOLUMECOMMAND']._serialized_start=1345 + _globals['_GETSPEAKERVOLUMECOMMAND']._serialized_end=1508 + _globals['_GETSPEAKERVOLUMECOMMAND_REQUEST']._serialized_start=416 + _globals['_GETSPEAKERVOLUMECOMMAND_REQUEST']._serialized_end=474 + _globals['_GETSPEAKERVOLUMECOMMAND_FEEDBACK']._serialized_start=1432 + _globals['_GETSPEAKERVOLUMECOMMAND_FEEDBACK']._serialized_end=1508 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/speaker_command_pb2.pyi b/src/cmvr/api/speaker_command_pb2.pyi new file mode 100644 index 0000000..016cd2e --- /dev/null +++ b/src/cmvr/api/speaker_command_pb2.pyi @@ -0,0 +1,154 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class AudioData(_message.Message): + __slots__ = ("data", "sample_rate", "channels", "format", "codec") + class AudioFormat(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PCM: _ClassVar[AudioData.AudioFormat] + MP3: _ClassVar[AudioData.AudioFormat] + AAC: _ClassVar[AudioData.AudioFormat] + WAV: _ClassVar[AudioData.AudioFormat] + PCM: AudioData.AudioFormat + MP3: AudioData.AudioFormat + AAC: AudioData.AudioFormat + WAV: AudioData.AudioFormat + DATA_FIELD_NUMBER: _ClassVar[int] + SAMPLE_RATE_FIELD_NUMBER: _ClassVar[int] + CHANNELS_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + CODEC_FIELD_NUMBER: _ClassVar[int] + data: bytes + sample_rate: int + channels: int + format: AudioData.AudioFormat + codec: str + def __init__(self, data: _Optional[bytes] = ..., sample_rate: _Optional[int] = ..., channels: _Optional[int] = ..., format: _Optional[_Union[AudioData.AudioFormat, str]] = ..., codec: _Optional[str] = ...) -> None: ... + +class SpeakerState(_message.Message): + __slots__ = ("is_initialized", "is_running", "is_decoding", "is_paused", "volume", "error_message") + IS_INITIALIZED_FIELD_NUMBER: _ClassVar[int] + IS_RUNNING_FIELD_NUMBER: _ClassVar[int] + IS_DECODING_FIELD_NUMBER: _ClassVar[int] + IS_PAUSED_FIELD_NUMBER: _ClassVar[int] + VOLUME_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + is_initialized: bool + is_running: bool + is_decoding: bool + is_paused: bool + volume: int + error_message: str + def __init__(self, is_initialized: _Optional[bool] = ..., is_running: _Optional[bool] = ..., is_decoding: _Optional[bool] = ..., is_paused: _Optional[bool] = ..., volume: _Optional[int] = ..., error_message: _Optional[str] = ...) -> None: ... + +class GetSpeakerStateCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "state") + HEADER_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + state: SpeakerState + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., state: _Optional[_Union[SpeakerState, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class PlayAudioCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "audio_path") + HEADER_FIELD_NUMBER: _ClassVar[int] + AUDIO_PATH_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + audio_path: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., audio_path: _Optional[str] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StopSpeakerCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class PauseSpeakerCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class ResumeSpeakerCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class SetSpeakerVolumeCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "volume") + HEADER_FIELD_NUMBER: _ClassVar[int] + VOLUME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + volume: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., volume: _Optional[int] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetSpeakerVolumeCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "volume") + HEADER_FIELD_NUMBER: _ClassVar[int] + VOLUME_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + volume: int + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., volume: _Optional[int] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/speaker_command_pb2_grpc.py b/src/cmvr/api/speaker_command_pb2_grpc.py new file mode 100644 index 0000000..de6592f --- /dev/null +++ b/src/cmvr/api/speaker_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/speaker_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/speaker_service_pb2.py b/src/cmvr/api/speaker_service_pb2.py new file mode 100644 index 0000000..6f8cd65 --- /dev/null +++ b/src/cmvr/api/speaker_service_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/speaker_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/speaker_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import speaker_command_pb2 as cmvr_dot_api_dot_speaker__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/speaker_service.proto\x12\x08\x63mvr.api\x1a\x1e\x63mvr/api/speaker_command.proto2\xb0\x05\n\x0eSpeakerService\x12`\n\tGetStatus\x12(.cmvr.api.GetSpeakerStateCommand.Request\x1a).cmvr.api.GetSpeakerStateCommand.Feedback\x12T\n\tPlayAudio\x12\".cmvr.api.PlayAudioCommand.Request\x1a#.cmvr.api.PlayAudioCommand.Feedback\x12[\n\x0cStopPlayback\x12$.cmvr.api.StopSpeakerCommand.Request\x1a%.cmvr.api.StopSpeakerCommand.Feedback\x12^\n\rPausePlayback\x12%.cmvr.api.PauseSpeakerCommand.Request\x1a&.cmvr.api.PauseSpeakerCommand.Feedback\x12\x61\n\x0eResumePlayback\x12&.cmvr.api.ResumeSpeakerCommand.Request\x1a\'.cmvr.api.ResumeSpeakerCommand.Feedback\x12\x62\n\tSetVolume\x12).cmvr.api.SetSpeakerVolumeCommand.Request\x1a*.cmvr.api.SetSpeakerVolumeCommand.Feedback\x12\x62\n\tGetVolume\x12).cmvr.api.GetSpeakerVolumeCommand.Request\x1a*.cmvr.api.GetSpeakerVolumeCommand.Feedbackb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.speaker_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_SPEAKERSERVICE']._serialized_start=77 + _globals['_SPEAKERSERVICE']._serialized_end=765 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/speaker_service_pb2.pyi b/src/cmvr/api/speaker_service_pb2.pyi new file mode 100644 index 0000000..b32548a --- /dev/null +++ b/src/cmvr/api/speaker_service_pb2.pyi @@ -0,0 +1,5 @@ +from cmvr.api import speaker_command_pb2 as _speaker_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/speaker_service_pb2_grpc.py b/src/cmvr/api/speaker_service_pb2_grpc.py new file mode 100644 index 0000000..5914f50 --- /dev/null +++ b/src/cmvr/api/speaker_service_pb2_grpc.py @@ -0,0 +1,357 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import speaker_command_pb2 as cmvr_dot_api_dot_speaker__command__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/speaker_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class SpeakerServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetStatus = channel.unary_unary( + '/cmvr.api.SpeakerService/GetStatus', + request_serializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerStateCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerStateCommand.Feedback.FromString, + _registered_method=True) + self.PlayAudio = channel.unary_unary( + '/cmvr.api.SpeakerService/PlayAudio', + request_serializer=cmvr_dot_api_dot_speaker__command__pb2.PlayAudioCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_speaker__command__pb2.PlayAudioCommand.Feedback.FromString, + _registered_method=True) + self.StopPlayback = channel.unary_unary( + '/cmvr.api.SpeakerService/StopPlayback', + request_serializer=cmvr_dot_api_dot_speaker__command__pb2.StopSpeakerCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_speaker__command__pb2.StopSpeakerCommand.Feedback.FromString, + _registered_method=True) + self.PausePlayback = channel.unary_unary( + '/cmvr.api.SpeakerService/PausePlayback', + request_serializer=cmvr_dot_api_dot_speaker__command__pb2.PauseSpeakerCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_speaker__command__pb2.PauseSpeakerCommand.Feedback.FromString, + _registered_method=True) + self.ResumePlayback = channel.unary_unary( + '/cmvr.api.SpeakerService/ResumePlayback', + request_serializer=cmvr_dot_api_dot_speaker__command__pb2.ResumeSpeakerCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_speaker__command__pb2.ResumeSpeakerCommand.Feedback.FromString, + _registered_method=True) + self.SetVolume = channel.unary_unary( + '/cmvr.api.SpeakerService/SetVolume', + request_serializer=cmvr_dot_api_dot_speaker__command__pb2.SetSpeakerVolumeCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_speaker__command__pb2.SetSpeakerVolumeCommand.Feedback.FromString, + _registered_method=True) + self.GetVolume = channel.unary_unary( + '/cmvr.api.SpeakerService/GetVolume', + request_serializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerVolumeCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerVolumeCommand.Feedback.FromString, + _registered_method=True) + + +class SpeakerServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def GetStatus(self, request, context): + """基本控制 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PlayAudio(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StopPlayback(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PausePlayback(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ResumePlayback(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetVolume(self, request, context): + """音量控制 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetVolume(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_SpeakerServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetStatus, + request_deserializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerStateCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerStateCommand.Feedback.SerializeToString, + ), + 'PlayAudio': grpc.unary_unary_rpc_method_handler( + servicer.PlayAudio, + request_deserializer=cmvr_dot_api_dot_speaker__command__pb2.PlayAudioCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_speaker__command__pb2.PlayAudioCommand.Feedback.SerializeToString, + ), + 'StopPlayback': grpc.unary_unary_rpc_method_handler( + servicer.StopPlayback, + request_deserializer=cmvr_dot_api_dot_speaker__command__pb2.StopSpeakerCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_speaker__command__pb2.StopSpeakerCommand.Feedback.SerializeToString, + ), + 'PausePlayback': grpc.unary_unary_rpc_method_handler( + servicer.PausePlayback, + request_deserializer=cmvr_dot_api_dot_speaker__command__pb2.PauseSpeakerCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_speaker__command__pb2.PauseSpeakerCommand.Feedback.SerializeToString, + ), + 'ResumePlayback': grpc.unary_unary_rpc_method_handler( + servicer.ResumePlayback, + request_deserializer=cmvr_dot_api_dot_speaker__command__pb2.ResumeSpeakerCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_speaker__command__pb2.ResumeSpeakerCommand.Feedback.SerializeToString, + ), + 'SetVolume': grpc.unary_unary_rpc_method_handler( + servicer.SetVolume, + request_deserializer=cmvr_dot_api_dot_speaker__command__pb2.SetSpeakerVolumeCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_speaker__command__pb2.SetSpeakerVolumeCommand.Feedback.SerializeToString, + ), + 'GetVolume': grpc.unary_unary_rpc_method_handler( + servicer.GetVolume, + request_deserializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerVolumeCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerVolumeCommand.Feedback.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.SpeakerService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.SpeakerService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class SpeakerService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SpeakerService/GetStatus', + cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerStateCommand.Request.SerializeToString, + cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerStateCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def PlayAudio(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SpeakerService/PlayAudio', + cmvr_dot_api_dot_speaker__command__pb2.PlayAudioCommand.Request.SerializeToString, + cmvr_dot_api_dot_speaker__command__pb2.PlayAudioCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StopPlayback(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SpeakerService/StopPlayback', + cmvr_dot_api_dot_speaker__command__pb2.StopSpeakerCommand.Request.SerializeToString, + cmvr_dot_api_dot_speaker__command__pb2.StopSpeakerCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def PausePlayback(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SpeakerService/PausePlayback', + cmvr_dot_api_dot_speaker__command__pb2.PauseSpeakerCommand.Request.SerializeToString, + cmvr_dot_api_dot_speaker__command__pb2.PauseSpeakerCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ResumePlayback(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SpeakerService/ResumePlayback', + cmvr_dot_api_dot_speaker__command__pb2.ResumeSpeakerCommand.Request.SerializeToString, + cmvr_dot_api_dot_speaker__command__pb2.ResumeSpeakerCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetVolume(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SpeakerService/SetVolume', + cmvr_dot_api_dot_speaker__command__pb2.SetSpeakerVolumeCommand.Request.SerializeToString, + cmvr_dot_api_dot_speaker__command__pb2.SetSpeakerVolumeCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetVolume(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SpeakerService/GetVolume', + cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerVolumeCommand.Request.SerializeToString, + cmvr_dot_api_dot_speaker__command__pb2.GetSpeakerVolumeCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/system_command_pb2.py b/src/cmvr/api/system_command_pb2.py new file mode 100644 index 0000000..ca596b5 --- /dev/null +++ b/src/cmvr/api/system_command_pb2.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/system_command.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/system_command.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/system_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"J\n\nDeviceList\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12)\n\x0b\x64\x65vice_type\x18\x02 \x01(\x0e\x32\x14.cmvr.api.DeviceType\"\xd5\x01\n\x14GetSystemInfoCommand\x1a\t\n\x07Request\x1a\xb1\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x13\n\x0bsystem_name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\n\n\x02os\x18\x05 \x01(\t\x12\x16\n\x0ekernel_version\x18\x06 \x01(\t\x12\x14\n\x0c\x61rchitecture\x18\x07 \x01(\t\"\xf8\x01\n\x16GetSystemStatusCommand\x1a\t\n\x07Request\x1a\xd2\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x11\n\tcpu_usage\x18\x02 \x01(\x02\x12\x14\n\x0cmem_total_mb\x18\x03 \x01(\x02\x12\x13\n\x0bmem_used_mb\x18\x04 \x01(\x02\x12\x15\n\rdisk_total_gb\x18\x05 \x01(\x02\x12\x14\n\x0c\x64isk_used_gb\x18\x06 \x01(\x02\x12)\n\x0b\x64\x65vice_list\x18\x07 \x03(\x0b\x32\x14.cmvr.api.DeviceList\"\xb6\x01\n\x13UpdateParamsCommand\x1a\x61\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12%\n\x06params\x18\x02 \x03(\x0b\x32\x15.cmvr.api.ConfigParam\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8a\x01\n\x0eStopAllCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback*}\n\nDeviceType\x12\x07\n\x03\x41GV\x10\x00\x12\x0b\n\x07\x42\x61ttery\x10\x01\x12\n\n\x06\x43\x61mera\x10\x02\x12\x0b\n\x07\x44\x65xHand\x10\x03\x12\x0b\n\x07Gripper\x10\x04\x12\x0e\n\nMicrophone\x10\x05\x12\t\n\x05Robot\x10\x06\x12\x0b\n\x07Speaker\x10\x07\x12\x0b\n\x07Unknown\x10\x14\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.system_command_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_DEVICETYPE']._serialized_start=935 + _globals['_DEVICETYPE']._serialized_end=1060 + _globals['_DEVICELIST']._serialized_start=66 + _globals['_DEVICELIST']._serialized_end=140 + _globals['_GETSYSTEMINFOCOMMAND']._serialized_start=143 + _globals['_GETSYSTEMINFOCOMMAND']._serialized_end=356 + _globals['_GETSYSTEMINFOCOMMAND_REQUEST']._serialized_start=167 + _globals['_GETSYSTEMINFOCOMMAND_REQUEST']._serialized_end=176 + _globals['_GETSYSTEMINFOCOMMAND_FEEDBACK']._serialized_start=179 + _globals['_GETSYSTEMINFOCOMMAND_FEEDBACK']._serialized_end=356 + _globals['_GETSYSTEMSTATUSCOMMAND']._serialized_start=359 + _globals['_GETSYSTEMSTATUSCOMMAND']._serialized_end=607 + _globals['_GETSYSTEMSTATUSCOMMAND_REQUEST']._serialized_start=167 + _globals['_GETSYSTEMSTATUSCOMMAND_REQUEST']._serialized_end=176 + _globals['_GETSYSTEMSTATUSCOMMAND_FEEDBACK']._serialized_start=397 + _globals['_GETSYSTEMSTATUSCOMMAND_FEEDBACK']._serialized_end=607 + _globals['_UPDATEPARAMSCOMMAND']._serialized_start=610 + _globals['_UPDATEPARAMSCOMMAND']._serialized_end=792 + _globals['_UPDATEPARAMSCOMMAND_REQUEST']._serialized_start=633 + _globals['_UPDATEPARAMSCOMMAND_REQUEST']._serialized_end=730 + _globals['_UPDATEPARAMSCOMMAND_FEEDBACK']._serialized_start=179 + _globals['_UPDATEPARAMSCOMMAND_FEEDBACK']._serialized_end=239 + _globals['_STOPALLCOMMAND']._serialized_start=795 + _globals['_STOPALLCOMMAND']._serialized_end=933 + _globals['_STOPALLCOMMAND_REQUEST']._serialized_start=633 + _globals['_STOPALLCOMMAND_REQUEST']._serialized_end=691 + _globals['_STOPALLCOMMAND_FEEDBACK']._serialized_start=179 + _globals['_STOPALLCOMMAND_FEEDBACK']._serialized_end=239 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/system_command_pb2.pyi b/src/cmvr/api/system_command_pb2.pyi new file mode 100644 index 0000000..6190153 --- /dev/null +++ b/src/cmvr/api/system_command_pb2.pyi @@ -0,0 +1,116 @@ +from cmvr.api import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class DeviceType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + AGV: _ClassVar[DeviceType] + Battery: _ClassVar[DeviceType] + Camera: _ClassVar[DeviceType] + DexHand: _ClassVar[DeviceType] + Gripper: _ClassVar[DeviceType] + Microphone: _ClassVar[DeviceType] + Robot: _ClassVar[DeviceType] + Speaker: _ClassVar[DeviceType] + Unknown: _ClassVar[DeviceType] +AGV: DeviceType +Battery: DeviceType +Camera: DeviceType +DexHand: DeviceType +Gripper: DeviceType +Microphone: DeviceType +Robot: DeviceType +Speaker: DeviceType +Unknown: DeviceType + +class DeviceList(_message.Message): + __slots__ = ("device_id", "device_type") + DEVICE_ID_FIELD_NUMBER: _ClassVar[int] + DEVICE_TYPE_FIELD_NUMBER: _ClassVar[int] + device_id: str + device_type: DeviceType + def __init__(self, device_id: _Optional[str] = ..., device_type: _Optional[_Union[DeviceType, str]] = ...) -> None: ... + +class GetSystemInfoCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "system_name", "version", "description", "os", "kernel_version", "architecture") + HEADER_FIELD_NUMBER: _ClassVar[int] + SYSTEM_NAME_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + OS_FIELD_NUMBER: _ClassVar[int] + KERNEL_VERSION_FIELD_NUMBER: _ClassVar[int] + ARCHITECTURE_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + system_name: str + version: str + description: str + os: str + kernel_version: str + architecture: str + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., system_name: _Optional[str] = ..., version: _Optional[str] = ..., description: _Optional[str] = ..., os: _Optional[str] = ..., kernel_version: _Optional[str] = ..., architecture: _Optional[str] = ...) -> None: ... + def __init__(self) -> None: ... + +class GetSystemStatusCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header", "cpu_usage", "mem_total_mb", "mem_used_mb", "disk_total_gb", "disk_used_gb", "device_list") + HEADER_FIELD_NUMBER: _ClassVar[int] + CPU_USAGE_FIELD_NUMBER: _ClassVar[int] + MEM_TOTAL_MB_FIELD_NUMBER: _ClassVar[int] + MEM_USED_MB_FIELD_NUMBER: _ClassVar[int] + DISK_TOTAL_GB_FIELD_NUMBER: _ClassVar[int] + DISK_USED_GB_FIELD_NUMBER: _ClassVar[int] + DEVICE_LIST_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + cpu_usage: float + mem_total_mb: float + mem_used_mb: float + disk_total_gb: float + disk_used_gb: float + device_list: _containers.RepeatedCompositeFieldContainer[DeviceList] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ..., cpu_usage: _Optional[float] = ..., mem_total_mb: _Optional[float] = ..., mem_used_mb: _Optional[float] = ..., disk_total_gb: _Optional[float] = ..., disk_used_gb: _Optional[float] = ..., device_list: _Optional[_Iterable[_Union[DeviceList, _Mapping]]] = ...) -> None: ... + def __init__(self) -> None: ... + +class UpdateParamsCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header", "params") + HEADER_FIELD_NUMBER: _ClassVar[int] + PARAMS_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + params: _containers.RepeatedCompositeFieldContainer[_common_pb2.ConfigParam] + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ..., params: _Optional[_Iterable[_Union[_common_pb2.ConfigParam, _Mapping]]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... + +class StopAllCommand(_message.Message): + __slots__ = () + class Request(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Request + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Request, _Mapping]] = ...) -> None: ... + class Feedback(_message.Message): + __slots__ = ("header",) + HEADER_FIELD_NUMBER: _ClassVar[int] + header: _common_pb2.CommandHeader.Feedback + def __init__(self, header: _Optional[_Union[_common_pb2.CommandHeader.Feedback, _Mapping]] = ...) -> None: ... + def __init__(self) -> None: ... diff --git a/src/cmvr/api/system_command_pb2_grpc.py b/src/cmvr/api/system_command_pb2_grpc.py new file mode 100644 index 0000000..e13624b --- /dev/null +++ b/src/cmvr/api/system_command_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/system_command_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/api/system_service_pb2.py b/src/cmvr/api/system_service_pb2.py new file mode 100644 index 0000000..859ba7f --- /dev/null +++ b/src/cmvr/api/system_service_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/system_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/system_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.api import system_command_pb2 as cmvr_dot_api_dot_system__command__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/system_service.proto\x12\x08\x63mvr.api\x1a\x1d\x63mvr/api/system_command.proto2\x90\x03\n\rSystemService\x12\x62\n\rGetSystemInfo\x12&.cmvr.api.GetSystemInfoCommand.Request\x1a\'.cmvr.api.GetSystemInfoCommand.Feedback\"\x00\x12h\n\x0fGetSystemStatus\x12(.cmvr.api.GetSystemStatusCommand.Request\x1a).cmvr.api.GetSystemStatusCommand.Feedback\"\x00\x12_\n\x0cUpdateParams\x12%.cmvr.api.UpdateParamsCommand.Request\x1a&.cmvr.api.UpdateParamsCommand.Feedback\"\x00\x12P\n\x07StopAll\x12 .cmvr.api.StopAllCommand.Request\x1a!.cmvr.api.StopAllCommand.Feedback\"\x00\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.system_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_SYSTEMSERVICE']._serialized_start=75 + _globals['_SYSTEMSERVICE']._serialized_end=475 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/system_service_pb2.pyi b/src/cmvr/api/system_service_pb2.pyi new file mode 100644 index 0000000..d696a2d --- /dev/null +++ b/src/cmvr/api/system_service_pb2.pyi @@ -0,0 +1,5 @@ +from cmvr.api import system_command_pb2 as _system_command_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor diff --git a/src/cmvr/api/system_service_pb2_grpc.py b/src/cmvr/api/system_service_pb2_grpc.py new file mode 100644 index 0000000..145c99c --- /dev/null +++ b/src/cmvr/api/system_service_pb2_grpc.py @@ -0,0 +1,226 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import system_command_pb2 as cmvr_dot_api_dot_system__command__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/system_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class SystemServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetSystemInfo = channel.unary_unary( + '/cmvr.api.SystemService/GetSystemInfo', + request_serializer=cmvr_dot_api_dot_system__command__pb2.GetSystemInfoCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_system__command__pb2.GetSystemInfoCommand.Feedback.FromString, + _registered_method=True) + self.GetSystemStatus = channel.unary_unary( + '/cmvr.api.SystemService/GetSystemStatus', + request_serializer=cmvr_dot_api_dot_system__command__pb2.GetSystemStatusCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_system__command__pb2.GetSystemStatusCommand.Feedback.FromString, + _registered_method=True) + self.UpdateParams = channel.unary_unary( + '/cmvr.api.SystemService/UpdateParams', + request_serializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Feedback.FromString, + _registered_method=True) + self.StopAll = channel.unary_unary( + '/cmvr.api.SystemService/StopAll', + request_serializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Request.SerializeToString, + response_deserializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Feedback.FromString, + _registered_method=True) + + +class SystemServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def GetSystemInfo(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSystemStatus(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateParams(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StopAll(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_SystemServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetSystemInfo': grpc.unary_unary_rpc_method_handler( + servicer.GetSystemInfo, + request_deserializer=cmvr_dot_api_dot_system__command__pb2.GetSystemInfoCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_system__command__pb2.GetSystemInfoCommand.Feedback.SerializeToString, + ), + 'GetSystemStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetSystemStatus, + request_deserializer=cmvr_dot_api_dot_system__command__pb2.GetSystemStatusCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_system__command__pb2.GetSystemStatusCommand.Feedback.SerializeToString, + ), + 'UpdateParams': grpc.unary_unary_rpc_method_handler( + servicer.UpdateParams, + request_deserializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Feedback.SerializeToString, + ), + 'StopAll': grpc.unary_unary_rpc_method_handler( + servicer.StopAll, + request_deserializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Request.FromString, + response_serializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Feedback.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.SystemService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.SystemService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class SystemService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetSystemInfo(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SystemService/GetSystemInfo', + cmvr_dot_api_dot_system__command__pb2.GetSystemInfoCommand.Request.SerializeToString, + cmvr_dot_api_dot_system__command__pb2.GetSystemInfoCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetSystemStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SystemService/GetSystemStatus', + cmvr_dot_api_dot_system__command__pb2.GetSystemStatusCommand.Request.SerializeToString, + cmvr_dot_api_dot_system__command__pb2.GetSystemStatusCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateParams(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SystemService/UpdateParams', + cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Request.SerializeToString, + cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StopAll(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.SystemService/StopAll', + cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Request.SerializeToString, + cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Feedback.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/api/test_service_pb2.py b/src/cmvr/api/test_service_pb2.py new file mode 100644 index 0000000..2241a7d --- /dev/null +++ b/src/cmvr/api/test_service_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/api/test_service.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/api/test_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1b\x63mvr/api/test_service.proto\x12\x08\x63mvr.api\"\x1b\n\x0bTestReqeust\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\t\"\x1c\n\x0cTestResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\t2D\n\x0bTestService\x12\x35\n\x04\x43\x61ll\x12\x15.cmvr.api.TestReqeust\x1a\x16.cmvr.api.TestResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.test_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TESTREQEUST']._serialized_start=41 + _globals['_TESTREQEUST']._serialized_end=68 + _globals['_TESTRESPONSE']._serialized_start=70 + _globals['_TESTRESPONSE']._serialized_end=98 + _globals['_TESTSERVICE']._serialized_start=100 + _globals['_TESTSERVICE']._serialized_end=168 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/api/test_service_pb2.pyi b/src/cmvr/api/test_service_pb2.pyi new file mode 100644 index 0000000..586bbd5 --- /dev/null +++ b/src/cmvr/api/test_service_pb2.pyi @@ -0,0 +1,17 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class TestReqeust(_message.Message): + __slots__ = ("data",) + DATA_FIELD_NUMBER: _ClassVar[int] + data: str + def __init__(self, data: _Optional[str] = ...) -> None: ... + +class TestResponse(_message.Message): + __slots__ = ("data",) + DATA_FIELD_NUMBER: _ClassVar[int] + data: str + def __init__(self, data: _Optional[str] = ...) -> None: ... diff --git a/src/cmvr/api/test_service_pb2_grpc.py b/src/cmvr/api/test_service_pb2_grpc.py new file mode 100644 index 0000000..df27866 --- /dev/null +++ b/src/cmvr/api/test_service_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from cmvr.api import test_service_pb2 as cmvr_dot_api_dot_test__service__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/api/test_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class TestServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Call = channel.unary_unary( + '/cmvr.api.TestService/Call', + request_serializer=cmvr_dot_api_dot_test__service__pb2.TestReqeust.SerializeToString, + response_deserializer=cmvr_dot_api_dot_test__service__pb2.TestResponse.FromString, + _registered_method=True) + + +class TestServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def Call(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TestServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Call': grpc.unary_unary_rpc_method_handler( + servicer.Call, + request_deserializer=cmvr_dot_api_dot_test__service__pb2.TestReqeust.FromString, + response_serializer=cmvr_dot_api_dot_test__service__pb2.TestResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'cmvr.api.TestService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('cmvr.api.TestService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class TestService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def Call(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/cmvr.api.TestService/Call', + cmvr_dot_api_dot_test__service__pb2.TestReqeust.SerializeToString, + cmvr_dot_api_dot_test__service__pb2.TestResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/cmvr/common/__pycache__/geometry_pb2.cpython-310.pyc b/src/cmvr/common/__pycache__/geometry_pb2.cpython-310.pyc new file mode 100644 index 0000000..4ec4a99 Binary files /dev/null and b/src/cmvr/common/__pycache__/geometry_pb2.cpython-310.pyc differ diff --git a/src/cmvr/common/__pycache__/geometry_pb2.cpython-312.pyc b/src/cmvr/common/__pycache__/geometry_pb2.cpython-312.pyc new file mode 100644 index 0000000..780fb0d Binary files /dev/null and b/src/cmvr/common/__pycache__/geometry_pb2.cpython-312.pyc differ diff --git a/src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-310.pyc b/src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..062f5c0 Binary files /dev/null and b/src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-312.pyc b/src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..861a77d Binary files /dev/null and b/src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/common/geometry_pb2.py b/src/cmvr/common/geometry_pb2.py new file mode 100644 index 0000000..34864a8 --- /dev/null +++ b/src/cmvr/common/geometry_pb2.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/common/geometry.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/common/geometry.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/common/geometry.proto\x12\x0b\x63mvr.common\"2\n\x04Vec2\x12\x0e\n\x01x\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0e\n\x01y\x18\x02 \x01(\x01H\x01\x88\x01\x01\x42\x04\n\x02_xB\x04\n\x02_y\"H\n\x04Vec3\x12\x0e\n\x01x\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0e\n\x01y\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x0e\n\x01z\x18\x03 \x01(\x01H\x02\x88\x01\x01\x42\x04\n\x02_xB\x04\n\x02_yB\x04\n\x02_z\"\x90\x01\n\x04Vec6\x12\x0e\n\x01x\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0e\n\x01y\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x0e\n\x01z\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x0f\n\x02rx\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x0f\n\x02ry\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x0f\n\x02rz\x18\x06 \x01(\x01H\x05\x88\x01\x01\x42\x04\n\x02_xB\x04\n\x02_yB\x04\n\x02_zB\x05\n\x03_rxB\x05\n\x03_ryB\x05\n\x03_rz\"^\n\x04Quat\x12\x0e\n\x01w\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0e\n\x01x\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x0e\n\x01y\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x0e\n\x01z\x18\x04 \x01(\x01H\x03\x88\x01\x01\x42\x04\n\x02_wB\x04\n\x02_xB\x04\n\x02_yB\x04\n\x02_z\"O\n\x05\x45uler\x12\x0f\n\x02rx\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0f\n\x02ry\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x0f\n\x02rz\x18\x03 \x01(\x01H\x02\x88\x01\x01\x42\x05\n\x03_rxB\x05\n\x03_ryB\x05\n\x03_rz\"w\n\x06Pose3d\x12#\n\x08position\x18\x01 \x01(\x0b\x32\x11.cmvr.common.Vec3\x12%\n\nquaternion\x18\x02 \x01(\x0b\x32\x11.cmvr.common.Quat\x12!\n\x05\x65uler\x18\x03 \x01(\x0b\x32\x12.cmvr.common.Euler\"R\n\x06Pose2d\x12\x0e\n\x01x\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0e\n\x01y\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x12\n\x05theta\x18\x03 \x01(\x01H\x02\x88\x01\x01\x42\x04\n\x02_xB\x04\n\x02_yB\x08\n\x06_theta\"\xf0\x01\n\x04Mat3\x12\x10\n\x03m00\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x10\n\x03m01\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x10\n\x03m02\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x10\n\x03m10\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x10\n\x03m11\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x10\n\x03m12\x18\x06 \x01(\x01H\x05\x88\x01\x01\x12\x10\n\x03m20\x18\x07 \x01(\x01H\x06\x88\x01\x01\x12\x10\n\x03m21\x18\x08 \x01(\x01H\x07\x88\x01\x01\x12\x10\n\x03m22\x18\t \x01(\x01H\x08\x88\x01\x01\x42\x06\n\x04_m00B\x06\n\x04_m01B\x06\n\x04_m02B\x06\n\x04_m10B\x06\n\x04_m11B\x06\n\x04_m12B\x06\n\x04_m20B\x06\n\x04_m21B\x06\n\x04_m22\"\xa6\x03\n\x04Mat4\x12\x10\n\x03m00\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x10\n\x03m01\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x10\n\x03m02\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x10\n\x03m03\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x10\n\x03m10\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x10\n\x03m11\x18\x06 \x01(\x01H\x05\x88\x01\x01\x12\x10\n\x03m12\x18\x07 \x01(\x01H\x06\x88\x01\x01\x12\x10\n\x03m13\x18\x08 \x01(\x01H\x07\x88\x01\x01\x12\x10\n\x03m20\x18\t \x01(\x01H\x08\x88\x01\x01\x12\x10\n\x03m21\x18\n \x01(\x01H\t\x88\x01\x01\x12\x10\n\x03m22\x18\x0b \x01(\x01H\n\x88\x01\x01\x12\x10\n\x03m23\x18\x0c \x01(\x01H\x0b\x88\x01\x01\x12\x10\n\x03m30\x18\r \x01(\x01H\x0c\x88\x01\x01\x12\x10\n\x03m31\x18\x0e \x01(\x01H\r\x88\x01\x01\x12\x10\n\x03m32\x18\x0f \x01(\x01H\x0e\x88\x01\x01\x12\x10\n\x03m33\x18\x10 \x01(\x01H\x0f\x88\x01\x01\x42\x06\n\x04_m00B\x06\n\x04_m01B\x06\n\x04_m02B\x06\n\x04_m03B\x06\n\x04_m10B\x06\n\x04_m11B\x06\n\x04_m12B\x06\n\x04_m13B\x06\n\x04_m20B\x06\n\x04_m21B\x06\n\x04_m22B\x06\n\x04_m23B\x06\n\x04_m30B\x06\n\x04_m31B\x06\n\x04_m32B\x06\n\x04_m33b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.common.geometry_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_VEC2']._serialized_start=43 + _globals['_VEC2']._serialized_end=93 + _globals['_VEC3']._serialized_start=95 + _globals['_VEC3']._serialized_end=167 + _globals['_VEC6']._serialized_start=170 + _globals['_VEC6']._serialized_end=314 + _globals['_QUAT']._serialized_start=316 + _globals['_QUAT']._serialized_end=410 + _globals['_EULER']._serialized_start=412 + _globals['_EULER']._serialized_end=491 + _globals['_POSE3D']._serialized_start=493 + _globals['_POSE3D']._serialized_end=612 + _globals['_POSE2D']._serialized_start=614 + _globals['_POSE2D']._serialized_end=696 + _globals['_MAT3']._serialized_start=699 + _globals['_MAT3']._serialized_end=939 + _globals['_MAT4']._serialized_start=942 + _globals['_MAT4']._serialized_end=1364 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/common/geometry_pb2.pyi b/src/cmvr/common/geometry_pb2.pyi new file mode 100644 index 0000000..0606137 --- /dev/null +++ b/src/cmvr/common/geometry_pb2.pyi @@ -0,0 +1,140 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Vec2(_message.Message): + __slots__ = ("x", "y") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ...) -> None: ... + +class Vec3(_message.Message): + __slots__ = ("x", "y", "z") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + z: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ...) -> None: ... + +class Vec6(_message.Message): + __slots__ = ("x", "y", "z", "rx", "ry", "rz") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + RX_FIELD_NUMBER: _ClassVar[int] + RY_FIELD_NUMBER: _ClassVar[int] + RZ_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + z: float + rx: float + ry: float + rz: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ..., rx: _Optional[float] = ..., ry: _Optional[float] = ..., rz: _Optional[float] = ...) -> None: ... + +class Quat(_message.Message): + __slots__ = ("w", "x", "y", "z") + W_FIELD_NUMBER: _ClassVar[int] + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + w: float + x: float + y: float + z: float + def __init__(self, w: _Optional[float] = ..., x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ...) -> None: ... + +class Euler(_message.Message): + __slots__ = ("rx", "ry", "rz") + RX_FIELD_NUMBER: _ClassVar[int] + RY_FIELD_NUMBER: _ClassVar[int] + RZ_FIELD_NUMBER: _ClassVar[int] + rx: float + ry: float + rz: float + def __init__(self, rx: _Optional[float] = ..., ry: _Optional[float] = ..., rz: _Optional[float] = ...) -> None: ... + +class Pose3d(_message.Message): + __slots__ = ("position", "quaternion", "euler") + POSITION_FIELD_NUMBER: _ClassVar[int] + QUATERNION_FIELD_NUMBER: _ClassVar[int] + EULER_FIELD_NUMBER: _ClassVar[int] + position: Vec3 + quaternion: Quat + euler: Euler + def __init__(self, position: _Optional[_Union[Vec3, _Mapping]] = ..., quaternion: _Optional[_Union[Quat, _Mapping]] = ..., euler: _Optional[_Union[Euler, _Mapping]] = ...) -> None: ... + +class Pose2d(_message.Message): + __slots__ = ("x", "y", "theta") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + THETA_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + theta: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., theta: _Optional[float] = ...) -> None: ... + +class Mat3(_message.Message): + __slots__ = ("m00", "m01", "m02", "m10", "m11", "m12", "m20", "m21", "m22") + M00_FIELD_NUMBER: _ClassVar[int] + M01_FIELD_NUMBER: _ClassVar[int] + M02_FIELD_NUMBER: _ClassVar[int] + M10_FIELD_NUMBER: _ClassVar[int] + M11_FIELD_NUMBER: _ClassVar[int] + M12_FIELD_NUMBER: _ClassVar[int] + M20_FIELD_NUMBER: _ClassVar[int] + M21_FIELD_NUMBER: _ClassVar[int] + M22_FIELD_NUMBER: _ClassVar[int] + m00: float + m01: float + m02: float + m10: float + m11: float + m12: float + m20: float + m21: float + m22: float + def __init__(self, m00: _Optional[float] = ..., m01: _Optional[float] = ..., m02: _Optional[float] = ..., m10: _Optional[float] = ..., m11: _Optional[float] = ..., m12: _Optional[float] = ..., m20: _Optional[float] = ..., m21: _Optional[float] = ..., m22: _Optional[float] = ...) -> None: ... + +class Mat4(_message.Message): + __slots__ = ("m00", "m01", "m02", "m03", "m10", "m11", "m12", "m13", "m20", "m21", "m22", "m23", "m30", "m31", "m32", "m33") + M00_FIELD_NUMBER: _ClassVar[int] + M01_FIELD_NUMBER: _ClassVar[int] + M02_FIELD_NUMBER: _ClassVar[int] + M03_FIELD_NUMBER: _ClassVar[int] + M10_FIELD_NUMBER: _ClassVar[int] + M11_FIELD_NUMBER: _ClassVar[int] + M12_FIELD_NUMBER: _ClassVar[int] + M13_FIELD_NUMBER: _ClassVar[int] + M20_FIELD_NUMBER: _ClassVar[int] + M21_FIELD_NUMBER: _ClassVar[int] + M22_FIELD_NUMBER: _ClassVar[int] + M23_FIELD_NUMBER: _ClassVar[int] + M30_FIELD_NUMBER: _ClassVar[int] + M31_FIELD_NUMBER: _ClassVar[int] + M32_FIELD_NUMBER: _ClassVar[int] + M33_FIELD_NUMBER: _ClassVar[int] + m00: float + m01: float + m02: float + m03: float + m10: float + m11: float + m12: float + m13: float + m20: float + m21: float + m22: float + m23: float + m30: float + m31: float + m32: float + m33: float + def __init__(self, m00: _Optional[float] = ..., m01: _Optional[float] = ..., m02: _Optional[float] = ..., m03: _Optional[float] = ..., m10: _Optional[float] = ..., m11: _Optional[float] = ..., m12: _Optional[float] = ..., m13: _Optional[float] = ..., m20: _Optional[float] = ..., m21: _Optional[float] = ..., m22: _Optional[float] = ..., m23: _Optional[float] = ..., m30: _Optional[float] = ..., m31: _Optional[float] = ..., m32: _Optional[float] = ..., m33: _Optional[float] = ...) -> None: ... diff --git a/src/cmvr/common/geometry_pb2_grpc.py b/src/cmvr/common/geometry_pb2_grpc.py new file mode 100644 index 0000000..180a532 --- /dev/null +++ b/src/cmvr/common/geometry_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/common/geometry_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-310.pyc b/src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..b6ef8ae Binary files /dev/null and b/src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-312.pyc b/src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..a2b79a5 Binary files /dev/null and b/src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..8208bb9 Binary files /dev/null and b/src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..9b5ca54 Binary files /dev/null and b/src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-310.pyc b/src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..bba24e0 Binary files /dev/null and b/src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-312.pyc b/src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..22102d7 Binary files /dev/null and b/src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..f11d9b5 Binary files /dev/null and b/src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..4b8f610 Binary files /dev/null and b/src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-310.pyc b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..7e1c0e9 Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-312.pyc b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..07d9116 Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..1830a58 Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..410981b Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-310.pyc b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..62f3955 Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-312.pyc b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..9d00d9a Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..1fc36a5 Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..b8efde0 Binary files /dev/null and b/src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-310.pyc b/src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..a9f8cd3 Binary files /dev/null and b/src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-312.pyc b/src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..04db026 Binary files /dev/null and b/src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..b420eae Binary files /dev/null and b/src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..e818646 Binary files /dev/null and b/src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-310.pyc b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..efa4719 Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-312.pyc b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..73cd09b Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..96dd86f Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..5761af9 Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-310.pyc b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..b6ce929 Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-312.pyc b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..68c155d Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..1d70193 Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..d736658 Binary files /dev/null and b/src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-310.pyc b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..c815843 Binary files /dev/null and b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-312.pyc b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..eb0bc52 Binary files /dev/null and b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..eae9d16 Binary files /dev/null and b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..df5581f Binary files /dev/null and b/src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/agv_config/agv_config_pb2.py b/src/cmvr/config/agv_config/agv_config_pb2.py new file mode 100644 index 0000000..36e373e --- /dev/null +++ b/src/cmvr/config/agv_config/agv_config_pb2.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/agv_config/agv_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/agv_config/agv_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'cmvr/config/agv_config/agv_config.proto\x12\x0b\x63mvr.config\"3\n\x0bMyAgvConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\"\xc7\x03\n\x10Src1100AgvConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\x12\x0e\n\x06\x65nable\x18\x03 \x01(\x08\x12\x13\n\x0bport_status\x18\x04 \x01(\x05\x12\x14\n\x0cport_control\x18\x05 \x01(\x05\x12\x10\n\x08port_nav\x18\x06 \x01(\x05\x12\x13\n\x0bport_config\x18\x07 \x01(\x05\x12\x12\n\nport_other\x18\x08 \x01(\x05\x12\x11\n\tport_push\x18\t \x01(\x05\x12\x1a\n\x12\x63onnect_timeout_ms\x18\n \x01(\x05\x12\x17\n\x0frecv_timeout_ms\x18\x0b \x01(\x05\x12\x19\n\x11\x65nable_state_push\x18\x0c \x01(\x08\x12\x1e\n\x16state_push_interval_ms\x18\r \x01(\x05\x12\"\n\x1astate_push_included_fields\x18\x0e \x03(\t\x12\"\n\x1astate_push_excluded_fields\x18\x0f \x03(\t\x12\x19\n\x11\x65nable_map_update\x18\x10 \x01(\x08\x12\x1e\n\x16map_update_interval_ms\x18\x11 \x01(\x05\x12\x1f\n\x17map_update_history_size\x18\x12 \x01(\r\"\x8a\x01\n\x0f\x41GVDeviceConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12*\n\x06my_agv\x18\n \x01(\x0b\x32\x18.cmvr.config.MyAgvConfigH\x00\x12\x34\n\x0bsrc1100_agv\x18\x0b \x01(\x0b\x32\x1d.cmvr.config.Src1100AgvConfigH\x00\x42\t\n\x07\x62\x61\x63kend\"7\n\tAGVConfig\x12*\n\x04\x61gvs\x18\x01 \x03(\x0b\x32\x1c.cmvr.config.AGVDeviceConfig\"4\n\rAGVRootConfig\x12#\n\x03\x61gv\x18\x01 \x01(\x0b\x32\x16.cmvr.config.AGVConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.agv_config.agv_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_MYAGVCONFIG']._serialized_start=56 + _globals['_MYAGVCONFIG']._serialized_end=107 + _globals['_SRC1100AGVCONFIG']._serialized_start=110 + _globals['_SRC1100AGVCONFIG']._serialized_end=565 + _globals['_AGVDEVICECONFIG']._serialized_start=568 + _globals['_AGVDEVICECONFIG']._serialized_end=706 + _globals['_AGVCONFIG']._serialized_start=708 + _globals['_AGVCONFIG']._serialized_end=763 + _globals['_AGVROOTCONFIG']._serialized_start=765 + _globals['_AGVROOTCONFIG']._serialized_end=817 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/agv_config/agv_config_pb2.pyi b/src/cmvr/config/agv_config/agv_config_pb2.pyi new file mode 100644 index 0000000..b83665f --- /dev/null +++ b/src/cmvr/config/agv_config/agv_config_pb2.pyi @@ -0,0 +1,79 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class MyAgvConfig(_message.Message): + __slots__ = ("id", "ip", "port") + ID_FIELD_NUMBER: _ClassVar[int] + IP_FIELD_NUMBER: _ClassVar[int] + PORT_FIELD_NUMBER: _ClassVar[int] + id: str + ip: str + port: int + def __init__(self, id: _Optional[str] = ..., ip: _Optional[str] = ..., port: _Optional[int] = ...) -> None: ... + +class Src1100AgvConfig(_message.Message): + __slots__ = ("id", "ip", "enable", "port_status", "port_control", "port_nav", "port_config", "port_other", "port_push", "connect_timeout_ms", "recv_timeout_ms", "enable_state_push", "state_push_interval_ms", "state_push_included_fields", "state_push_excluded_fields", "enable_map_update", "map_update_interval_ms", "map_update_history_size") + ID_FIELD_NUMBER: _ClassVar[int] + IP_FIELD_NUMBER: _ClassVar[int] + ENABLE_FIELD_NUMBER: _ClassVar[int] + PORT_STATUS_FIELD_NUMBER: _ClassVar[int] + PORT_CONTROL_FIELD_NUMBER: _ClassVar[int] + PORT_NAV_FIELD_NUMBER: _ClassVar[int] + PORT_CONFIG_FIELD_NUMBER: _ClassVar[int] + PORT_OTHER_FIELD_NUMBER: _ClassVar[int] + PORT_PUSH_FIELD_NUMBER: _ClassVar[int] + CONNECT_TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int] + RECV_TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int] + ENABLE_STATE_PUSH_FIELD_NUMBER: _ClassVar[int] + STATE_PUSH_INTERVAL_MS_FIELD_NUMBER: _ClassVar[int] + STATE_PUSH_INCLUDED_FIELDS_FIELD_NUMBER: _ClassVar[int] + STATE_PUSH_EXCLUDED_FIELDS_FIELD_NUMBER: _ClassVar[int] + ENABLE_MAP_UPDATE_FIELD_NUMBER: _ClassVar[int] + MAP_UPDATE_INTERVAL_MS_FIELD_NUMBER: _ClassVar[int] + MAP_UPDATE_HISTORY_SIZE_FIELD_NUMBER: _ClassVar[int] + id: str + ip: str + enable: bool + port_status: int + port_control: int + port_nav: int + port_config: int + port_other: int + port_push: int + connect_timeout_ms: int + recv_timeout_ms: int + enable_state_push: bool + state_push_interval_ms: int + state_push_included_fields: _containers.RepeatedScalarFieldContainer[str] + state_push_excluded_fields: _containers.RepeatedScalarFieldContainer[str] + enable_map_update: bool + map_update_interval_ms: int + map_update_history_size: int + def __init__(self, id: _Optional[str] = ..., ip: _Optional[str] = ..., enable: _Optional[bool] = ..., port_status: _Optional[int] = ..., port_control: _Optional[int] = ..., port_nav: _Optional[int] = ..., port_config: _Optional[int] = ..., port_other: _Optional[int] = ..., port_push: _Optional[int] = ..., connect_timeout_ms: _Optional[int] = ..., recv_timeout_ms: _Optional[int] = ..., enable_state_push: _Optional[bool] = ..., state_push_interval_ms: _Optional[int] = ..., state_push_included_fields: _Optional[_Iterable[str]] = ..., state_push_excluded_fields: _Optional[_Iterable[str]] = ..., enable_map_update: _Optional[bool] = ..., map_update_interval_ms: _Optional[int] = ..., map_update_history_size: _Optional[int] = ...) -> None: ... + +class AGVDeviceConfig(_message.Message): + __slots__ = ("id", "my_agv", "src1100_agv") + ID_FIELD_NUMBER: _ClassVar[int] + MY_AGV_FIELD_NUMBER: _ClassVar[int] + SRC1100_AGV_FIELD_NUMBER: _ClassVar[int] + id: str + my_agv: MyAgvConfig + src1100_agv: Src1100AgvConfig + def __init__(self, id: _Optional[str] = ..., my_agv: _Optional[_Union[MyAgvConfig, _Mapping]] = ..., src1100_agv: _Optional[_Union[Src1100AgvConfig, _Mapping]] = ...) -> None: ... + +class AGVConfig(_message.Message): + __slots__ = ("agvs",) + AGVS_FIELD_NUMBER: _ClassVar[int] + agvs: _containers.RepeatedCompositeFieldContainer[AGVDeviceConfig] + def __init__(self, agvs: _Optional[_Iterable[_Union[AGVDeviceConfig, _Mapping]]] = ...) -> None: ... + +class AGVRootConfig(_message.Message): + __slots__ = ("agv",) + AGV_FIELD_NUMBER: _ClassVar[int] + agv: AGVConfig + def __init__(self, agv: _Optional[_Union[AGVConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/agv_config/agv_config_pb2_grpc.py b/src/cmvr/config/agv_config/agv_config_pb2_grpc.py new file mode 100644 index 0000000..18b89e7 --- /dev/null +++ b/src/cmvr/config/agv_config/agv_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/agv_config/agv_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-310.pyc b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..081e2b3 Binary files /dev/null and b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-312.pyc b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..c897c71 Binary files /dev/null and b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..ea2f6c8 Binary files /dev/null and b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..e0418da Binary files /dev/null and b/src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/arm_config/arm_config_pb2.py b/src/cmvr/config/arm_config/arm_config_pb2.py new file mode 100644 index 0000000..c5ec716 --- /dev/null +++ b/src/cmvr/config/arm_config/arm_config_pb2.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/arm_config/arm_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/arm_config/arm_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.common import geometry_pb2 as cmvr_dot_common_dot_geometry__pb2 +from cmvr.config import joint_limits_config_pb2 as cmvr_dot_config_dot_joint__limits__config__pb2 +from cmvr.config import lawba_ik_config_pb2 as cmvr_dot_config_dot_lawba__ik__config__pb2 +from cmvr.config import pinocchio_dls_ik_config_pb2 as cmvr_dot_config_dot_pinocchio__dls__ik__config__pb2 +from cmvr.config import pinocchio_qp_ik_config_pb2 as cmvr_dot_config_dot_pinocchio__qp__ik__config__pb2 +from cmvr.config import srs_ik_config_pb2 as cmvr_dot_config_dot_srs__ik__config__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'cmvr/config/arm_config/arm_config.proto\x12\x0b\x63mvr.config\x1a\x1a\x63mvr/common/geometry.proto\x1a%cmvr/config/joint_limits_config.proto\x1a!cmvr/config/lawba_ik_config.proto\x1a)cmvr/config/pinocchio_dls_ik_config.proto\x1a(cmvr/config/pinocchio_qp_ik_config.proto\x1a\x1f\x63mvr/config/srs_ik_config.proto\"\xc1\x01\n\x1aMotorRobotArmBackendConfig\x12\x17\n\x0fmotor_system_id\x18\x02 \x01(\t\x12\x0b\n\x03\x64of\x18\x03 \x01(\x05\x12\x13\n\x0bjoint_names\x18\x04 \x03(\t\x12\x10\n\x08upd_freq\x18\x05 \x01(\x05\x12\x13\n\x0b\x62uffer_size\x18\x06 \x01(\x05\x12\x13\n\x0b\x64\x65\x66\x61ult_vel\x18\x07 \x01(\x01\x12\x13\n\x0b\x64\x65\x66\x61ult_acc\x18\x08 \x01(\x01\x12\x17\n\x0fmotor_group_ids\x18\t \x03(\t\"\xe5\x01\n\x1bVendorRobotArmBackendConfig\x12/\n\x05\x62rand\x18\x01 \x01(\x0e\x32 .cmvr.config.VendorRobotArmBrand\x12\n\n\x02ip\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\x12\x0b\n\x03\x64of\x18\x05 \x01(\x05\x12\x13\n\x0bjoint_names\x18\x06 \x03(\t\x12\x12\n\nbase_frame\x18\x07 \x01(\t\x12\x12\n\ntool_frame\x18\x08 \x01(\t\x12\x10\n\x08username\x18\t \x01(\t\x12\x10\n\x08password\x18\n \x01(\t\"s\n\x1bQpJointLimitAvoidanceConfig\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\x12\x14\n\x0cmargin_ratio\x18\x02 \x01(\x01\x12\x0c\n\x04gain\x18\x03 \x01(\x01\x12\x10\n\x08max_push\x18\x04 \x01(\x01\x12\x0e\n\x06weight\x18\x05 \x01(\x01\"\x9f\x02\n\x19\x43\x61rtesianVelocityQpConfig\x12\x34\n\x0cjoint_limits\x18\x01 \x01(\x0b\x32\x1e.cmvr.config.JointLimitsConfig\x12\x30\n\x15twist_tracking_weight\x18\x02 \x01(\x0b\x32\x11.cmvr.common.Vec6\x12\x1b\n\x13qdot_regularization\x18\x03 \x01(\x01\x12 \n\x18prev_qdot_regularization\x18\x04 \x01(\x01\x12\x12\n\nsolver_eps\x18\x05 \x01(\x01\x12G\n\x15joint_limit_avoidance\x18\x06 \x01(\x0b\x32(.cmvr.config.QpJointLimitAvoidanceConfig\"\xd6\x04\n\x13SpeedLPlannerConfig\x12\x1b\n\x13linear_velocity_max\x18\x01 \x01(\x01\x12\x1f\n\x17linear_acceleration_max\x18\x02 \x01(\x01\x12\x17\n\x0flinear_jerk_max\x18\x03 \x01(\x01\x12\x1c\n\x14\x61ngular_velocity_max\x18\x04 \x01(\x01\x12 \n\x18\x61ngular_acceleration_max\x18\x05 \x01(\x01\x12\x18\n\x10\x61ngular_jerk_max\x18\x06 \x01(\x01\x12\x1e\n\x16joint_acceleration_max\x18\x07 \x03(\x01\x12&\n\x1elinear_target_replan_threshold\x18\x08 \x01(\x01\x12\'\n\x1f\x61ngular_target_replan_threshold\x18\t \x01(\x01\x12$\n\x1clinear_reverse_cos_threshold\x18\n \x01(\x01\x12-\n%linear_reverse_switch_speed_threshold\x18\x0b \x01(\x01\x12&\n\x1enormal_direction_deviation_deg\x18\x0c \x01(\x01\x12$\n\x1cmild_direction_deviation_deg\x18\r \x01(\x01\x12&\n\x1esevere_direction_deviation_deg\x18\x0e \x01(\x01\x12\x1e\n\x16linear_min_speed_ratio\x18\x0f \x01(\x01\x12\x32\n\x02qp\x18\x10 \x01(\x0b\x32&.cmvr.config.CartesianVelocityQpConfig\"\x9f\x01\n!CartesianVelocityControllerConfig\x12\x18\n\x10\x63ontrol_period_s\x18\x01 \x01(\x01\x12\x17\n\x0fstop_twist_norm\x18\x02 \x01(\x01\x12\"\n\x1astop_command_velocity_norm\x18\x03 \x01(\x01\x12#\n\x1bstop_measured_velocity_norm\x18\x04 \x01(\x01\"\x94\x01\n\x1eToppraJointMotionPlannerConfig\x12.\n\tpath_type\x18\x01 \x01(\x0e\x32\x1b.cmvr.config.ToppraPathType\x12\x17\n\x0fsample_period_s\x18\x02 \x01(\x01\x12\x11\n\tgrid_size\x18\x03 \x01(\x05\x12\x16\n\x0ehigh_grid_size\x18\x04 \x01(\x05\"n\n\x0bMoveJConfig\x12R\n\x1btoppra_joint_motion_planner\x18\x01 \x01(\x0b\x32+.cmvr.config.ToppraJointMotionPlannerConfigH\x00\x42\x0b\n\talgorithm\"\x8f\x01\n\x12MoveLPlannerConfig\x12\x17\n\x0fsample_period_s\x18\x01 \x01(\x01\x12\x15\n\rposition_gain\x18\x02 \x01(\x01\x12\x15\n\rrotation_gain\x18\x03 \x01(\x01\x12\x32\n\x02qp\x18\x04 \x01(\x0b\x32&.cmvr.config.CartesianVelocityQpConfig\"\xbf\x01\n\x0bMoveLConfig\x12Q\n&pinocchio_dls_cartesian_motion_planner\x18\x01 \x01(\x0b\x32\x1f.cmvr.config.MoveLPlannerConfigH\x00\x12P\n%pinocchio_qp_cartesian_motion_planner\x18\x02 \x01(\x0b\x32\x1f.cmvr.config.MoveLPlannerConfigH\x00\x42\x0b\n\talgorithm\"~\n\x16SpeedLControllerConfig\x12W\n\x1d\x63\x61rtesian_velocity_controller\x18\x01 \x01(\x0b\x32..cmvr.config.CartesianVelocityControllerConfigH\x00\x42\x0b\n\talgorithm\"\x83\x02\n\x0cSpeedLConfig\x12R\n&pinocchio_dls_cartesian_motion_planner\x18\x01 \x01(\x0b\x32 .cmvr.config.SpeedLPlannerConfigH\x00\x12Q\n%pinocchio_qp_cartesian_motion_planner\x18\x02 \x01(\x0b\x32 .cmvr.config.SpeedLPlannerConfigH\x00\x12?\n\x12speed_l_controller\x18\n \x01(\x0b\x32#.cmvr.config.SpeedLControllerConfigB\x0b\n\talgorithm\"\x96\x02\n\x13\x41rmKinematicsConfig\x12\x44\n\x17pinocchio_dls_ik_solver\x18\n \x01(\x0b\x32!.cmvr.config.PinocchioDlsIKConfigH\x00\x12\x42\n\x16pinocchio_qp_ik_solver\x18\x0b \x01(\x0b\x32 .cmvr.config.PinocchioQpIKConfigH\x00\x12\x31\n\rsrs_ik_solver\x18\x0c \x01(\x0b\x32\x18.cmvr.config.SrsIKConfigH\x00\x12\x35\n\x0flawba_ik_solver\x18\r \x01(\x0b\x32\x1a.cmvr.config.LawbaIKConfigH\x00\x42\x0b\n\talgorithm\"\x97\x01\n\x0f\x41rmMotionConfig\x12(\n\x06move_j\x18\x01 \x01(\x0b\x32\x18.cmvr.config.MoveJConfig\x12(\n\x06move_l\x18\x02 \x01(\x0b\x32\x18.cmvr.config.MoveLConfig\x12*\n\x07speed_l\x18\x04 \x01(\x0b\x32\x19.cmvr.config.SpeedLConfigJ\x04\x08\x03\x10\x04\"\x87\x02\n\x0eRobotArmConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x38\n\x05motor\x18\n \x01(\x0b\x32\'.cmvr.config.MotorRobotArmBackendConfigH\x00\x12:\n\x06vendor\x18\x0b \x01(\x0b\x32(.cmvr.config.VendorRobotArmBackendConfigH\x00\x12\x34\n\nkinematics\x18\x14 \x01(\x0b\x32 .cmvr.config.ArmKinematicsConfig\x12,\n\x06motion\x18\x15 \x01(\x0b\x32\x1c.cmvr.config.ArmMotionConfigB\t\n\x07\x62\x61\x63kendJ\x04\x08\x02\x10\x03\"<\n\tArmConfig\x12/\n\nrobot_arms\x18\x01 \x03(\x0b\x32\x1b.cmvr.config.RobotArmConfig\"4\n\rArmRootConfig\x12#\n\x03\x61rm\x18\x01 \x01(\x0b\x32\x16.cmvr.config.ArmConfig*\xab\x01\n\x0eToppraPathType\x12\x1c\n\x18TOPPRA_PATH_TYPE_UNKNOWN\x10\x00\x12\x1b\n\x17TOPPRA_PATH_TYPE_LINEAR\x10\x01\x12\"\n\x1eTOPPRA_PATH_TYPE_CUBIC_HERMITE\x10\x02\x12\x1c\n\x18TOPPRA_PATH_TYPE_QUINTIC\x10\x03\x12\x1c\n\x18TOPPRA_PATH_TYPE_NATURAL\x10\x04*\x85\x01\n\x13VendorRobotArmBrand\x12\"\n\x1eVENDOR_ROBOT_ARM_BRAND_UNKNOWN\x10\x00\x12#\n\x1fVENDOR_ROBOT_ARM_BRAND_AUBO_ARM\x10\x01\x12%\n!VENDOR_ROBOT_ARM_BRAND_HUAYAN_ARM\x10\x02\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.arm_config.arm_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TOPPRAPATHTYPE']._serialized_start=3685 + _globals['_TOPPRAPATHTYPE']._serialized_end=3856 + _globals['_VENDORROBOTARMBRAND']._serialized_start=3859 + _globals['_VENDORROBOTARMBRAND']._serialized_end=3992 + _globals['_MOTORROBOTARMBACKENDCONFIG']._serialized_start=277 + _globals['_MOTORROBOTARMBACKENDCONFIG']._serialized_end=470 + _globals['_VENDORROBOTARMBACKENDCONFIG']._serialized_start=473 + _globals['_VENDORROBOTARMBACKENDCONFIG']._serialized_end=702 + _globals['_QPJOINTLIMITAVOIDANCECONFIG']._serialized_start=704 + _globals['_QPJOINTLIMITAVOIDANCECONFIG']._serialized_end=819 + _globals['_CARTESIANVELOCITYQPCONFIG']._serialized_start=822 + _globals['_CARTESIANVELOCITYQPCONFIG']._serialized_end=1109 + _globals['_SPEEDLPLANNERCONFIG']._serialized_start=1112 + _globals['_SPEEDLPLANNERCONFIG']._serialized_end=1710 + _globals['_CARTESIANVELOCITYCONTROLLERCONFIG']._serialized_start=1713 + _globals['_CARTESIANVELOCITYCONTROLLERCONFIG']._serialized_end=1872 + _globals['_TOPPRAJOINTMOTIONPLANNERCONFIG']._serialized_start=1875 + _globals['_TOPPRAJOINTMOTIONPLANNERCONFIG']._serialized_end=2023 + _globals['_MOVEJCONFIG']._serialized_start=2025 + _globals['_MOVEJCONFIG']._serialized_end=2135 + _globals['_MOVELPLANNERCONFIG']._serialized_start=2138 + _globals['_MOVELPLANNERCONFIG']._serialized_end=2281 + _globals['_MOVELCONFIG']._serialized_start=2284 + _globals['_MOVELCONFIG']._serialized_end=2475 + _globals['_SPEEDLCONTROLLERCONFIG']._serialized_start=2477 + _globals['_SPEEDLCONTROLLERCONFIG']._serialized_end=2603 + _globals['_SPEEDLCONFIG']._serialized_start=2606 + _globals['_SPEEDLCONFIG']._serialized_end=2865 + _globals['_ARMKINEMATICSCONFIG']._serialized_start=2868 + _globals['_ARMKINEMATICSCONFIG']._serialized_end=3146 + _globals['_ARMMOTIONCONFIG']._serialized_start=3149 + _globals['_ARMMOTIONCONFIG']._serialized_end=3300 + _globals['_ROBOTARMCONFIG']._serialized_start=3303 + _globals['_ROBOTARMCONFIG']._serialized_end=3566 + _globals['_ARMCONFIG']._serialized_start=3568 + _globals['_ARMCONFIG']._serialized_end=3628 + _globals['_ARMROOTCONFIG']._serialized_start=3630 + _globals['_ARMROOTCONFIG']._serialized_end=3682 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/arm_config/arm_config_pb2.pyi b/src/cmvr/config/arm_config/arm_config_pb2.pyi new file mode 100644 index 0000000..b5c4aa8 --- /dev/null +++ b/src/cmvr/config/arm_config/arm_config_pb2.pyi @@ -0,0 +1,260 @@ +from cmvr.common import geometry_pb2 as _geometry_pb2 +from cmvr.config import joint_limits_config_pb2 as _joint_limits_config_pb2 +from cmvr.config import lawba_ik_config_pb2 as _lawba_ik_config_pb2 +from cmvr.config import pinocchio_dls_ik_config_pb2 as _pinocchio_dls_ik_config_pb2 +from cmvr.config import pinocchio_qp_ik_config_pb2 as _pinocchio_qp_ik_config_pb2 +from cmvr.config import srs_ik_config_pb2 as _srs_ik_config_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ToppraPathType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TOPPRA_PATH_TYPE_UNKNOWN: _ClassVar[ToppraPathType] + TOPPRA_PATH_TYPE_LINEAR: _ClassVar[ToppraPathType] + TOPPRA_PATH_TYPE_CUBIC_HERMITE: _ClassVar[ToppraPathType] + TOPPRA_PATH_TYPE_QUINTIC: _ClassVar[ToppraPathType] + TOPPRA_PATH_TYPE_NATURAL: _ClassVar[ToppraPathType] + +class VendorRobotArmBrand(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + VENDOR_ROBOT_ARM_BRAND_UNKNOWN: _ClassVar[VendorRobotArmBrand] + VENDOR_ROBOT_ARM_BRAND_AUBO_ARM: _ClassVar[VendorRobotArmBrand] + VENDOR_ROBOT_ARM_BRAND_HUAYAN_ARM: _ClassVar[VendorRobotArmBrand] +TOPPRA_PATH_TYPE_UNKNOWN: ToppraPathType +TOPPRA_PATH_TYPE_LINEAR: ToppraPathType +TOPPRA_PATH_TYPE_CUBIC_HERMITE: ToppraPathType +TOPPRA_PATH_TYPE_QUINTIC: ToppraPathType +TOPPRA_PATH_TYPE_NATURAL: ToppraPathType +VENDOR_ROBOT_ARM_BRAND_UNKNOWN: VendorRobotArmBrand +VENDOR_ROBOT_ARM_BRAND_AUBO_ARM: VendorRobotArmBrand +VENDOR_ROBOT_ARM_BRAND_HUAYAN_ARM: VendorRobotArmBrand + +class MotorRobotArmBackendConfig(_message.Message): + __slots__ = ("motor_system_id", "dof", "joint_names", "upd_freq", "buffer_size", "default_vel", "default_acc", "motor_group_ids") + MOTOR_SYSTEM_ID_FIELD_NUMBER: _ClassVar[int] + DOF_FIELD_NUMBER: _ClassVar[int] + JOINT_NAMES_FIELD_NUMBER: _ClassVar[int] + UPD_FREQ_FIELD_NUMBER: _ClassVar[int] + BUFFER_SIZE_FIELD_NUMBER: _ClassVar[int] + DEFAULT_VEL_FIELD_NUMBER: _ClassVar[int] + DEFAULT_ACC_FIELD_NUMBER: _ClassVar[int] + MOTOR_GROUP_IDS_FIELD_NUMBER: _ClassVar[int] + motor_system_id: str + dof: int + joint_names: _containers.RepeatedScalarFieldContainer[str] + upd_freq: int + buffer_size: int + default_vel: float + default_acc: float + motor_group_ids: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, motor_system_id: _Optional[str] = ..., dof: _Optional[int] = ..., joint_names: _Optional[_Iterable[str]] = ..., upd_freq: _Optional[int] = ..., buffer_size: _Optional[int] = ..., default_vel: _Optional[float] = ..., default_acc: _Optional[float] = ..., motor_group_ids: _Optional[_Iterable[str]] = ...) -> None: ... + +class VendorRobotArmBackendConfig(_message.Message): + __slots__ = ("brand", "ip", "port", "model", "dof", "joint_names", "base_frame", "tool_frame", "username", "password") + BRAND_FIELD_NUMBER: _ClassVar[int] + IP_FIELD_NUMBER: _ClassVar[int] + PORT_FIELD_NUMBER: _ClassVar[int] + MODEL_FIELD_NUMBER: _ClassVar[int] + DOF_FIELD_NUMBER: _ClassVar[int] + JOINT_NAMES_FIELD_NUMBER: _ClassVar[int] + BASE_FRAME_FIELD_NUMBER: _ClassVar[int] + TOOL_FRAME_FIELD_NUMBER: _ClassVar[int] + USERNAME_FIELD_NUMBER: _ClassVar[int] + PASSWORD_FIELD_NUMBER: _ClassVar[int] + brand: VendorRobotArmBrand + ip: str + port: int + model: str + dof: int + joint_names: _containers.RepeatedScalarFieldContainer[str] + base_frame: str + tool_frame: str + username: str + password: str + def __init__(self, brand: _Optional[_Union[VendorRobotArmBrand, str]] = ..., ip: _Optional[str] = ..., port: _Optional[int] = ..., model: _Optional[str] = ..., dof: _Optional[int] = ..., joint_names: _Optional[_Iterable[str]] = ..., base_frame: _Optional[str] = ..., tool_frame: _Optional[str] = ..., username: _Optional[str] = ..., password: _Optional[str] = ...) -> None: ... + +class QpJointLimitAvoidanceConfig(_message.Message): + __slots__ = ("enable", "margin_ratio", "gain", "max_push", "weight") + ENABLE_FIELD_NUMBER: _ClassVar[int] + MARGIN_RATIO_FIELD_NUMBER: _ClassVar[int] + GAIN_FIELD_NUMBER: _ClassVar[int] + MAX_PUSH_FIELD_NUMBER: _ClassVar[int] + WEIGHT_FIELD_NUMBER: _ClassVar[int] + enable: bool + margin_ratio: float + gain: float + max_push: float + weight: float + def __init__(self, enable: _Optional[bool] = ..., margin_ratio: _Optional[float] = ..., gain: _Optional[float] = ..., max_push: _Optional[float] = ..., weight: _Optional[float] = ...) -> None: ... + +class CartesianVelocityQpConfig(_message.Message): + __slots__ = ("joint_limits", "twist_tracking_weight", "qdot_regularization", "prev_qdot_regularization", "solver_eps", "joint_limit_avoidance") + JOINT_LIMITS_FIELD_NUMBER: _ClassVar[int] + TWIST_TRACKING_WEIGHT_FIELD_NUMBER: _ClassVar[int] + QDOT_REGULARIZATION_FIELD_NUMBER: _ClassVar[int] + PREV_QDOT_REGULARIZATION_FIELD_NUMBER: _ClassVar[int] + SOLVER_EPS_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMIT_AVOIDANCE_FIELD_NUMBER: _ClassVar[int] + joint_limits: _joint_limits_config_pb2.JointLimitsConfig + twist_tracking_weight: _geometry_pb2.Vec6 + qdot_regularization: float + prev_qdot_regularization: float + solver_eps: float + joint_limit_avoidance: QpJointLimitAvoidanceConfig + def __init__(self, joint_limits: _Optional[_Union[_joint_limits_config_pb2.JointLimitsConfig, _Mapping]] = ..., twist_tracking_weight: _Optional[_Union[_geometry_pb2.Vec6, _Mapping]] = ..., qdot_regularization: _Optional[float] = ..., prev_qdot_regularization: _Optional[float] = ..., solver_eps: _Optional[float] = ..., joint_limit_avoidance: _Optional[_Union[QpJointLimitAvoidanceConfig, _Mapping]] = ...) -> None: ... + +class SpeedLPlannerConfig(_message.Message): + __slots__ = ("linear_velocity_max", "linear_acceleration_max", "linear_jerk_max", "angular_velocity_max", "angular_acceleration_max", "angular_jerk_max", "joint_acceleration_max", "linear_target_replan_threshold", "angular_target_replan_threshold", "linear_reverse_cos_threshold", "linear_reverse_switch_speed_threshold", "normal_direction_deviation_deg", "mild_direction_deviation_deg", "severe_direction_deviation_deg", "linear_min_speed_ratio", "qp") + LINEAR_VELOCITY_MAX_FIELD_NUMBER: _ClassVar[int] + LINEAR_ACCELERATION_MAX_FIELD_NUMBER: _ClassVar[int] + LINEAR_JERK_MAX_FIELD_NUMBER: _ClassVar[int] + ANGULAR_VELOCITY_MAX_FIELD_NUMBER: _ClassVar[int] + ANGULAR_ACCELERATION_MAX_FIELD_NUMBER: _ClassVar[int] + ANGULAR_JERK_MAX_FIELD_NUMBER: _ClassVar[int] + JOINT_ACCELERATION_MAX_FIELD_NUMBER: _ClassVar[int] + LINEAR_TARGET_REPLAN_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + ANGULAR_TARGET_REPLAN_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + LINEAR_REVERSE_COS_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + LINEAR_REVERSE_SWITCH_SPEED_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + NORMAL_DIRECTION_DEVIATION_DEG_FIELD_NUMBER: _ClassVar[int] + MILD_DIRECTION_DEVIATION_DEG_FIELD_NUMBER: _ClassVar[int] + SEVERE_DIRECTION_DEVIATION_DEG_FIELD_NUMBER: _ClassVar[int] + LINEAR_MIN_SPEED_RATIO_FIELD_NUMBER: _ClassVar[int] + QP_FIELD_NUMBER: _ClassVar[int] + linear_velocity_max: float + linear_acceleration_max: float + linear_jerk_max: float + angular_velocity_max: float + angular_acceleration_max: float + angular_jerk_max: float + joint_acceleration_max: _containers.RepeatedScalarFieldContainer[float] + linear_target_replan_threshold: float + angular_target_replan_threshold: float + linear_reverse_cos_threshold: float + linear_reverse_switch_speed_threshold: float + normal_direction_deviation_deg: float + mild_direction_deviation_deg: float + severe_direction_deviation_deg: float + linear_min_speed_ratio: float + qp: CartesianVelocityQpConfig + def __init__(self, linear_velocity_max: _Optional[float] = ..., linear_acceleration_max: _Optional[float] = ..., linear_jerk_max: _Optional[float] = ..., angular_velocity_max: _Optional[float] = ..., angular_acceleration_max: _Optional[float] = ..., angular_jerk_max: _Optional[float] = ..., joint_acceleration_max: _Optional[_Iterable[float]] = ..., linear_target_replan_threshold: _Optional[float] = ..., angular_target_replan_threshold: _Optional[float] = ..., linear_reverse_cos_threshold: _Optional[float] = ..., linear_reverse_switch_speed_threshold: _Optional[float] = ..., normal_direction_deviation_deg: _Optional[float] = ..., mild_direction_deviation_deg: _Optional[float] = ..., severe_direction_deviation_deg: _Optional[float] = ..., linear_min_speed_ratio: _Optional[float] = ..., qp: _Optional[_Union[CartesianVelocityQpConfig, _Mapping]] = ...) -> None: ... + +class CartesianVelocityControllerConfig(_message.Message): + __slots__ = ("control_period_s", "stop_twist_norm", "stop_command_velocity_norm", "stop_measured_velocity_norm") + CONTROL_PERIOD_S_FIELD_NUMBER: _ClassVar[int] + STOP_TWIST_NORM_FIELD_NUMBER: _ClassVar[int] + STOP_COMMAND_VELOCITY_NORM_FIELD_NUMBER: _ClassVar[int] + STOP_MEASURED_VELOCITY_NORM_FIELD_NUMBER: _ClassVar[int] + control_period_s: float + stop_twist_norm: float + stop_command_velocity_norm: float + stop_measured_velocity_norm: float + def __init__(self, control_period_s: _Optional[float] = ..., stop_twist_norm: _Optional[float] = ..., stop_command_velocity_norm: _Optional[float] = ..., stop_measured_velocity_norm: _Optional[float] = ...) -> None: ... + +class ToppraJointMotionPlannerConfig(_message.Message): + __slots__ = ("path_type", "sample_period_s", "grid_size", "high_grid_size") + PATH_TYPE_FIELD_NUMBER: _ClassVar[int] + SAMPLE_PERIOD_S_FIELD_NUMBER: _ClassVar[int] + GRID_SIZE_FIELD_NUMBER: _ClassVar[int] + HIGH_GRID_SIZE_FIELD_NUMBER: _ClassVar[int] + path_type: ToppraPathType + sample_period_s: float + grid_size: int + high_grid_size: int + def __init__(self, path_type: _Optional[_Union[ToppraPathType, str]] = ..., sample_period_s: _Optional[float] = ..., grid_size: _Optional[int] = ..., high_grid_size: _Optional[int] = ...) -> None: ... + +class MoveJConfig(_message.Message): + __slots__ = ("toppra_joint_motion_planner",) + TOPPRA_JOINT_MOTION_PLANNER_FIELD_NUMBER: _ClassVar[int] + toppra_joint_motion_planner: ToppraJointMotionPlannerConfig + def __init__(self, toppra_joint_motion_planner: _Optional[_Union[ToppraJointMotionPlannerConfig, _Mapping]] = ...) -> None: ... + +class MoveLPlannerConfig(_message.Message): + __slots__ = ("sample_period_s", "position_gain", "rotation_gain", "qp") + SAMPLE_PERIOD_S_FIELD_NUMBER: _ClassVar[int] + POSITION_GAIN_FIELD_NUMBER: _ClassVar[int] + ROTATION_GAIN_FIELD_NUMBER: _ClassVar[int] + QP_FIELD_NUMBER: _ClassVar[int] + sample_period_s: float + position_gain: float + rotation_gain: float + qp: CartesianVelocityQpConfig + def __init__(self, sample_period_s: _Optional[float] = ..., position_gain: _Optional[float] = ..., rotation_gain: _Optional[float] = ..., qp: _Optional[_Union[CartesianVelocityQpConfig, _Mapping]] = ...) -> None: ... + +class MoveLConfig(_message.Message): + __slots__ = ("pinocchio_dls_cartesian_motion_planner", "pinocchio_qp_cartesian_motion_planner") + PINOCCHIO_DLS_CARTESIAN_MOTION_PLANNER_FIELD_NUMBER: _ClassVar[int] + PINOCCHIO_QP_CARTESIAN_MOTION_PLANNER_FIELD_NUMBER: _ClassVar[int] + pinocchio_dls_cartesian_motion_planner: MoveLPlannerConfig + pinocchio_qp_cartesian_motion_planner: MoveLPlannerConfig + def __init__(self, pinocchio_dls_cartesian_motion_planner: _Optional[_Union[MoveLPlannerConfig, _Mapping]] = ..., pinocchio_qp_cartesian_motion_planner: _Optional[_Union[MoveLPlannerConfig, _Mapping]] = ...) -> None: ... + +class SpeedLControllerConfig(_message.Message): + __slots__ = ("cartesian_velocity_controller",) + CARTESIAN_VELOCITY_CONTROLLER_FIELD_NUMBER: _ClassVar[int] + cartesian_velocity_controller: CartesianVelocityControllerConfig + def __init__(self, cartesian_velocity_controller: _Optional[_Union[CartesianVelocityControllerConfig, _Mapping]] = ...) -> None: ... + +class SpeedLConfig(_message.Message): + __slots__ = ("pinocchio_dls_cartesian_motion_planner", "pinocchio_qp_cartesian_motion_planner", "speed_l_controller") + PINOCCHIO_DLS_CARTESIAN_MOTION_PLANNER_FIELD_NUMBER: _ClassVar[int] + PINOCCHIO_QP_CARTESIAN_MOTION_PLANNER_FIELD_NUMBER: _ClassVar[int] + SPEED_L_CONTROLLER_FIELD_NUMBER: _ClassVar[int] + pinocchio_dls_cartesian_motion_planner: SpeedLPlannerConfig + pinocchio_qp_cartesian_motion_planner: SpeedLPlannerConfig + speed_l_controller: SpeedLControllerConfig + def __init__(self, pinocchio_dls_cartesian_motion_planner: _Optional[_Union[SpeedLPlannerConfig, _Mapping]] = ..., pinocchio_qp_cartesian_motion_planner: _Optional[_Union[SpeedLPlannerConfig, _Mapping]] = ..., speed_l_controller: _Optional[_Union[SpeedLControllerConfig, _Mapping]] = ...) -> None: ... + +class ArmKinematicsConfig(_message.Message): + __slots__ = ("pinocchio_dls_ik_solver", "pinocchio_qp_ik_solver", "srs_ik_solver", "lawba_ik_solver") + PINOCCHIO_DLS_IK_SOLVER_FIELD_NUMBER: _ClassVar[int] + PINOCCHIO_QP_IK_SOLVER_FIELD_NUMBER: _ClassVar[int] + SRS_IK_SOLVER_FIELD_NUMBER: _ClassVar[int] + LAWBA_IK_SOLVER_FIELD_NUMBER: _ClassVar[int] + pinocchio_dls_ik_solver: _pinocchio_dls_ik_config_pb2.PinocchioDlsIKConfig + pinocchio_qp_ik_solver: _pinocchio_qp_ik_config_pb2.PinocchioQpIKConfig + srs_ik_solver: _srs_ik_config_pb2.SrsIKConfig + lawba_ik_solver: _lawba_ik_config_pb2.LawbaIKConfig + def __init__(self, pinocchio_dls_ik_solver: _Optional[_Union[_pinocchio_dls_ik_config_pb2.PinocchioDlsIKConfig, _Mapping]] = ..., pinocchio_qp_ik_solver: _Optional[_Union[_pinocchio_qp_ik_config_pb2.PinocchioQpIKConfig, _Mapping]] = ..., srs_ik_solver: _Optional[_Union[_srs_ik_config_pb2.SrsIKConfig, _Mapping]] = ..., lawba_ik_solver: _Optional[_Union[_lawba_ik_config_pb2.LawbaIKConfig, _Mapping]] = ...) -> None: ... + +class ArmMotionConfig(_message.Message): + __slots__ = ("move_j", "move_l", "speed_l") + MOVE_J_FIELD_NUMBER: _ClassVar[int] + MOVE_L_FIELD_NUMBER: _ClassVar[int] + SPEED_L_FIELD_NUMBER: _ClassVar[int] + move_j: MoveJConfig + move_l: MoveLConfig + speed_l: SpeedLConfig + def __init__(self, move_j: _Optional[_Union[MoveJConfig, _Mapping]] = ..., move_l: _Optional[_Union[MoveLConfig, _Mapping]] = ..., speed_l: _Optional[_Union[SpeedLConfig, _Mapping]] = ...) -> None: ... + +class RobotArmConfig(_message.Message): + __slots__ = ("id", "motor", "vendor", "kinematics", "motion") + ID_FIELD_NUMBER: _ClassVar[int] + MOTOR_FIELD_NUMBER: _ClassVar[int] + VENDOR_FIELD_NUMBER: _ClassVar[int] + KINEMATICS_FIELD_NUMBER: _ClassVar[int] + MOTION_FIELD_NUMBER: _ClassVar[int] + id: str + motor: MotorRobotArmBackendConfig + vendor: VendorRobotArmBackendConfig + kinematics: ArmKinematicsConfig + motion: ArmMotionConfig + def __init__(self, id: _Optional[str] = ..., motor: _Optional[_Union[MotorRobotArmBackendConfig, _Mapping]] = ..., vendor: _Optional[_Union[VendorRobotArmBackendConfig, _Mapping]] = ..., kinematics: _Optional[_Union[ArmKinematicsConfig, _Mapping]] = ..., motion: _Optional[_Union[ArmMotionConfig, _Mapping]] = ...) -> None: ... + +class ArmConfig(_message.Message): + __slots__ = ("robot_arms",) + ROBOT_ARMS_FIELD_NUMBER: _ClassVar[int] + robot_arms: _containers.RepeatedCompositeFieldContainer[RobotArmConfig] + def __init__(self, robot_arms: _Optional[_Iterable[_Union[RobotArmConfig, _Mapping]]] = ...) -> None: ... + +class ArmRootConfig(_message.Message): + __slots__ = ("arm",) + ARM_FIELD_NUMBER: _ClassVar[int] + arm: ArmConfig + def __init__(self, arm: _Optional[_Union[ArmConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/arm_config/arm_config_pb2_grpc.py b/src/cmvr/config/arm_config/arm_config_pb2_grpc.py new file mode 100644 index 0000000..e75605d --- /dev/null +++ b/src/cmvr/config/arm_config/arm_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/arm_config/arm_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-310.pyc b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..5bde9c3 Binary files /dev/null and b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-312.pyc b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..e297722 Binary files /dev/null and b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..4a55020 Binary files /dev/null and b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..7a7aa1a Binary files /dev/null and b/src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.py b/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.py new file mode 100644 index 0000000..6816aa9 --- /dev/null +++ b/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/aubo_robot_config/aubo_robot_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/aubo_robot_config/aubo_robot_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n5cmvr/config/aubo_robot_config/aubo_robot_config.proto\x12\x0b\x63mvr.config\"[\n\x0f\x41uboRobotConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\x10\n\x08username\x18\x04 \x01(\t\x12\x10\n\x08password\x18\x05 \x01(\tb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.aubo_robot_config.aubo_robot_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_AUBOROBOTCONFIG']._serialized_start=70 + _globals['_AUBOROBOTCONFIG']._serialized_end=161 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.pyi b/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.pyi new file mode 100644 index 0000000..10d0a6f --- /dev/null +++ b/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.pyi @@ -0,0 +1,19 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class AuboRobotConfig(_message.Message): + __slots__ = ("id", "ip", "port", "username", "password") + ID_FIELD_NUMBER: _ClassVar[int] + IP_FIELD_NUMBER: _ClassVar[int] + PORT_FIELD_NUMBER: _ClassVar[int] + USERNAME_FIELD_NUMBER: _ClassVar[int] + PASSWORD_FIELD_NUMBER: _ClassVar[int] + id: str + ip: str + port: int + username: str + password: str + def __init__(self, id: _Optional[str] = ..., ip: _Optional[str] = ..., port: _Optional[int] = ..., username: _Optional[str] = ..., password: _Optional[str] = ...) -> None: ... diff --git a/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2_grpc.py b/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2_grpc.py new file mode 100644 index 0000000..390b828 --- /dev/null +++ b/src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/aubo_robot_config/aubo_robot_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-310.pyc b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..a902be1 Binary files /dev/null and b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-312.pyc b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..fc23359 Binary files /dev/null and b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..c51de0e Binary files /dev/null and b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..0267183 Binary files /dev/null and b/src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/biohead_config/biohead_config_pb2.py b/src/cmvr/config/biohead_config/biohead_config_pb2.py new file mode 100644 index 0000000..d39c9de --- /dev/null +++ b/src/cmvr/config/biohead_config/biohead_config_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/biohead_config/biohead_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/biohead_config/biohead_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/biohead_config/biohead_config.proto\x12\x0b\x63mvr.config\"\x9c\x01\n\x17\x42ioHeadServoGroupConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\x05\x12\x15\n\rchannel_start\x18\x03 \x01(\x05\x12\x13\n\x0b\x63hannel_end\x18\x04 \x01(\x05\x12\x0e\n\x06offset\x18\x05 \x03(\x01\x12\x12\n\nmin_angles\x18\x06 \x03(\x01\x12\x12\n\nmax_angles\x18\x07 \x03(\x01\"\x84\x01\n\x12\x42ioHeadRobotConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bserial_port\x18\x02 \x01(\t\x12\x11\n\tbaud_rate\x18\x03 \x01(\x05\x12:\n\x0cservo_groups\x18\x04 \x03(\x0b\x32$.cmvr.config.BioHeadServoGroupConfig\"K\n\x16\x42ioHeadRobotRootConfig\x12\x31\n\x08\x62io_head\x18\x01 \x01(\x0b\x32\x1f.cmvr.config.BioHeadRobotConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.biohead_config.biohead_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_BIOHEADSERVOGROUPCONFIG']._serialized_start=65 + _globals['_BIOHEADSERVOGROUPCONFIG']._serialized_end=221 + _globals['_BIOHEADROBOTCONFIG']._serialized_start=224 + _globals['_BIOHEADROBOTCONFIG']._serialized_end=356 + _globals['_BIOHEADROBOTROOTCONFIG']._serialized_start=358 + _globals['_BIOHEADROBOTROOTCONFIG']._serialized_end=433 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/biohead_config/biohead_config_pb2.pyi b/src/cmvr/config/biohead_config/biohead_config_pb2.pyi new file mode 100644 index 0000000..34787c3 --- /dev/null +++ b/src/cmvr/config/biohead_config/biohead_config_pb2.pyi @@ -0,0 +1,43 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class BioHeadServoGroupConfig(_message.Message): + __slots__ = ("name", "address", "channel_start", "channel_end", "offset", "min_angles", "max_angles") + NAME_FIELD_NUMBER: _ClassVar[int] + ADDRESS_FIELD_NUMBER: _ClassVar[int] + CHANNEL_START_FIELD_NUMBER: _ClassVar[int] + CHANNEL_END_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + MIN_ANGLES_FIELD_NUMBER: _ClassVar[int] + MAX_ANGLES_FIELD_NUMBER: _ClassVar[int] + name: str + address: int + channel_start: int + channel_end: int + offset: _containers.RepeatedScalarFieldContainer[float] + min_angles: _containers.RepeatedScalarFieldContainer[float] + max_angles: _containers.RepeatedScalarFieldContainer[float] + def __init__(self, name: _Optional[str] = ..., address: _Optional[int] = ..., channel_start: _Optional[int] = ..., channel_end: _Optional[int] = ..., offset: _Optional[_Iterable[float]] = ..., min_angles: _Optional[_Iterable[float]] = ..., max_angles: _Optional[_Iterable[float]] = ...) -> None: ... + +class BioHeadRobotConfig(_message.Message): + __slots__ = ("id", "serial_port", "baud_rate", "servo_groups") + ID_FIELD_NUMBER: _ClassVar[int] + SERIAL_PORT_FIELD_NUMBER: _ClassVar[int] + BAUD_RATE_FIELD_NUMBER: _ClassVar[int] + SERVO_GROUPS_FIELD_NUMBER: _ClassVar[int] + id: str + serial_port: str + baud_rate: int + servo_groups: _containers.RepeatedCompositeFieldContainer[BioHeadServoGroupConfig] + def __init__(self, id: _Optional[str] = ..., serial_port: _Optional[str] = ..., baud_rate: _Optional[int] = ..., servo_groups: _Optional[_Iterable[_Union[BioHeadServoGroupConfig, _Mapping]]] = ...) -> None: ... + +class BioHeadRobotRootConfig(_message.Message): + __slots__ = ("bio_head",) + BIO_HEAD_FIELD_NUMBER: _ClassVar[int] + bio_head: BioHeadRobotConfig + def __init__(self, bio_head: _Optional[_Union[BioHeadRobotConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/biohead_config/biohead_config_pb2_grpc.py b/src/cmvr/config/biohead_config/biohead_config_pb2_grpc.py new file mode 100644 index 0000000..4bc14cb --- /dev/null +++ b/src/cmvr/config/biohead_config/biohead_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/biohead_config/biohead_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-310.pyc b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..d17b87f Binary files /dev/null and b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-312.pyc b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..233c6cc Binary files /dev/null and b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..4b25031 Binary files /dev/null and b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..80e1bd5 Binary files /dev/null and b/src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/camera_config/camera_config_pb2.py b/src/cmvr/config/camera_config/camera_config_pb2.py new file mode 100644 index 0000000..ae2aa4e --- /dev/null +++ b/src/cmvr/config/camera_config/camera_config_pb2.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/camera_config/camera_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/camera_config/camera_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n-cmvr/config/camera_config/camera_config.proto\x12\x0b\x63mvr.config\"\xd2\x02\n\x15RealSenseCameraConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cserialNumber\x18\x02 \x01(\t\x12\r\n\x05width\x18\x03 \x01(\x05\x12\x0e\n\x06height\x18\x04 \x01(\x05\x12\x0b\n\x03\x66ps\x18\x05 \x01(\x05\x12\r\n\x05\x63odec\x18\x06 \x01(\t\x12,\n\x0b\x63\x61mera_mode\x18\x07 \x01(\x0e\x32\x17.cmvr.config.CameraMode\x12,\n\x0bstream_mode\x18\x08 \x01(\x0e\x32\x17.cmvr.config.StreamMode\x12*\n\nalign_mode\x18\t \x01(\x0e\x32\x16.cmvr.config.AlignMode\x12\x13\n\x0b\x62uffer_size\x18\n \x01(\x05\x12\x0c\n\x04sync\x18\x0b \x01(\x08\x12\x14\n\x0c\x65ncode_width\x18\r \x01(\x05\x12\x15\n\rencode_height\x18\x0e \x01(\x05J\x04\x08\x0c\x10\r\"\x89\x02\n\x0fUVCCameraConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03usb\x18\x02 \x01(\t\x12\r\n\x05width\x18\x03 \x01(\x05\x12\x0e\n\x06height\x18\x04 \x01(\x05\x12\x0b\n\x03\x66ps\x18\x05 \x01(\x05\x12\r\n\x05\x63odec\x18\x06 \x01(\t\x12,\n\x0b\x63\x61mera_mode\x18\x07 \x01(\x0e\x32\x17.cmvr.config.CameraMode\x12,\n\x0bstream_mode\x18\x08 \x01(\x0e\x32\x17.cmvr.config.StreamMode\x12\x13\n\x0b\x62uffer_size\x18\t \x01(\x05\x12\x14\n\x0c\x65ncode_width\x18\x0b \x01(\x05\x12\x15\n\rencode_height\x18\x0c \x01(\x05J\x04\x08\n\x10\x0b\"h\n\x14MechMindCameraConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\x12\x12\n\x05\x61lign\x18\x03 \x01(\x08H\x00\x88\x01\x01\x12\x14\n\x0cimage2d_type\x18\x04 \x01(\tB\x08\n\x06_alignJ\x04\x08\x05\x10\x06\"\xce\x01\n\x12\x43\x61meraDeviceConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\x03uvc\x18\n \x01(\x0b\x32\x1c.cmvr.config.UVCCameraConfigH\x00\x12\x37\n\trealsense\x18\x0b \x01(\x0b\x32\".cmvr.config.RealSenseCameraConfigH\x00\x12\x35\n\x08mechmind\x18\x0c \x01(\x0b\x32!.cmvr.config.MechMindCameraConfigH\x00\x42\t\n\x07\x62\x61\x63kendJ\x04\x08\x02\x10\x03\"@\n\x0c\x43\x61meraConfig\x12\x30\n\x07\x63\x61meras\x18\x01 \x03(\x0b\x32\x1f.cmvr.config.CameraDeviceConfig\"=\n\x10\x43\x61meraRootConfig\x12)\n\x06\x63\x61mera\x18\x01 \x01(\x0b\x32\x19.cmvr.config.CameraConfig*:\n\nCameraMode\x12\x15\n\x11\x43\x41MERA_MODE_PHOTO\x10\x00\x12\x15\n\x11\x43\x41MERA_MODE_VIDEO\x10\x01*N\n\nStreamMode\x12\x13\n\x0fSTREAM_MODE_RGB\x10\x00\x12\x14\n\x10STREAM_MODE_RGBD\x10\x01\x12\x15\n\x11STREAM_MODE_DEPTH\x10\x02*7\n\tAlignMode\x12\x14\n\x10\x41LIGN_MODE_COLOR\x10\x00\x12\x14\n\x10\x41LIGN_MODE_DEPTH\x10\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.camera_config.camera_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_CAMERAMODE']._serialized_start=1115 + _globals['_CAMERAMODE']._serialized_end=1173 + _globals['_STREAMMODE']._serialized_start=1175 + _globals['_STREAMMODE']._serialized_end=1253 + _globals['_ALIGNMODE']._serialized_start=1255 + _globals['_ALIGNMODE']._serialized_end=1310 + _globals['_REALSENSECAMERACONFIG']._serialized_start=63 + _globals['_REALSENSECAMERACONFIG']._serialized_end=401 + _globals['_UVCCAMERACONFIG']._serialized_start=404 + _globals['_UVCCAMERACONFIG']._serialized_end=669 + _globals['_MECHMINDCAMERACONFIG']._serialized_start=671 + _globals['_MECHMINDCAMERACONFIG']._serialized_end=775 + _globals['_CAMERADEVICECONFIG']._serialized_start=778 + _globals['_CAMERADEVICECONFIG']._serialized_end=984 + _globals['_CAMERACONFIG']._serialized_start=986 + _globals['_CAMERACONFIG']._serialized_end=1050 + _globals['_CAMERAROOTCONFIG']._serialized_start=1052 + _globals['_CAMERAROOTCONFIG']._serialized_end=1113 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/camera_config/camera_config_pb2.pyi b/src/cmvr/config/camera_config/camera_config_pb2.pyi new file mode 100644 index 0000000..96d41b9 --- /dev/null +++ b/src/cmvr/config/camera_config/camera_config_pb2.pyi @@ -0,0 +1,123 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class CameraMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + CAMERA_MODE_PHOTO: _ClassVar[CameraMode] + CAMERA_MODE_VIDEO: _ClassVar[CameraMode] + +class StreamMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + STREAM_MODE_RGB: _ClassVar[StreamMode] + STREAM_MODE_RGBD: _ClassVar[StreamMode] + STREAM_MODE_DEPTH: _ClassVar[StreamMode] + +class AlignMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ALIGN_MODE_COLOR: _ClassVar[AlignMode] + ALIGN_MODE_DEPTH: _ClassVar[AlignMode] +CAMERA_MODE_PHOTO: CameraMode +CAMERA_MODE_VIDEO: CameraMode +STREAM_MODE_RGB: StreamMode +STREAM_MODE_RGBD: StreamMode +STREAM_MODE_DEPTH: StreamMode +ALIGN_MODE_COLOR: AlignMode +ALIGN_MODE_DEPTH: AlignMode + +class RealSenseCameraConfig(_message.Message): + __slots__ = ("id", "serialNumber", "width", "height", "fps", "codec", "camera_mode", "stream_mode", "align_mode", "buffer_size", "sync", "encode_width", "encode_height") + ID_FIELD_NUMBER: _ClassVar[int] + SERIALNUMBER_FIELD_NUMBER: _ClassVar[int] + WIDTH_FIELD_NUMBER: _ClassVar[int] + HEIGHT_FIELD_NUMBER: _ClassVar[int] + FPS_FIELD_NUMBER: _ClassVar[int] + CODEC_FIELD_NUMBER: _ClassVar[int] + CAMERA_MODE_FIELD_NUMBER: _ClassVar[int] + STREAM_MODE_FIELD_NUMBER: _ClassVar[int] + ALIGN_MODE_FIELD_NUMBER: _ClassVar[int] + BUFFER_SIZE_FIELD_NUMBER: _ClassVar[int] + SYNC_FIELD_NUMBER: _ClassVar[int] + ENCODE_WIDTH_FIELD_NUMBER: _ClassVar[int] + ENCODE_HEIGHT_FIELD_NUMBER: _ClassVar[int] + id: str + serialNumber: str + width: int + height: int + fps: int + codec: str + camera_mode: CameraMode + stream_mode: StreamMode + align_mode: AlignMode + buffer_size: int + sync: bool + encode_width: int + encode_height: int + def __init__(self, id: _Optional[str] = ..., serialNumber: _Optional[str] = ..., width: _Optional[int] = ..., height: _Optional[int] = ..., fps: _Optional[int] = ..., codec: _Optional[str] = ..., camera_mode: _Optional[_Union[CameraMode, str]] = ..., stream_mode: _Optional[_Union[StreamMode, str]] = ..., align_mode: _Optional[_Union[AlignMode, str]] = ..., buffer_size: _Optional[int] = ..., sync: _Optional[bool] = ..., encode_width: _Optional[int] = ..., encode_height: _Optional[int] = ...) -> None: ... + +class UVCCameraConfig(_message.Message): + __slots__ = ("id", "usb", "width", "height", "fps", "codec", "camera_mode", "stream_mode", "buffer_size", "encode_width", "encode_height") + ID_FIELD_NUMBER: _ClassVar[int] + USB_FIELD_NUMBER: _ClassVar[int] + WIDTH_FIELD_NUMBER: _ClassVar[int] + HEIGHT_FIELD_NUMBER: _ClassVar[int] + FPS_FIELD_NUMBER: _ClassVar[int] + CODEC_FIELD_NUMBER: _ClassVar[int] + CAMERA_MODE_FIELD_NUMBER: _ClassVar[int] + STREAM_MODE_FIELD_NUMBER: _ClassVar[int] + BUFFER_SIZE_FIELD_NUMBER: _ClassVar[int] + ENCODE_WIDTH_FIELD_NUMBER: _ClassVar[int] + ENCODE_HEIGHT_FIELD_NUMBER: _ClassVar[int] + id: str + usb: str + width: int + height: int + fps: int + codec: str + camera_mode: CameraMode + stream_mode: StreamMode + buffer_size: int + encode_width: int + encode_height: int + def __init__(self, id: _Optional[str] = ..., usb: _Optional[str] = ..., width: _Optional[int] = ..., height: _Optional[int] = ..., fps: _Optional[int] = ..., codec: _Optional[str] = ..., camera_mode: _Optional[_Union[CameraMode, str]] = ..., stream_mode: _Optional[_Union[StreamMode, str]] = ..., buffer_size: _Optional[int] = ..., encode_width: _Optional[int] = ..., encode_height: _Optional[int] = ...) -> None: ... + +class MechMindCameraConfig(_message.Message): + __slots__ = ("id", "ip", "align", "image2d_type") + ID_FIELD_NUMBER: _ClassVar[int] + IP_FIELD_NUMBER: _ClassVar[int] + ALIGN_FIELD_NUMBER: _ClassVar[int] + IMAGE2D_TYPE_FIELD_NUMBER: _ClassVar[int] + id: str + ip: str + align: bool + image2d_type: str + def __init__(self, id: _Optional[str] = ..., ip: _Optional[str] = ..., align: _Optional[bool] = ..., image2d_type: _Optional[str] = ...) -> None: ... + +class CameraDeviceConfig(_message.Message): + __slots__ = ("id", "uvc", "realsense", "mechmind") + ID_FIELD_NUMBER: _ClassVar[int] + UVC_FIELD_NUMBER: _ClassVar[int] + REALSENSE_FIELD_NUMBER: _ClassVar[int] + MECHMIND_FIELD_NUMBER: _ClassVar[int] + id: str + uvc: UVCCameraConfig + realsense: RealSenseCameraConfig + mechmind: MechMindCameraConfig + def __init__(self, id: _Optional[str] = ..., uvc: _Optional[_Union[UVCCameraConfig, _Mapping]] = ..., realsense: _Optional[_Union[RealSenseCameraConfig, _Mapping]] = ..., mechmind: _Optional[_Union[MechMindCameraConfig, _Mapping]] = ...) -> None: ... + +class CameraConfig(_message.Message): + __slots__ = ("cameras",) + CAMERAS_FIELD_NUMBER: _ClassVar[int] + cameras: _containers.RepeatedCompositeFieldContainer[CameraDeviceConfig] + def __init__(self, cameras: _Optional[_Iterable[_Union[CameraDeviceConfig, _Mapping]]] = ...) -> None: ... + +class CameraRootConfig(_message.Message): + __slots__ = ("camera",) + CAMERA_FIELD_NUMBER: _ClassVar[int] + camera: CameraConfig + def __init__(self, camera: _Optional[_Union[CameraConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/camera_config/camera_config_pb2_grpc.py b/src/cmvr/config/camera_config/camera_config_pb2_grpc.py new file mode 100644 index 0000000..f9f5ad4 --- /dev/null +++ b/src/cmvr/config/camera_config/camera_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/camera_config/camera_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-310.pyc b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..22266c8 Binary files /dev/null and b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-312.pyc b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..0f81662 Binary files /dev/null and b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..1d5bcb8 Binary files /dev/null and b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..907b13d Binary files /dev/null and b/src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.py b/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.py new file mode 100644 index 0000000..d357f3a --- /dev/null +++ b/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/cmvr_es_config/cmvr_es_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/cmvr_es_config/cmvr_es_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/cmvr_es_config/cmvr_es_config.proto\x12\x0b\x63mvr.config\"\x9d\x01\n\x0c\x43MVRESConfig\x12\x1a\n\x12logger_config_file\x18\x01 \x01(\t\x12\"\n\x1a\x64\x65vice_manager_config_file\x18\x02 \x01(\t\x12 \n\x18task_manager_config_file\x18\x04 \x01(\t\x12\x1f\n\x17grpc_server_config_file\x18\x06 \x01(\tJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06\">\n\x10\x43MVRESRootConfig\x12*\n\x07\x63mvr_es\x18\x01 \x01(\x0b\x32\x19.cmvr.config.CMVRESConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.cmvr_es_config.cmvr_es_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_CMVRESCONFIG']._serialized_start=65 + _globals['_CMVRESCONFIG']._serialized_end=222 + _globals['_CMVRESROOTCONFIG']._serialized_start=224 + _globals['_CMVRESROOTCONFIG']._serialized_end=286 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.pyi b/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.pyi new file mode 100644 index 0000000..ce0019e --- /dev/null +++ b/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.pyi @@ -0,0 +1,24 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class CMVRESConfig(_message.Message): + __slots__ = ("logger_config_file", "device_manager_config_file", "task_manager_config_file", "grpc_server_config_file") + LOGGER_CONFIG_FILE_FIELD_NUMBER: _ClassVar[int] + DEVICE_MANAGER_CONFIG_FILE_FIELD_NUMBER: _ClassVar[int] + TASK_MANAGER_CONFIG_FILE_FIELD_NUMBER: _ClassVar[int] + GRPC_SERVER_CONFIG_FILE_FIELD_NUMBER: _ClassVar[int] + logger_config_file: str + device_manager_config_file: str + task_manager_config_file: str + grpc_server_config_file: str + def __init__(self, logger_config_file: _Optional[str] = ..., device_manager_config_file: _Optional[str] = ..., task_manager_config_file: _Optional[str] = ..., grpc_server_config_file: _Optional[str] = ...) -> None: ... + +class CMVRESRootConfig(_message.Message): + __slots__ = ("cmvr_es",) + CMVR_ES_FIELD_NUMBER: _ClassVar[int] + cmvr_es: CMVRESConfig + def __init__(self, cmvr_es: _Optional[_Union[CMVRESConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2_grpc.py b/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2_grpc.py new file mode 100644 index 0000000..05e5367 --- /dev/null +++ b/src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/cmvr_es_config/cmvr_es_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-310.pyc b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..b528c6d Binary files /dev/null and b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-312.pyc b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..cdff94d Binary files /dev/null and b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..4197d06 Binary files /dev/null and b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..0bef689 Binary files /dev/null and b/src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/device_manager_config/device_manager_config_pb2.py b/src/cmvr/config/device_manager_config/device_manager_config_pb2.py new file mode 100644 index 0000000..b487e0e --- /dev/null +++ b/src/cmvr/config/device_manager_config/device_manager_config_pb2.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/device_manager_config/device_manager_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/device_manager_config/device_manager_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n=cmvr/config/device_manager_config/device_manager_config.proto\x12\x0b\x63mvr.config\"\xa4\x05\n\x11\x44\x65viceConfigEntry\x12\n\n\x02id\x18\x01 \x01(\t\x12\x37\n\x04type\x18\x02 \x01(\x0e\x32).cmvr.config.DeviceConfigEntry.DeviceType\x12\x13\n\x0b\x63onfig_file\x18\x03 \x01(\t\x12\x0e\n\x06\x65nable\x18\x04 \x01(\x08\"\xa4\x04\n\nDeviceType\x12\x17\n\x13\x44\x45VICE_TYPE_UNKNOWN\x10\x00\x12\x1e\n\x1a\x44\x45VICE_TYPE_BIO_HEAD_ROBOT\x10\x08\x12\x1c\n\x18\x44\x45VICE_TYPE_MOTOR_SYSTEM\x10\t\x12\x19\n\x15\x44\x45VICE_TYPE_ROBOT_ARM\x10\x0c\x12\x16\n\x12\x44\x45VICE_TYPE_CAMERA\x10\r\x12\x17\n\x13\x44\x45VICE_TYPE_DEXHAND\x10\x0e\x12\x1a\n\x16\x44\x45VICE_TYPE_MICROPHONE\x10\x0f\x12\x17\n\x13\x44\x45VICE_TYPE_SPEAKER\x10\x10\x12\x13\n\x0f\x44\x45VICE_TYPE_AGV\x10\x11\"\x04\x08\x01\x10\x01\"\x04\x08\x02\x10\x02\"\x04\x08\x03\x10\x03\"\x04\x08\x04\x10\x04\"\x04\x08\x05\x10\x05\"\x04\x08\x06\x10\x06\"\x04\x08\x07\x10\x07\"\x04\x08\n\x10\n\"\x04\x08\x0b\x10\x0b*\x16\x44\x45VICE_TYPE_UVC_CAMERA*\x1c\x44\x45VICE_TYPE_REALSENSE_CAMERA*\x1c\x44\x45VICE_TYPE_RH56DFTP_DEXHAND*\x16\x44\x45VICE_TYPE_PX6AX_GEN3*\x1d\x44\x45VICE_TYPE_FFMPEG_MICROPHONE*\x1a\x44\x45VICE_TYPE_FFMPEG_SPEAKER*\x1a\x44\x45VICE_TYPE_HUMANOID_ROBOT*\x15\x44\x45VICE_TYPE_MOTOR_ARM*\x19\x44\x45VICE_TYPE_CARTESIAN_ARM\"z\n\x13\x44\x65viceManagerConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12/\n\x07\x64\x65vices\x18\x04 \x03(\x0b\x32\x1e.cmvr.config.DeviceConfigEntry\"S\n\x17\x44\x65viceManagerRootConfig\x12\x38\n\x0e\x64\x65vice_manager\x18\x01 \x01(\x0b\x32 .cmvr.config.DeviceManagerConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.device_manager_config.device_manager_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_DEVICECONFIGENTRY']._serialized_start=79 + _globals['_DEVICECONFIGENTRY']._serialized_end=755 + _globals['_DEVICECONFIGENTRY_DEVICETYPE']._serialized_start=207 + _globals['_DEVICECONFIGENTRY_DEVICETYPE']._serialized_end=755 + _globals['_DEVICEMANAGERCONFIG']._serialized_start=757 + _globals['_DEVICEMANAGERCONFIG']._serialized_end=879 + _globals['_DEVICEMANAGERROOTCONFIG']._serialized_start=881 + _globals['_DEVICEMANAGERROOTCONFIG']._serialized_end=964 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/device_manager_config/device_manager_config_pb2.pyi b/src/cmvr/config/device_manager_config/device_manager_config_pb2.pyi new file mode 100644 index 0000000..92801ce --- /dev/null +++ b/src/cmvr/config/device_manager_config/device_manager_config_pb2.pyi @@ -0,0 +1,58 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class DeviceConfigEntry(_message.Message): + __slots__ = ("id", "type", "config_file", "enable") + class DeviceType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + DEVICE_TYPE_UNKNOWN: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_BIO_HEAD_ROBOT: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_MOTOR_SYSTEM: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_ROBOT_ARM: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_CAMERA: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_DEXHAND: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_MICROPHONE: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_SPEAKER: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_AGV: _ClassVar[DeviceConfigEntry.DeviceType] + DEVICE_TYPE_UNKNOWN: DeviceConfigEntry.DeviceType + DEVICE_TYPE_BIO_HEAD_ROBOT: DeviceConfigEntry.DeviceType + DEVICE_TYPE_MOTOR_SYSTEM: DeviceConfigEntry.DeviceType + DEVICE_TYPE_ROBOT_ARM: DeviceConfigEntry.DeviceType + DEVICE_TYPE_CAMERA: DeviceConfigEntry.DeviceType + DEVICE_TYPE_DEXHAND: DeviceConfigEntry.DeviceType + DEVICE_TYPE_MICROPHONE: DeviceConfigEntry.DeviceType + DEVICE_TYPE_SPEAKER: DeviceConfigEntry.DeviceType + DEVICE_TYPE_AGV: DeviceConfigEntry.DeviceType + ID_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + CONFIG_FILE_FIELD_NUMBER: _ClassVar[int] + ENABLE_FIELD_NUMBER: _ClassVar[int] + id: str + type: DeviceConfigEntry.DeviceType + config_file: str + enable: bool + def __init__(self, id: _Optional[str] = ..., type: _Optional[_Union[DeviceConfigEntry.DeviceType, str]] = ..., config_file: _Optional[str] = ..., enable: _Optional[bool] = ...) -> None: ... + +class DeviceManagerConfig(_message.Message): + __slots__ = ("name", "version", "description", "devices") + NAME_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + DEVICES_FIELD_NUMBER: _ClassVar[int] + name: str + version: str + description: str + devices: _containers.RepeatedCompositeFieldContainer[DeviceConfigEntry] + def __init__(self, name: _Optional[str] = ..., version: _Optional[str] = ..., description: _Optional[str] = ..., devices: _Optional[_Iterable[_Union[DeviceConfigEntry, _Mapping]]] = ...) -> None: ... + +class DeviceManagerRootConfig(_message.Message): + __slots__ = ("device_manager",) + DEVICE_MANAGER_FIELD_NUMBER: _ClassVar[int] + device_manager: DeviceManagerConfig + def __init__(self, device_manager: _Optional[_Union[DeviceManagerConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/device_manager_config/device_manager_config_pb2_grpc.py b/src/cmvr/config/device_manager_config/device_manager_config_pb2_grpc.py new file mode 100644 index 0000000..828fc9d --- /dev/null +++ b/src/cmvr/config/device_manager_config/device_manager_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/device_manager_config/device_manager_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-310.pyc b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..bb053cc Binary files /dev/null and b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-312.pyc b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..9002119 Binary files /dev/null and b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..19b8c7a Binary files /dev/null and b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..5351738 Binary files /dev/null and b/src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/dexhand_config/dexhand_config_pb2.py b/src/cmvr/config/dexhand_config/dexhand_config_pb2.py new file mode 100644 index 0000000..d5fb86e --- /dev/null +++ b/src/cmvr/config/dexhand_config/dexhand_config_pb2.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/dexhand_config/dexhand_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/dexhand_config/dexhand_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/dexhand_config/dexhand_config.proto\x12\x0b\x63mvr.config\"]\n\x15RH56DFTPDexHandConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\x18\n\x10poll_interval_ms\x18\x05 \x01(\x05J\x04\x08\x04\x10\x05\"\xc5\x03\n\tPX6AXGen3\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bserial_port\x18\x02 \x01(\t\x12\x14\n\x0csensor_model\x18\x03 \x01(\t\x12\x11\n\tmodule_id\x18\x04 \x01(\x05\x12\x11\n\tbaud_rate\x18\x05 \x01(\x05\x12\x1a\n\x12\x64istributed_length\x18\x06 \x01(\x05\x12\x18\n\x10resultant_length\x18\x07 \x01(\x05\x12\x18\n\x10poll_interval_ms\x18\x08 \x01(\x05\x12\x16\n\x0e\x61uto_calibrate\x18\t \x01(\x08\x12\x1b\n\x13response_timeout_ms\x18\x0b \x01(\x05\x12\x14\n\x0ctactile_rows\x18\x0c \x01(\x05\x12\x14\n\x0ctactile_cols\x18\r \x01(\x05\x12\x1d\n\x15response_header_bytes\x18\x0e \x01(\x05\x12\x16\n\x0etactile_finger\x18\x0f \x01(\t\x12\x16\n\x0etactile_region\x18\x10 \x01(\t\x12\x13\n\x0bsensor_name\x18\x11 \x01(\t\x12@\n\x11polling_read_mode\x18\x12 \x01(\x0e\x32%.cmvr.config.PX6AXGen3PollingReadModeJ\x04\x08\n\x10\x0b\"\x99\x01\n\x13\x44\x65xHandDeviceConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x36\n\x08rh56dftp\x18\n \x01(\x0b\x32\".cmvr.config.RH56DFTPDexHandConfigH\x00\x12-\n\x0bpx_6ax_gen3\x18\x0b \x01(\x0b\x32\x16.cmvr.config.PX6AXGen3H\x00\x42\t\n\x07\x62\x61\x63kendJ\x04\x08\x02\x10\x03\"C\n\rDexHandConfig\x12\x32\n\x08\x64\x65xhands\x18\x01 \x03(\x0b\x32 .cmvr.config.DexHandDeviceConfig\"@\n\x11\x44\x65xHandRootConfig\x12+\n\x07\x64\x65xhand\x18\x01 \x01(\x0b\x32\x1a.cmvr.config.DexHandConfig*\xc5\x01\n\x18PX6AXGen3PollingReadMode\x12\x41\n=PX_6AX_GEN3_POLLING_READ_MODE_DISTRIBUTED_AND_RESULTANT_FORCE\x10\x00\x12\x33\n/PX_6AX_GEN3_POLLING_READ_MODE_DISTRIBUTED_FORCE\x10\x01\x12\x31\n-PX_6AX_GEN3_POLLING_READ_MODE_RESULTANT_FORCE\x10\x02\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.dexhand_config.dexhand_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_PX6AXGEN3POLLINGREADMODE']._serialized_start=907 + _globals['_PX6AXGEN3POLLINGREADMODE']._serialized_end=1104 + _globals['_RH56DFTPDEXHANDCONFIG']._serialized_start=64 + _globals['_RH56DFTPDEXHANDCONFIG']._serialized_end=157 + _globals['_PX6AXGEN3']._serialized_start=160 + _globals['_PX6AXGEN3']._serialized_end=613 + _globals['_DEXHANDDEVICECONFIG']._serialized_start=616 + _globals['_DEXHANDDEVICECONFIG']._serialized_end=769 + _globals['_DEXHANDCONFIG']._serialized_start=771 + _globals['_DEXHANDCONFIG']._serialized_end=838 + _globals['_DEXHANDROOTCONFIG']._serialized_start=840 + _globals['_DEXHANDROOTCONFIG']._serialized_end=904 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/dexhand_config/dexhand_config_pb2.pyi b/src/cmvr/config/dexhand_config/dexhand_config_pb2.pyi new file mode 100644 index 0000000..9e695f5 --- /dev/null +++ b/src/cmvr/config/dexhand_config/dexhand_config_pb2.pyi @@ -0,0 +1,89 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class PX6AXGen3PollingReadMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PX_6AX_GEN3_POLLING_READ_MODE_DISTRIBUTED_AND_RESULTANT_FORCE: _ClassVar[PX6AXGen3PollingReadMode] + PX_6AX_GEN3_POLLING_READ_MODE_DISTRIBUTED_FORCE: _ClassVar[PX6AXGen3PollingReadMode] + PX_6AX_GEN3_POLLING_READ_MODE_RESULTANT_FORCE: _ClassVar[PX6AXGen3PollingReadMode] +PX_6AX_GEN3_POLLING_READ_MODE_DISTRIBUTED_AND_RESULTANT_FORCE: PX6AXGen3PollingReadMode +PX_6AX_GEN3_POLLING_READ_MODE_DISTRIBUTED_FORCE: PX6AXGen3PollingReadMode +PX_6AX_GEN3_POLLING_READ_MODE_RESULTANT_FORCE: PX6AXGen3PollingReadMode + +class RH56DFTPDexHandConfig(_message.Message): + __slots__ = ("id", "ip", "port", "poll_interval_ms") + ID_FIELD_NUMBER: _ClassVar[int] + IP_FIELD_NUMBER: _ClassVar[int] + PORT_FIELD_NUMBER: _ClassVar[int] + POLL_INTERVAL_MS_FIELD_NUMBER: _ClassVar[int] + id: str + ip: str + port: int + poll_interval_ms: int + def __init__(self, id: _Optional[str] = ..., ip: _Optional[str] = ..., port: _Optional[int] = ..., poll_interval_ms: _Optional[int] = ...) -> None: ... + +class PX6AXGen3(_message.Message): + __slots__ = ("id", "serial_port", "sensor_model", "module_id", "baud_rate", "distributed_length", "resultant_length", "poll_interval_ms", "auto_calibrate", "response_timeout_ms", "tactile_rows", "tactile_cols", "response_header_bytes", "tactile_finger", "tactile_region", "sensor_name", "polling_read_mode") + ID_FIELD_NUMBER: _ClassVar[int] + SERIAL_PORT_FIELD_NUMBER: _ClassVar[int] + SENSOR_MODEL_FIELD_NUMBER: _ClassVar[int] + MODULE_ID_FIELD_NUMBER: _ClassVar[int] + BAUD_RATE_FIELD_NUMBER: _ClassVar[int] + DISTRIBUTED_LENGTH_FIELD_NUMBER: _ClassVar[int] + RESULTANT_LENGTH_FIELD_NUMBER: _ClassVar[int] + POLL_INTERVAL_MS_FIELD_NUMBER: _ClassVar[int] + AUTO_CALIBRATE_FIELD_NUMBER: _ClassVar[int] + RESPONSE_TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int] + TACTILE_ROWS_FIELD_NUMBER: _ClassVar[int] + TACTILE_COLS_FIELD_NUMBER: _ClassVar[int] + RESPONSE_HEADER_BYTES_FIELD_NUMBER: _ClassVar[int] + TACTILE_FINGER_FIELD_NUMBER: _ClassVar[int] + TACTILE_REGION_FIELD_NUMBER: _ClassVar[int] + SENSOR_NAME_FIELD_NUMBER: _ClassVar[int] + POLLING_READ_MODE_FIELD_NUMBER: _ClassVar[int] + id: str + serial_port: str + sensor_model: str + module_id: int + baud_rate: int + distributed_length: int + resultant_length: int + poll_interval_ms: int + auto_calibrate: bool + response_timeout_ms: int + tactile_rows: int + tactile_cols: int + response_header_bytes: int + tactile_finger: str + tactile_region: str + sensor_name: str + polling_read_mode: PX6AXGen3PollingReadMode + def __init__(self, id: _Optional[str] = ..., serial_port: _Optional[str] = ..., sensor_model: _Optional[str] = ..., module_id: _Optional[int] = ..., baud_rate: _Optional[int] = ..., distributed_length: _Optional[int] = ..., resultant_length: _Optional[int] = ..., poll_interval_ms: _Optional[int] = ..., auto_calibrate: _Optional[bool] = ..., response_timeout_ms: _Optional[int] = ..., tactile_rows: _Optional[int] = ..., tactile_cols: _Optional[int] = ..., response_header_bytes: _Optional[int] = ..., tactile_finger: _Optional[str] = ..., tactile_region: _Optional[str] = ..., sensor_name: _Optional[str] = ..., polling_read_mode: _Optional[_Union[PX6AXGen3PollingReadMode, str]] = ...) -> None: ... + +class DexHandDeviceConfig(_message.Message): + __slots__ = ("id", "rh56dftp", "px_6ax_gen3") + ID_FIELD_NUMBER: _ClassVar[int] + RH56DFTP_FIELD_NUMBER: _ClassVar[int] + PX_6AX_GEN3_FIELD_NUMBER: _ClassVar[int] + id: str + rh56dftp: RH56DFTPDexHandConfig + px_6ax_gen3: PX6AXGen3 + def __init__(self, id: _Optional[str] = ..., rh56dftp: _Optional[_Union[RH56DFTPDexHandConfig, _Mapping]] = ..., px_6ax_gen3: _Optional[_Union[PX6AXGen3, _Mapping]] = ...) -> None: ... + +class DexHandConfig(_message.Message): + __slots__ = ("dexhands",) + DEXHANDS_FIELD_NUMBER: _ClassVar[int] + dexhands: _containers.RepeatedCompositeFieldContainer[DexHandDeviceConfig] + def __init__(self, dexhands: _Optional[_Iterable[_Union[DexHandDeviceConfig, _Mapping]]] = ...) -> None: ... + +class DexHandRootConfig(_message.Message): + __slots__ = ("dexhand",) + DEXHAND_FIELD_NUMBER: _ClassVar[int] + dexhand: DexHandConfig + def __init__(self, dexhand: _Optional[_Union[DexHandConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py b/src/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py new file mode 100644 index 0000000..a750f92 --- /dev/null +++ b/src/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-310.pyc b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..4a82ba4 Binary files /dev/null and b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-312.pyc b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..0def31d Binary files /dev/null and b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..4f04f4f Binary files /dev/null and b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..23b3afa Binary files /dev/null and b/src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/grpc_server_config/grpc_server_config_pb2.py b/src/cmvr/config/grpc_server_config/grpc_server_config_pb2.py new file mode 100644 index 0000000..5d41463 --- /dev/null +++ b/src/cmvr/config/grpc_server_config/grpc_server_config_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/grpc_server_config/grpc_server_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/grpc_server_config/grpc_server_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n7cmvr/config/grpc_server_config/grpc_server_config.proto\x12\x0b\x63mvr.config\"U\n\x10GRPCServerConfig\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0c\n\x04port\x18\x02 \x01(\t\x12\x19\n\x11\x65nable_reflection\x18\x03 \x01(\x08\x12\n\n\x02id\x18\x04 \x01(\t\"J\n\x14GRPCServerRootConfig\x12\x32\n\x0bgrpc_server\x18\x01 \x01(\x0b\x32\x1d.cmvr.config.GRPCServerConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.grpc_server_config.grpc_server_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_GRPCSERVERCONFIG']._serialized_start=72 + _globals['_GRPCSERVERCONFIG']._serialized_end=157 + _globals['_GRPCSERVERROOTCONFIG']._serialized_start=159 + _globals['_GRPCSERVERROOTCONFIG']._serialized_end=233 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/grpc_server_config/grpc_server_config_pb2.pyi b/src/cmvr/config/grpc_server_config/grpc_server_config_pb2.pyi new file mode 100644 index 0000000..458e702 --- /dev/null +++ b/src/cmvr/config/grpc_server_config/grpc_server_config_pb2.pyi @@ -0,0 +1,24 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class GRPCServerConfig(_message.Message): + __slots__ = ("host", "port", "enable_reflection", "id") + HOST_FIELD_NUMBER: _ClassVar[int] + PORT_FIELD_NUMBER: _ClassVar[int] + ENABLE_REFLECTION_FIELD_NUMBER: _ClassVar[int] + ID_FIELD_NUMBER: _ClassVar[int] + host: str + port: str + enable_reflection: bool + id: str + def __init__(self, host: _Optional[str] = ..., port: _Optional[str] = ..., enable_reflection: _Optional[bool] = ..., id: _Optional[str] = ...) -> None: ... + +class GRPCServerRootConfig(_message.Message): + __slots__ = ("grpc_server",) + GRPC_SERVER_FIELD_NUMBER: _ClassVar[int] + grpc_server: GRPCServerConfig + def __init__(self, grpc_server: _Optional[_Union[GRPCServerConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/grpc_server_config/grpc_server_config_pb2_grpc.py b/src/cmvr/config/grpc_server_config/grpc_server_config_pb2_grpc.py new file mode 100644 index 0000000..82d5de1 --- /dev/null +++ b/src/cmvr/config/grpc_server_config/grpc_server_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/grpc_server_config/grpc_server_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/joint_limits_config_pb2.py b/src/cmvr/config/joint_limits_config_pb2.py new file mode 100644 index 0000000..e4a4cf7 --- /dev/null +++ b/src/cmvr/config/joint_limits_config_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/joint_limits_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/joint_limits_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%cmvr/config/joint_limits_config.proto\x12\x0b\x63mvr.config\"V\n\x10JointLimitConfig\x12\x12\n\njoint_name\x18\x01 \x01(\t\x12\r\n\x05lower\x18\x02 \x01(\x01\x12\r\n\x05upper\x18\x03 \x01(\x01\x12\x10\n\x08velocity\x18\x04 \x01(\x01\"q\n\x11JointLimitsConfig\x12-\n\x06source\x18\x01 \x01(\x0e\x32\x1d.cmvr.config.JointLimitSource\x12-\n\x06joints\x18\x02 \x03(\x0b\x32\x1d.cmvr.config.JointLimitConfig*n\n\x10JointLimitSource\x12\x1e\n\x1aJOINT_LIMIT_SOURCE_UNKNOWN\x10\x00\x12\x1b\n\x17JOINT_LIMIT_SOURCE_URDF\x10\x01\x12\x1d\n\x19JOINT_LIMIT_SOURCE_CUSTOM\x10\x02\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.joint_limits_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_JOINTLIMITSOURCE']._serialized_start=257 + _globals['_JOINTLIMITSOURCE']._serialized_end=367 + _globals['_JOINTLIMITCONFIG']._serialized_start=54 + _globals['_JOINTLIMITCONFIG']._serialized_end=140 + _globals['_JOINTLIMITSCONFIG']._serialized_start=142 + _globals['_JOINTLIMITSCONFIG']._serialized_end=255 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/joint_limits_config_pb2.pyi b/src/cmvr/config/joint_limits_config_pb2.pyi new file mode 100644 index 0000000..5a48aba --- /dev/null +++ b/src/cmvr/config/joint_limits_config_pb2.pyi @@ -0,0 +1,37 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class JointLimitSource(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + JOINT_LIMIT_SOURCE_UNKNOWN: _ClassVar[JointLimitSource] + JOINT_LIMIT_SOURCE_URDF: _ClassVar[JointLimitSource] + JOINT_LIMIT_SOURCE_CUSTOM: _ClassVar[JointLimitSource] +JOINT_LIMIT_SOURCE_UNKNOWN: JointLimitSource +JOINT_LIMIT_SOURCE_URDF: JointLimitSource +JOINT_LIMIT_SOURCE_CUSTOM: JointLimitSource + +class JointLimitConfig(_message.Message): + __slots__ = ("joint_name", "lower", "upper", "velocity") + JOINT_NAME_FIELD_NUMBER: _ClassVar[int] + LOWER_FIELD_NUMBER: _ClassVar[int] + UPPER_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + joint_name: str + lower: float + upper: float + velocity: float + def __init__(self, joint_name: _Optional[str] = ..., lower: _Optional[float] = ..., upper: _Optional[float] = ..., velocity: _Optional[float] = ...) -> None: ... + +class JointLimitsConfig(_message.Message): + __slots__ = ("source", "joints") + SOURCE_FIELD_NUMBER: _ClassVar[int] + JOINTS_FIELD_NUMBER: _ClassVar[int] + source: JointLimitSource + joints: _containers.RepeatedCompositeFieldContainer[JointLimitConfig] + def __init__(self, source: _Optional[_Union[JointLimitSource, str]] = ..., joints: _Optional[_Iterable[_Union[JointLimitConfig, _Mapping]]] = ...) -> None: ... diff --git a/src/cmvr/config/joint_limits_config_pb2_grpc.py b/src/cmvr/config/joint_limits_config_pb2_grpc.py new file mode 100644 index 0000000..de4465e --- /dev/null +++ b/src/cmvr/config/joint_limits_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/joint_limits_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/lawba_ik_config_pb2.py b/src/cmvr/config/lawba_ik_config_pb2.py new file mode 100644 index 0000000..33c6278 --- /dev/null +++ b/src/cmvr/config/lawba_ik_config_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/lawba_ik_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/lawba_ik_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.config import srs_ik_config_pb2 as cmvr_dot_config_dot_srs__ik__config__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!cmvr/config/lawba_ik_config.proto\x12\x0b\x63mvr.config\x1a\x1f\x63mvr/config/srs_ik_config.proto\"\xd5\x01\n\rLawbaIKConfig\x12,\n\nsrs_config\x18\x01 \x01(\x0b\x32\x18.cmvr.config.SrsIKConfig\x12\x10\n\x08lambda_q\x18\x02 \x01(\x01\x12!\n\x19\x63ur_branch_cost_threshold\x18\x03 \x01(\x01\x12\x1c\n\x14opt_psi_update_alpha\x18\x04 \x01(\x01\x12\x19\n\x11opt_psi_max_delta\x18\x05 \x01(\x01\x12\x13\n\x0bopt_psi_min\x18\x06 \x01(\x01\x12\x13\n\x0bopt_psi_eps\x18\x07 \x01(\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.lawba_ik_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_LAWBAIKCONFIG']._serialized_start=84 + _globals['_LAWBAIKCONFIG']._serialized_end=297 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/lawba_ik_config_pb2.pyi b/src/cmvr/config/lawba_ik_config_pb2.pyi new file mode 100644 index 0000000..1cf0ccc --- /dev/null +++ b/src/cmvr/config/lawba_ik_config_pb2.pyi @@ -0,0 +1,25 @@ +from cmvr.config import srs_ik_config_pb2 as _srs_ik_config_pb2 +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class LawbaIKConfig(_message.Message): + __slots__ = ("srs_config", "lambda_q", "cur_branch_cost_threshold", "opt_psi_update_alpha", "opt_psi_max_delta", "opt_psi_min", "opt_psi_eps") + SRS_CONFIG_FIELD_NUMBER: _ClassVar[int] + LAMBDA_Q_FIELD_NUMBER: _ClassVar[int] + CUR_BRANCH_COST_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + OPT_PSI_UPDATE_ALPHA_FIELD_NUMBER: _ClassVar[int] + OPT_PSI_MAX_DELTA_FIELD_NUMBER: _ClassVar[int] + OPT_PSI_MIN_FIELD_NUMBER: _ClassVar[int] + OPT_PSI_EPS_FIELD_NUMBER: _ClassVar[int] + srs_config: _srs_ik_config_pb2.SrsIKConfig + lambda_q: float + cur_branch_cost_threshold: float + opt_psi_update_alpha: float + opt_psi_max_delta: float + opt_psi_min: float + opt_psi_eps: float + def __init__(self, srs_config: _Optional[_Union[_srs_ik_config_pb2.SrsIKConfig, _Mapping]] = ..., lambda_q: _Optional[float] = ..., cur_branch_cost_threshold: _Optional[float] = ..., opt_psi_update_alpha: _Optional[float] = ..., opt_psi_max_delta: _Optional[float] = ..., opt_psi_min: _Optional[float] = ..., opt_psi_eps: _Optional[float] = ...) -> None: ... diff --git a/src/cmvr/config/lawba_ik_config_pb2_grpc.py b/src/cmvr/config/lawba_ik_config_pb2_grpc.py new file mode 100644 index 0000000..820a36d --- /dev/null +++ b/src/cmvr/config/lawba_ik_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/lawba_ik_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-310.pyc b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..b552b80 Binary files /dev/null and b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-312.pyc b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..08e6a9f Binary files /dev/null and b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..5e17bd9 Binary files /dev/null and b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..002d0ba Binary files /dev/null and b/src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/logger_config/logger_config_pb2.py b/src/cmvr/config/logger_config/logger_config_pb2.py new file mode 100644 index 0000000..8c4572b --- /dev/null +++ b/src/cmvr/config/logger_config/logger_config_pb2.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/logger_config/logger_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/logger_config/logger_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n-cmvr/config/logger_config/logger_config.proto\x12\x0b\x63mvr.config\"P\n\x08LogRoute\x12$\n\x05level\x18\x01 \x01(\x0e\x32\x15.cmvr.config.LogLevel\x12\x10\n\x08terminal\x18\x02 \x01(\x08\x12\x0c\n\x04\x66ile\x18\x03 \x01(\x08\"\xc5\x01\n\tLogFormat\x12\x16\n\tshow_time\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x17\n\nshow_level\x18\x02 \x01(\x08H\x01\x88\x01\x01\x12\x1b\n\x0eshow_thread_id\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12!\n\x14show_source_location\x18\x04 \x01(\x08H\x03\x88\x01\x01\x42\x0c\n\n_show_timeB\r\n\x0b_show_levelB\x11\n\x0f_show_thread_idB\x17\n\x15_show_source_location\"\xd8\x01\n\x0cLoggerConfig\x12,\n\rminimum_level\x18\x01 \x01(\x0e\x32\x15.cmvr.config.LogLevel\x12%\n\x06routes\x18\x02 \x03(\x0b\x32\x15.cmvr.config.LogRoute\x12\x11\n\tdirectory\x18\x03 \x01(\t\x12\x18\n\x10max_file_size_mb\x18\x04 \x01(\x05\x12\x1e\n\x16\x66lush_interval_seconds\x18\x05 \x01(\x05\x12&\n\x06\x66ormat\x18\x06 \x01(\x0b\x32\x16.cmvr.config.LogFormat\"=\n\x10LoggerRootConfig\x12)\n\x06logger\x18\x01 \x01(\x0b\x32\x19.cmvr.config.LoggerConfig*\x8f\x01\n\x08LogLevel\x12\x19\n\x15LOG_LEVEL_UNSPECIFIED\x10\x00\x12\x13\n\x0fLOG_LEVEL_DEBUG\x10\x01\x12\x12\n\x0eLOG_LEVEL_INFO\x10\x02\x12\x15\n\x11LOG_LEVEL_WARNING\x10\x03\x12\x13\n\x0fLOG_LEVEL_ERROR\x10\x04\x12\x13\n\x0fLOG_LEVEL_FATAL\x10\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.logger_config.logger_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_LOGLEVEL']._serialized_start=627 + _globals['_LOGLEVEL']._serialized_end=770 + _globals['_LOGROUTE']._serialized_start=62 + _globals['_LOGROUTE']._serialized_end=142 + _globals['_LOGFORMAT']._serialized_start=145 + _globals['_LOGFORMAT']._serialized_end=342 + _globals['_LOGGERCONFIG']._serialized_start=345 + _globals['_LOGGERCONFIG']._serialized_end=561 + _globals['_LOGGERROOTCONFIG']._serialized_start=563 + _globals['_LOGGERROOTCONFIG']._serialized_end=624 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/logger_config/logger_config_pb2.pyi b/src/cmvr/config/logger_config/logger_config_pb2.pyi new file mode 100644 index 0000000..335fd91 --- /dev/null +++ b/src/cmvr/config/logger_config/logger_config_pb2.pyi @@ -0,0 +1,67 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class LogLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + LOG_LEVEL_UNSPECIFIED: _ClassVar[LogLevel] + LOG_LEVEL_DEBUG: _ClassVar[LogLevel] + LOG_LEVEL_INFO: _ClassVar[LogLevel] + LOG_LEVEL_WARNING: _ClassVar[LogLevel] + LOG_LEVEL_ERROR: _ClassVar[LogLevel] + LOG_LEVEL_FATAL: _ClassVar[LogLevel] +LOG_LEVEL_UNSPECIFIED: LogLevel +LOG_LEVEL_DEBUG: LogLevel +LOG_LEVEL_INFO: LogLevel +LOG_LEVEL_WARNING: LogLevel +LOG_LEVEL_ERROR: LogLevel +LOG_LEVEL_FATAL: LogLevel + +class LogRoute(_message.Message): + __slots__ = ("level", "terminal", "file") + LEVEL_FIELD_NUMBER: _ClassVar[int] + TERMINAL_FIELD_NUMBER: _ClassVar[int] + FILE_FIELD_NUMBER: _ClassVar[int] + level: LogLevel + terminal: bool + file: bool + def __init__(self, level: _Optional[_Union[LogLevel, str]] = ..., terminal: _Optional[bool] = ..., file: _Optional[bool] = ...) -> None: ... + +class LogFormat(_message.Message): + __slots__ = ("show_time", "show_level", "show_thread_id", "show_source_location") + SHOW_TIME_FIELD_NUMBER: _ClassVar[int] + SHOW_LEVEL_FIELD_NUMBER: _ClassVar[int] + SHOW_THREAD_ID_FIELD_NUMBER: _ClassVar[int] + SHOW_SOURCE_LOCATION_FIELD_NUMBER: _ClassVar[int] + show_time: bool + show_level: bool + show_thread_id: bool + show_source_location: bool + def __init__(self, show_time: _Optional[bool] = ..., show_level: _Optional[bool] = ..., show_thread_id: _Optional[bool] = ..., show_source_location: _Optional[bool] = ...) -> None: ... + +class LoggerConfig(_message.Message): + __slots__ = ("minimum_level", "routes", "directory", "max_file_size_mb", "flush_interval_seconds", "format") + MINIMUM_LEVEL_FIELD_NUMBER: _ClassVar[int] + ROUTES_FIELD_NUMBER: _ClassVar[int] + DIRECTORY_FIELD_NUMBER: _ClassVar[int] + MAX_FILE_SIZE_MB_FIELD_NUMBER: _ClassVar[int] + FLUSH_INTERVAL_SECONDS_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + minimum_level: LogLevel + routes: _containers.RepeatedCompositeFieldContainer[LogRoute] + directory: str + max_file_size_mb: int + flush_interval_seconds: int + format: LogFormat + def __init__(self, minimum_level: _Optional[_Union[LogLevel, str]] = ..., routes: _Optional[_Iterable[_Union[LogRoute, _Mapping]]] = ..., directory: _Optional[str] = ..., max_file_size_mb: _Optional[int] = ..., flush_interval_seconds: _Optional[int] = ..., format: _Optional[_Union[LogFormat, _Mapping]] = ...) -> None: ... + +class LoggerRootConfig(_message.Message): + __slots__ = ("logger",) + LOGGER_FIELD_NUMBER: _ClassVar[int] + logger: LoggerConfig + def __init__(self, logger: _Optional[_Union[LoggerConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/logger_config/logger_config_pb2_grpc.py b/src/cmvr/config/logger_config/logger_config_pb2_grpc.py new file mode 100644 index 0000000..759e3e1 --- /dev/null +++ b/src/cmvr/config/logger_config/logger_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/logger_config/logger_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-310.pyc b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..205054f Binary files /dev/null and b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-312.pyc b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..2275338 Binary files /dev/null and b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..08e25b1 Binary files /dev/null and b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..36f1b9d Binary files /dev/null and b/src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/microphone_config/microphone_config_pb2.py b/src/cmvr/config/microphone_config/microphone_config_pb2.py new file mode 100644 index 0000000..70b6bc2 --- /dev/null +++ b/src/cmvr/config/microphone_config/microphone_config_pb2.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/microphone_config/microphone_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/microphone_config/microphone_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n5cmvr/config/microphone_config/microphone_config.proto\x12\x0b\x63mvr.config\"v\n\x16\x46\x46MpegMicroPhoneConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08\x63hannels\x18\x02 \x01(\x05\x12\x12\n\nsampleRate\x18\x03 \x01(\x05\x12\x0e\n\x06volume\x18\x04 \x01(\x05\x12\x14\n\x0cinput_device\x18\x06 \x01(\tJ\x04\x08\x05\x10\x06\"l\n\x16MicrophoneDeviceConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x35\n\x06\x66\x66mpeg\x18\n \x01(\x0b\x32#.cmvr.config.FFMpegMicroPhoneConfigH\x00\x42\t\n\x07\x62\x61\x63kendJ\x04\x08\x02\x10\x03\"L\n\x10MicroPhoneConfig\x12\x38\n\x0bmicrophones\x18\x01 \x03(\x0b\x32#.cmvr.config.MicrophoneDeviceConfig\"I\n\x14MicroPhoneRootConfig\x12\x31\n\nmicrophone\x18\x01 \x01(\x0b\x32\x1d.cmvr.config.MicroPhoneConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.microphone_config.microphone_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_FFMPEGMICROPHONECONFIG']._serialized_start=70 + _globals['_FFMPEGMICROPHONECONFIG']._serialized_end=188 + _globals['_MICROPHONEDEVICECONFIG']._serialized_start=190 + _globals['_MICROPHONEDEVICECONFIG']._serialized_end=298 + _globals['_MICROPHONECONFIG']._serialized_start=300 + _globals['_MICROPHONECONFIG']._serialized_end=376 + _globals['_MICROPHONEROOTCONFIG']._serialized_start=378 + _globals['_MICROPHONEROOTCONFIG']._serialized_end=451 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/microphone_config/microphone_config_pb2.pyi b/src/cmvr/config/microphone_config/microphone_config_pb2.pyi new file mode 100644 index 0000000..a837c3c --- /dev/null +++ b/src/cmvr/config/microphone_config/microphone_config_pb2.pyi @@ -0,0 +1,41 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class FFMpegMicroPhoneConfig(_message.Message): + __slots__ = ("id", "channels", "sampleRate", "volume", "input_device") + ID_FIELD_NUMBER: _ClassVar[int] + CHANNELS_FIELD_NUMBER: _ClassVar[int] + SAMPLERATE_FIELD_NUMBER: _ClassVar[int] + VOLUME_FIELD_NUMBER: _ClassVar[int] + INPUT_DEVICE_FIELD_NUMBER: _ClassVar[int] + id: str + channels: int + sampleRate: int + volume: int + input_device: str + def __init__(self, id: _Optional[str] = ..., channels: _Optional[int] = ..., sampleRate: _Optional[int] = ..., volume: _Optional[int] = ..., input_device: _Optional[str] = ...) -> None: ... + +class MicrophoneDeviceConfig(_message.Message): + __slots__ = ("id", "ffmpeg") + ID_FIELD_NUMBER: _ClassVar[int] + FFMPEG_FIELD_NUMBER: _ClassVar[int] + id: str + ffmpeg: FFMpegMicroPhoneConfig + def __init__(self, id: _Optional[str] = ..., ffmpeg: _Optional[_Union[FFMpegMicroPhoneConfig, _Mapping]] = ...) -> None: ... + +class MicroPhoneConfig(_message.Message): + __slots__ = ("microphones",) + MICROPHONES_FIELD_NUMBER: _ClassVar[int] + microphones: _containers.RepeatedCompositeFieldContainer[MicrophoneDeviceConfig] + def __init__(self, microphones: _Optional[_Iterable[_Union[MicrophoneDeviceConfig, _Mapping]]] = ...) -> None: ... + +class MicroPhoneRootConfig(_message.Message): + __slots__ = ("microphone",) + MICROPHONE_FIELD_NUMBER: _ClassVar[int] + microphone: MicroPhoneConfig + def __init__(self, microphone: _Optional[_Union[MicroPhoneConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/microphone_config/microphone_config_pb2_grpc.py b/src/cmvr/config/microphone_config/microphone_config_pb2_grpc.py new file mode 100644 index 0000000..dba125a --- /dev/null +++ b/src/cmvr/config/microphone_config/microphone_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/microphone_config/microphone_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-310.pyc b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..d0da46b Binary files /dev/null and b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-312.pyc b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..023b358 Binary files /dev/null and b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..0c008cb Binary files /dev/null and b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..9ca1b4f Binary files /dev/null and b/src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/motor_config/motor_config_pb2.py b/src/cmvr/config/motor_config/motor_config_pb2.py new file mode 100644 index 0000000..30a5262 --- /dev/null +++ b/src/cmvr/config/motor_config/motor_config_pb2.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/motor_config/motor_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/motor_config/motor_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.config import joint_limits_config_pb2 as cmvr_dot_config_dot_joint__limits__config__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+cmvr/config/motor_config/motor_config.proto\x12\x0b\x63mvr.config\x1a%cmvr/config/joint_limits_config.proto\"j\n\x0eTi5MotorConfig\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x12\n\njoint_name\x18\x02 \x01(\t\x12\x12\n\nlimit_q_lb\x18\x03 \x01(\x01\x12\x12\n\nlimit_q_ub\x18\x04 \x01(\x01\x12\x10\n\x08limit_qd\x18\x05 \x01(\x01\"5\n\x0fSocketCanConfig\x12\x0e\n\x06\x64\x65v_id\x18\x01 \x01(\t\x12\x12\n\nchannel_id\x18\x02 \x01(\x05\"5\n\x0e\x45therCATConfig\x12\x11\n\tmaster_id\x18\x01 \x01(\t\x12\x10\n\x08\x63ycle_us\x18\x02 \x01(\x05\"\xce\x02\n\x10MotorGroupConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\x08\x62us_type\x18\x02 \x01(\x0e\x32\x19.cmvr.config.MotorBusType\x12\x12\n\ntool_frame\x18\x03 \x01(\t\x12+\n\x03\x63\x61n\x18\n \x01(\x0b\x32\x1c.cmvr.config.SocketCanConfigH\x00\x12/\n\x08\x65thercat\x18\x0b \x01(\x0b\x32\x1b.cmvr.config.EtherCATConfigH\x00\x12+\n\x06motors\x18\x14 \x03(\x0b\x32\x1b.cmvr.config.Ti5MotorConfig\x12\x34\n\x0cjoint_limits\x18\x1e \x01(\x0b\x32\x1e.cmvr.config.JointLimitsConfig\x12\x1e\n\x16joint_limits_urdf_path\x18\x1f \x01(\tB\x0c\n\nbus_config\"N\n\x0bMotorConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x33\n\x0cmotor_groups\x18\x02 \x03(\x0b\x32\x1d.cmvr.config.MotorGroupConfig\":\n\x0fMotorRootConfig\x12\'\n\x05motor\x18\x01 \x01(\x0b\x32\x18.cmvr.config.MotorConfig*f\n\x0cMotorBusType\x12\x15\n\x11MOTOR_BUS_UNKNOWN\x10\x00\x12\x11\n\rMOTOR_BUS_CAN\x10\x01\x12\x16\n\x12MOTOR_BUS_ETHERCAT\x10\x02\x12\x14\n\x10MOTOR_BUS_MUJOCO\x10\x03\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.motor_config.motor_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_MOTORBUSTYPE']._serialized_start=794 + _globals['_MOTORBUSTYPE']._serialized_end=896 + _globals['_TI5MOTORCONFIG']._serialized_start=99 + _globals['_TI5MOTORCONFIG']._serialized_end=205 + _globals['_SOCKETCANCONFIG']._serialized_start=207 + _globals['_SOCKETCANCONFIG']._serialized_end=260 + _globals['_ETHERCATCONFIG']._serialized_start=262 + _globals['_ETHERCATCONFIG']._serialized_end=315 + _globals['_MOTORGROUPCONFIG']._serialized_start=318 + _globals['_MOTORGROUPCONFIG']._serialized_end=652 + _globals['_MOTORCONFIG']._serialized_start=654 + _globals['_MOTORCONFIG']._serialized_end=732 + _globals['_MOTORROOTCONFIG']._serialized_start=734 + _globals['_MOTORROOTCONFIG']._serialized_end=792 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/motor_config/motor_config_pb2.pyi b/src/cmvr/config/motor_config/motor_config_pb2.pyi new file mode 100644 index 0000000..3743b31 --- /dev/null +++ b/src/cmvr/config/motor_config/motor_config_pb2.pyi @@ -0,0 +1,84 @@ +from cmvr.config import joint_limits_config_pb2 as _joint_limits_config_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class MotorBusType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + MOTOR_BUS_UNKNOWN: _ClassVar[MotorBusType] + MOTOR_BUS_CAN: _ClassVar[MotorBusType] + MOTOR_BUS_ETHERCAT: _ClassVar[MotorBusType] + MOTOR_BUS_MUJOCO: _ClassVar[MotorBusType] +MOTOR_BUS_UNKNOWN: MotorBusType +MOTOR_BUS_CAN: MotorBusType +MOTOR_BUS_ETHERCAT: MotorBusType +MOTOR_BUS_MUJOCO: MotorBusType + +class Ti5MotorConfig(_message.Message): + __slots__ = ("id", "joint_name", "limit_q_lb", "limit_q_ub", "limit_qd") + ID_FIELD_NUMBER: _ClassVar[int] + JOINT_NAME_FIELD_NUMBER: _ClassVar[int] + LIMIT_Q_LB_FIELD_NUMBER: _ClassVar[int] + LIMIT_Q_UB_FIELD_NUMBER: _ClassVar[int] + LIMIT_QD_FIELD_NUMBER: _ClassVar[int] + id: int + joint_name: str + limit_q_lb: float + limit_q_ub: float + limit_qd: float + def __init__(self, id: _Optional[int] = ..., joint_name: _Optional[str] = ..., limit_q_lb: _Optional[float] = ..., limit_q_ub: _Optional[float] = ..., limit_qd: _Optional[float] = ...) -> None: ... + +class SocketCanConfig(_message.Message): + __slots__ = ("dev_id", "channel_id") + DEV_ID_FIELD_NUMBER: _ClassVar[int] + CHANNEL_ID_FIELD_NUMBER: _ClassVar[int] + dev_id: str + channel_id: int + def __init__(self, dev_id: _Optional[str] = ..., channel_id: _Optional[int] = ...) -> None: ... + +class EtherCATConfig(_message.Message): + __slots__ = ("master_id", "cycle_us") + MASTER_ID_FIELD_NUMBER: _ClassVar[int] + CYCLE_US_FIELD_NUMBER: _ClassVar[int] + master_id: str + cycle_us: int + def __init__(self, master_id: _Optional[str] = ..., cycle_us: _Optional[int] = ...) -> None: ... + +class MotorGroupConfig(_message.Message): + __slots__ = ("id", "bus_type", "tool_frame", "can", "ethercat", "motors", "joint_limits", "joint_limits_urdf_path") + ID_FIELD_NUMBER: _ClassVar[int] + BUS_TYPE_FIELD_NUMBER: _ClassVar[int] + TOOL_FRAME_FIELD_NUMBER: _ClassVar[int] + CAN_FIELD_NUMBER: _ClassVar[int] + ETHERCAT_FIELD_NUMBER: _ClassVar[int] + MOTORS_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMITS_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMITS_URDF_PATH_FIELD_NUMBER: _ClassVar[int] + id: str + bus_type: MotorBusType + tool_frame: str + can: SocketCanConfig + ethercat: EtherCATConfig + motors: _containers.RepeatedCompositeFieldContainer[Ti5MotorConfig] + joint_limits: _joint_limits_config_pb2.JointLimitsConfig + joint_limits_urdf_path: str + def __init__(self, id: _Optional[str] = ..., bus_type: _Optional[_Union[MotorBusType, str]] = ..., tool_frame: _Optional[str] = ..., can: _Optional[_Union[SocketCanConfig, _Mapping]] = ..., ethercat: _Optional[_Union[EtherCATConfig, _Mapping]] = ..., motors: _Optional[_Iterable[_Union[Ti5MotorConfig, _Mapping]]] = ..., joint_limits: _Optional[_Union[_joint_limits_config_pb2.JointLimitsConfig, _Mapping]] = ..., joint_limits_urdf_path: _Optional[str] = ...) -> None: ... + +class MotorConfig(_message.Message): + __slots__ = ("id", "motor_groups") + ID_FIELD_NUMBER: _ClassVar[int] + MOTOR_GROUPS_FIELD_NUMBER: _ClassVar[int] + id: str + motor_groups: _containers.RepeatedCompositeFieldContainer[MotorGroupConfig] + def __init__(self, id: _Optional[str] = ..., motor_groups: _Optional[_Iterable[_Union[MotorGroupConfig, _Mapping]]] = ...) -> None: ... + +class MotorRootConfig(_message.Message): + __slots__ = ("motor",) + MOTOR_FIELD_NUMBER: _ClassVar[int] + motor: MotorConfig + def __init__(self, motor: _Optional[_Union[MotorConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/motor_config/motor_config_pb2_grpc.py b/src/cmvr/config/motor_config/motor_config_pb2_grpc.py new file mode 100644 index 0000000..f4bdce8 --- /dev/null +++ b/src/cmvr/config/motor_config/motor_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/motor_config/motor_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/pinocchio_dls_ik_config_pb2.py b/src/cmvr/config/pinocchio_dls_ik_config_pb2.py new file mode 100644 index 0000000..3b5df7f --- /dev/null +++ b/src/cmvr/config/pinocchio_dls_ik_config_pb2.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/pinocchio_dls_ik_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/pinocchio_dls_ik_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.config import joint_limits_config_pb2 as cmvr_dot_config_dot_joint__limits__config__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)cmvr/config/pinocchio_dls_ik_config.proto\x12\x0b\x63mvr.config\x1a%cmvr/config/joint_limits_config.proto\"\xb8\x02\n\x14PinocchioDlsIKConfig\x12\x11\n\turdf_path\x18\x01 \x01(\t\x12\x17\n\x0f\x62\x61se_frame_name\x18\x02 \x01(\t\x12\x19\n\x11\x66lange_frame_name\x18\x03 \x01(\t\x12\x16\n\x0etcp_frame_name\x18\x04 \x01(\t\x12\x11\n\tmax_iters\x18\x05 \x01(\x05\x12\x0f\n\x07pos_eps\x18\x06 \x01(\x01\x12\x0f\n\x07rot_eps\x18\x07 \x01(\x01\x12\x0f\n\x07\x64\x61mping\x18\x08 \x01(\x01\x12\x45\n\x15joint_limit_avoidance\x18\t \x01(\x0b\x32&.cmvr.config.JointLimitAvoidanceConfig\x12\x34\n\x0cjoint_limits\x18\n \x01(\x0b\x32\x1e.cmvr.config.JointLimitsConfig\"a\n\x19JointLimitAvoidanceConfig\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\x12\x0c\n\x04gain\x18\x02 \x01(\x01\x12\x14\n\x0cmargin_ratio\x18\x03 \x01(\x01\x12\x10\n\x08max_push\x18\x04 \x01(\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.pinocchio_dls_ik_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_PINOCCHIODLSIKCONFIG']._serialized_start=98 + _globals['_PINOCCHIODLSIKCONFIG']._serialized_end=410 + _globals['_JOINTLIMITAVOIDANCECONFIG']._serialized_start=412 + _globals['_JOINTLIMITAVOIDANCECONFIG']._serialized_end=509 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/pinocchio_dls_ik_config_pb2.pyi b/src/cmvr/config/pinocchio_dls_ik_config_pb2.pyi new file mode 100644 index 0000000..ade3aba --- /dev/null +++ b/src/cmvr/config/pinocchio_dls_ik_config_pb2.pyi @@ -0,0 +1,43 @@ +from cmvr.config import joint_limits_config_pb2 as _joint_limits_config_pb2 +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class PinocchioDlsIKConfig(_message.Message): + __slots__ = ("urdf_path", "base_frame_name", "flange_frame_name", "tcp_frame_name", "max_iters", "pos_eps", "rot_eps", "damping", "joint_limit_avoidance", "joint_limits") + URDF_PATH_FIELD_NUMBER: _ClassVar[int] + BASE_FRAME_NAME_FIELD_NUMBER: _ClassVar[int] + FLANGE_FRAME_NAME_FIELD_NUMBER: _ClassVar[int] + TCP_FRAME_NAME_FIELD_NUMBER: _ClassVar[int] + MAX_ITERS_FIELD_NUMBER: _ClassVar[int] + POS_EPS_FIELD_NUMBER: _ClassVar[int] + ROT_EPS_FIELD_NUMBER: _ClassVar[int] + DAMPING_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMIT_AVOIDANCE_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMITS_FIELD_NUMBER: _ClassVar[int] + urdf_path: str + base_frame_name: str + flange_frame_name: str + tcp_frame_name: str + max_iters: int + pos_eps: float + rot_eps: float + damping: float + joint_limit_avoidance: JointLimitAvoidanceConfig + joint_limits: _joint_limits_config_pb2.JointLimitsConfig + def __init__(self, urdf_path: _Optional[str] = ..., base_frame_name: _Optional[str] = ..., flange_frame_name: _Optional[str] = ..., tcp_frame_name: _Optional[str] = ..., max_iters: _Optional[int] = ..., pos_eps: _Optional[float] = ..., rot_eps: _Optional[float] = ..., damping: _Optional[float] = ..., joint_limit_avoidance: _Optional[_Union[JointLimitAvoidanceConfig, _Mapping]] = ..., joint_limits: _Optional[_Union[_joint_limits_config_pb2.JointLimitsConfig, _Mapping]] = ...) -> None: ... + +class JointLimitAvoidanceConfig(_message.Message): + __slots__ = ("enable", "gain", "margin_ratio", "max_push") + ENABLE_FIELD_NUMBER: _ClassVar[int] + GAIN_FIELD_NUMBER: _ClassVar[int] + MARGIN_RATIO_FIELD_NUMBER: _ClassVar[int] + MAX_PUSH_FIELD_NUMBER: _ClassVar[int] + enable: bool + gain: float + margin_ratio: float + max_push: float + def __init__(self, enable: _Optional[bool] = ..., gain: _Optional[float] = ..., margin_ratio: _Optional[float] = ..., max_push: _Optional[float] = ...) -> None: ... diff --git a/src/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py b/src/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py new file mode 100644 index 0000000..e44a961 --- /dev/null +++ b/src/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/pinocchio_qp_ik_config_pb2.py b/src/cmvr/config/pinocchio_qp_ik_config_pb2.py new file mode 100644 index 0000000..8cf275d --- /dev/null +++ b/src/cmvr/config/pinocchio_qp_ik_config_pb2.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/pinocchio_qp_ik_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/pinocchio_qp_ik_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(cmvr/config/pinocchio_qp_ik_config.proto\x12\x0b\x63mvr.config\"\xcd\x01\n\x13PinocchioQpIKConfig\x12\x11\n\turdf_path\x18\x01 \x01(\t\x12\x17\n\x0f\x62\x61se_frame_name\x18\x02 \x01(\t\x12\x19\n\x11\x66lange_frame_name\x18\x03 \x01(\t\x12\x16\n\x0etcp_frame_name\x18\x04 \x01(\t\x12\x0e\n\x06lambda\x18\x05 \x01(\x01\x12\x10\n\x08w_posrot\x18\x06 \x01(\x01\x12\x11\n\tmax_iters\x18\x07 \x01(\x05\x12\x0b\n\x03tol\x18\x08 \x01(\x01\x12\x15\n\rqp_time_limit\x18\t \x01(\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.pinocchio_qp_ik_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_PINOCCHIOQPIKCONFIG']._serialized_start=58 + _globals['_PINOCCHIOQPIKCONFIG']._serialized_end=263 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/pinocchio_qp_ik_config_pb2.pyi b/src/cmvr/config/pinocchio_qp_ik_config_pb2.pyi new file mode 100644 index 0000000..ba7aeef --- /dev/null +++ b/src/cmvr/config/pinocchio_qp_ik_config_pb2.pyi @@ -0,0 +1,26 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class PinocchioQpIKConfig(_message.Message): + __slots__ = ("urdf_path", "base_frame_name", "flange_frame_name", "tcp_frame_name", "w_posrot", "max_iters", "tol", "qp_time_limit") + URDF_PATH_FIELD_NUMBER: _ClassVar[int] + BASE_FRAME_NAME_FIELD_NUMBER: _ClassVar[int] + FLANGE_FRAME_NAME_FIELD_NUMBER: _ClassVar[int] + TCP_FRAME_NAME_FIELD_NUMBER: _ClassVar[int] + LAMBDA_FIELD_NUMBER: _ClassVar[int] + W_POSROT_FIELD_NUMBER: _ClassVar[int] + MAX_ITERS_FIELD_NUMBER: _ClassVar[int] + TOL_FIELD_NUMBER: _ClassVar[int] + QP_TIME_LIMIT_FIELD_NUMBER: _ClassVar[int] + urdf_path: str + base_frame_name: str + flange_frame_name: str + tcp_frame_name: str + w_posrot: float + max_iters: int + tol: float + qp_time_limit: float + def __init__(self, urdf_path: _Optional[str] = ..., base_frame_name: _Optional[str] = ..., flange_frame_name: _Optional[str] = ..., tcp_frame_name: _Optional[str] = ..., w_posrot: _Optional[float] = ..., max_iters: _Optional[int] = ..., tol: _Optional[float] = ..., qp_time_limit: _Optional[float] = ..., **kwargs) -> None: ... diff --git a/src/cmvr/config/pinocchio_qp_ik_config_pb2_grpc.py b/src/cmvr/config/pinocchio_qp_ik_config_pb2_grpc.py new file mode 100644 index 0000000..29da68f --- /dev/null +++ b/src/cmvr/config/pinocchio_qp_ik_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/pinocchio_qp_ik_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-310.pyc b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..a414907 Binary files /dev/null and b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-312.pyc b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..434bac0 Binary files /dev/null and b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..d25d72e Binary files /dev/null and b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..be1da55 Binary files /dev/null and b/src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/speaker_config/speaker_config_pb2.py b/src/cmvr/config/speaker_config/speaker_config_pb2.py new file mode 100644 index 0000000..966909c --- /dev/null +++ b/src/cmvr/config/speaker_config/speaker_config_pb2.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/speaker_config/speaker_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/speaker_config/speaker_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/speaker_config/speaker_config.proto\x12\x0b\x63mvr.config\"\'\n\x13\x46\x46MpegSpeakerConfig\x12\n\n\x02id\x18\x01 \x01(\tJ\x04\x08\x02\x10\x03\"f\n\x13SpeakerDeviceConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x32\n\x06\x66\x66mpeg\x18\n \x01(\x0b\x32 .cmvr.config.FFMpegSpeakerConfigH\x00\x42\t\n\x07\x62\x61\x63kendJ\x04\x08\x02\x10\x03\"C\n\rSpeakerConfig\x12\x32\n\x08speakers\x18\x01 \x03(\x0b\x32 .cmvr.config.SpeakerDeviceConfig\"@\n\x11SpeakerRootConfig\x12+\n\x07speaker\x18\x01 \x01(\x0b\x32\x1a.cmvr.config.SpeakerConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.speaker_config.speaker_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_FFMPEGSPEAKERCONFIG']._serialized_start=64 + _globals['_FFMPEGSPEAKERCONFIG']._serialized_end=103 + _globals['_SPEAKERDEVICECONFIG']._serialized_start=105 + _globals['_SPEAKERDEVICECONFIG']._serialized_end=207 + _globals['_SPEAKERCONFIG']._serialized_start=209 + _globals['_SPEAKERCONFIG']._serialized_end=276 + _globals['_SPEAKERROOTCONFIG']._serialized_start=278 + _globals['_SPEAKERROOTCONFIG']._serialized_end=342 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/speaker_config/speaker_config_pb2.pyi b/src/cmvr/config/speaker_config/speaker_config_pb2.pyi new file mode 100644 index 0000000..833d735 --- /dev/null +++ b/src/cmvr/config/speaker_config/speaker_config_pb2.pyi @@ -0,0 +1,33 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class FFMpegSpeakerConfig(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class SpeakerDeviceConfig(_message.Message): + __slots__ = ("id", "ffmpeg") + ID_FIELD_NUMBER: _ClassVar[int] + FFMPEG_FIELD_NUMBER: _ClassVar[int] + id: str + ffmpeg: FFMpegSpeakerConfig + def __init__(self, id: _Optional[str] = ..., ffmpeg: _Optional[_Union[FFMpegSpeakerConfig, _Mapping]] = ...) -> None: ... + +class SpeakerConfig(_message.Message): + __slots__ = ("speakers",) + SPEAKERS_FIELD_NUMBER: _ClassVar[int] + speakers: _containers.RepeatedCompositeFieldContainer[SpeakerDeviceConfig] + def __init__(self, speakers: _Optional[_Iterable[_Union[SpeakerDeviceConfig, _Mapping]]] = ...) -> None: ... + +class SpeakerRootConfig(_message.Message): + __slots__ = ("speaker",) + SPEAKER_FIELD_NUMBER: _ClassVar[int] + speaker: SpeakerConfig + def __init__(self, speaker: _Optional[_Union[SpeakerConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/speaker_config/speaker_config_pb2_grpc.py b/src/cmvr/config/speaker_config/speaker_config_pb2_grpc.py new file mode 100644 index 0000000..cb837ad --- /dev/null +++ b/src/cmvr/config/speaker_config/speaker_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/speaker_config/speaker_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/srs_ik_config_pb2.py b/src/cmvr/config/srs_ik_config_pb2.py new file mode 100644 index 0000000..07dc7c9 --- /dev/null +++ b/src/cmvr/config/srs_ik_config_pb2.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/srs_ik_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/srs_ik_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x63mvr/config/srs_ik_config.proto\x12\x0b\x63mvr.config\"\xb5\x01\n\x0bSrsIKConfig\x12\x13\n\x0binitial_psi\x18\x01 \x01(\x01\x12\x17\n\x0fshoulder_config\x18\x02 \x01(\x05\x12\x14\n\x0c\x65lbow_config\x18\x03 \x01(\x05\x12\x14\n\x0cwrist_config\x18\x04 \x01(\x05\x12\x14\n\x0clink_lengths\x18\n \x03(\x01\x12\x1a\n\x12joint_lower_limits\x18\x0b \x03(\x01\x12\x1a\n\x12joint_upper_limits\x18\x0c \x03(\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.srs_ik_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_SRSIKCONFIG']._serialized_start=49 + _globals['_SRSIKCONFIG']._serialized_end=230 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/srs_ik_config_pb2.pyi b/src/cmvr/config/srs_ik_config_pb2.pyi new file mode 100644 index 0000000..746b87c --- /dev/null +++ b/src/cmvr/config/srs_ik_config_pb2.pyi @@ -0,0 +1,25 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable +from typing import ClassVar as _ClassVar, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class SrsIKConfig(_message.Message): + __slots__ = ("initial_psi", "shoulder_config", "elbow_config", "wrist_config", "link_lengths", "joint_lower_limits", "joint_upper_limits") + INITIAL_PSI_FIELD_NUMBER: _ClassVar[int] + SHOULDER_CONFIG_FIELD_NUMBER: _ClassVar[int] + ELBOW_CONFIG_FIELD_NUMBER: _ClassVar[int] + WRIST_CONFIG_FIELD_NUMBER: _ClassVar[int] + LINK_LENGTHS_FIELD_NUMBER: _ClassVar[int] + JOINT_LOWER_LIMITS_FIELD_NUMBER: _ClassVar[int] + JOINT_UPPER_LIMITS_FIELD_NUMBER: _ClassVar[int] + initial_psi: float + shoulder_config: int + elbow_config: int + wrist_config: int + link_lengths: _containers.RepeatedScalarFieldContainer[float] + joint_lower_limits: _containers.RepeatedScalarFieldContainer[float] + joint_upper_limits: _containers.RepeatedScalarFieldContainer[float] + def __init__(self, initial_psi: _Optional[float] = ..., shoulder_config: _Optional[int] = ..., elbow_config: _Optional[int] = ..., wrist_config: _Optional[int] = ..., link_lengths: _Optional[_Iterable[float]] = ..., joint_lower_limits: _Optional[_Iterable[float]] = ..., joint_upper_limits: _Optional[_Iterable[float]] = ...) -> None: ... diff --git a/src/cmvr/config/srs_ik_config_pb2_grpc.py b/src/cmvr/config/srs_ik_config_pb2_grpc.py new file mode 100644 index 0000000..44032ce --- /dev/null +++ b/src/cmvr/config/srs_ik_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/srs_ik_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-310.pyc b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..156b4d4 Binary files /dev/null and b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-312.pyc b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..e513b8f Binary files /dev/null and b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..6e5d2da Binary files /dev/null and b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..bc596b9 Binary files /dev/null and b/src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/task_manager_config/task_manager_config_pb2.py b/src/cmvr/config/task_manager_config/task_manager_config_pb2.py new file mode 100644 index 0000000..a4faaaf --- /dev/null +++ b/src/cmvr/config/task_manager_config/task_manager_config_pb2.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/task_manager_config/task_manager_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/task_manager_config/task_manager_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n9cmvr/config/task_manager_config/task_manager_config.proto\x12\x0b\x63mvr.config\"\xb3\x03\n\x0fTaskConfigEntry\x12\n\n\x02id\x18\x01 \x01(\t\x12\x33\n\x04type\x18\x02 \x01(\x0e\x32%.cmvr.config.TaskConfigEntry.TaskType\x12\x13\n\x0b\x63onfig_file\x18\x03 \x01(\t\x12\x0e\n\x06\x65nable\x18\x04 \x01(\x08\x12:\n\x08run_mode\x18\x05 \x01(\x0e\x32(.cmvr.config.TaskConfigEntry.TaskRunMode\x12\x18\n\x10\x63ontrol_period_s\x18\x06 \x01(\x01\"u\n\x08TaskType\x12\x15\n\x11TASK_TYPE_UNKNOWN\x10\x00\x12\x1a\n\x16TASK_TYPE_TOUCH_SCREEN\x10\x01\x12\x19\n\x15TASK_TYPE_GRPC_SERVER\x10\x03\"\x04\x08\x02\x10\x02*\x15TASK_TYPE_ARM_CONTROL\"m\n\x0bTaskRunMode\x12\x19\n\x15TASK_RUN_MODE_UNKNOWN\x10\x00\x12\x1f\n\x1bTASK_RUN_MODE_PERIODIC_STEP\x10\x01\x12\"\n\x1eTASK_RUN_MODE_BLOCKING_SERVICE\x10\x02\"@\n\x11TaskManagerConfig\x12+\n\x05tasks\x18\x01 \x03(\x0b\x32\x1c.cmvr.config.TaskConfigEntry\"M\n\x15TaskManagerRootConfig\x12\x34\n\x0ctask_manager\x18\x01 \x01(\x0b\x32\x1e.cmvr.config.TaskManagerConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.task_manager_config.task_manager_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TASKCONFIGENTRY']._serialized_start=75 + _globals['_TASKCONFIGENTRY']._serialized_end=510 + _globals['_TASKCONFIGENTRY_TASKTYPE']._serialized_start=282 + _globals['_TASKCONFIGENTRY_TASKTYPE']._serialized_end=399 + _globals['_TASKCONFIGENTRY_TASKRUNMODE']._serialized_start=401 + _globals['_TASKCONFIGENTRY_TASKRUNMODE']._serialized_end=510 + _globals['_TASKMANAGERCONFIG']._serialized_start=512 + _globals['_TASKMANAGERCONFIG']._serialized_end=576 + _globals['_TASKMANAGERROOTCONFIG']._serialized_start=578 + _globals['_TASKMANAGERROOTCONFIG']._serialized_end=655 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/task_manager_config/task_manager_config_pb2.pyi b/src/cmvr/config/task_manager_config/task_manager_config_pb2.pyi new file mode 100644 index 0000000..ecdd172 --- /dev/null +++ b/src/cmvr/config/task_manager_config/task_manager_config_pb2.pyi @@ -0,0 +1,52 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class TaskConfigEntry(_message.Message): + __slots__ = ("id", "type", "config_file", "enable", "run_mode", "control_period_s") + class TaskType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TASK_TYPE_UNKNOWN: _ClassVar[TaskConfigEntry.TaskType] + TASK_TYPE_TOUCH_SCREEN: _ClassVar[TaskConfigEntry.TaskType] + TASK_TYPE_GRPC_SERVER: _ClassVar[TaskConfigEntry.TaskType] + TASK_TYPE_UNKNOWN: TaskConfigEntry.TaskType + TASK_TYPE_TOUCH_SCREEN: TaskConfigEntry.TaskType + TASK_TYPE_GRPC_SERVER: TaskConfigEntry.TaskType + class TaskRunMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TASK_RUN_MODE_UNKNOWN: _ClassVar[TaskConfigEntry.TaskRunMode] + TASK_RUN_MODE_PERIODIC_STEP: _ClassVar[TaskConfigEntry.TaskRunMode] + TASK_RUN_MODE_BLOCKING_SERVICE: _ClassVar[TaskConfigEntry.TaskRunMode] + TASK_RUN_MODE_UNKNOWN: TaskConfigEntry.TaskRunMode + TASK_RUN_MODE_PERIODIC_STEP: TaskConfigEntry.TaskRunMode + TASK_RUN_MODE_BLOCKING_SERVICE: TaskConfigEntry.TaskRunMode + ID_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + CONFIG_FILE_FIELD_NUMBER: _ClassVar[int] + ENABLE_FIELD_NUMBER: _ClassVar[int] + RUN_MODE_FIELD_NUMBER: _ClassVar[int] + CONTROL_PERIOD_S_FIELD_NUMBER: _ClassVar[int] + id: str + type: TaskConfigEntry.TaskType + config_file: str + enable: bool + run_mode: TaskConfigEntry.TaskRunMode + control_period_s: float + def __init__(self, id: _Optional[str] = ..., type: _Optional[_Union[TaskConfigEntry.TaskType, str]] = ..., config_file: _Optional[str] = ..., enable: _Optional[bool] = ..., run_mode: _Optional[_Union[TaskConfigEntry.TaskRunMode, str]] = ..., control_period_s: _Optional[float] = ...) -> None: ... + +class TaskManagerConfig(_message.Message): + __slots__ = ("tasks",) + TASKS_FIELD_NUMBER: _ClassVar[int] + tasks: _containers.RepeatedCompositeFieldContainer[TaskConfigEntry] + def __init__(self, tasks: _Optional[_Iterable[_Union[TaskConfigEntry, _Mapping]]] = ...) -> None: ... + +class TaskManagerRootConfig(_message.Message): + __slots__ = ("task_manager",) + TASK_MANAGER_FIELD_NUMBER: _ClassVar[int] + task_manager: TaskManagerConfig + def __init__(self, task_manager: _Optional[_Union[TaskManagerConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/task_manager_config/task_manager_config_pb2_grpc.py b/src/cmvr/config/task_manager_config/task_manager_config_pb2_grpc.py new file mode 100644 index 0000000..09ebfa7 --- /dev/null +++ b/src/cmvr/config/task_manager_config/task_manager_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/task_manager_config/task_manager_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/touch_screen_algorithm_config_pb2.py b/src/cmvr/config/touch_screen_algorithm_config_pb2.py new file mode 100644 index 0000000..d263353 --- /dev/null +++ b/src/cmvr/config/touch_screen_algorithm_config_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/touch_screen_algorithm_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/touch_screen_algorithm_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.common import geometry_pb2 as cmvr_dot_common_dot_geometry__pb2 +from cmvr.config import touch_screen_common_config_pb2 as cmvr_dot_config_dot_touch__screen__common__config__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/touch_screen_algorithm_config.proto\x12\x0b\x63mvr.config\x1a\x1a\x63mvr/common/geometry.proto\x1a,cmvr/config/touch_screen_common_config.proto\"\xf9\x01\n\x19TouchScreenApriltagConfig\x12\x17\n\ntag_size_m\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12>\n\x0c\x64\x65pth_policy\x18\x02 \x01(\x0e\x32#.cmvr.config.TouchScreenDepthPolicyH\x01\x88\x01\x01\x12K\n\x13target_point_method\x18\x03 \x01(\x0e\x32).cmvr.config.TouchScreenTargetPointMethodH\x02\x88\x01\x01\x42\r\n\x0b_tag_size_mB\x0f\n\r_depth_policyB\x16\n\x14_target_point_method\"\xcc\x05\n\x15TouchScreenIbvsConfig\x12\x18\n\x0b\x63\x61mera_link\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06lambda\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x0f\n\x02mu\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x15\n\x08qdot_max\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12 \n\x05vmax6\x18\x05 \x01(\x0b\x32\x11.cmvr.common.Vec6\x12 \n\x05\x61max6\x18\x06 \x01(\x0b\x32\x11.cmvr.common.Vec6\x12\x1f\n\x12twist_filter_alpha\x18\x07 \x01(\x01H\x04\x88\x01\x01\x12)\n\x1c\x65nable_joint_limit_avoidance\x18\x08 \x01(\x08H\x05\x88\x01\x01\x12\'\n\x1ajoint_limit_avoidance_gain\x18\t \x01(\x01H\x06\x88\x01\x01\x12/\n\"joint_limit_avoidance_margin_ratio\x18\n \x01(\x01H\x07\x88\x01\x01\x12+\n\x1ejoint_limit_avoidance_max_push\x18\x0b \x01(\x01H\x08\x88\x01\x01\x12+\n\x10r_camera_to_visp\x18\x0c \x01(\x0b\x32\x11.cmvr.common.Mat3\x12+\n\x10r_camera_to_urdf\x18\r \x01(\x0b\x32\x11.cmvr.common.Mat3\x12\x1b\n\x13\x63ontrol_joint_names\x18\x0e \x03(\tB\x0e\n\x0c_camera_linkB\t\n\x07_lambdaB\x05\n\x03_muB\x0b\n\t_qdot_maxB\x15\n\x13_twist_filter_alphaB\x1f\n\x1d_enable_joint_limit_avoidanceB\x1d\n\x1b_joint_limit_avoidance_gainB%\n#_joint_limit_avoidance_margin_ratioB!\n\x1f_joint_limit_avoidance_max_pushb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.touch_screen_algorithm_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TOUCHSCREENAPRILTAGCONFIG']._serialized_start=139 + _globals['_TOUCHSCREENAPRILTAGCONFIG']._serialized_end=388 + _globals['_TOUCHSCREENIBVSCONFIG']._serialized_start=391 + _globals['_TOUCHSCREENIBVSCONFIG']._serialized_end=1107 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/touch_screen_algorithm_config_pb2.pyi b/src/cmvr/config/touch_screen_algorithm_config_pb2.pyi new file mode 100644 index 0000000..3d0c683 --- /dev/null +++ b/src/cmvr/config/touch_screen_algorithm_config_pb2.pyi @@ -0,0 +1,50 @@ +from cmvr.common import geometry_pb2 as _geometry_pb2 +from cmvr.config import touch_screen_common_config_pb2 as _touch_screen_common_config_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class TouchScreenApriltagConfig(_message.Message): + __slots__ = ("tag_size_m", "depth_policy", "target_point_method") + TAG_SIZE_M_FIELD_NUMBER: _ClassVar[int] + DEPTH_POLICY_FIELD_NUMBER: _ClassVar[int] + TARGET_POINT_METHOD_FIELD_NUMBER: _ClassVar[int] + tag_size_m: float + depth_policy: _touch_screen_common_config_pb2.TouchScreenDepthPolicy + target_point_method: _touch_screen_common_config_pb2.TouchScreenTargetPointMethod + def __init__(self, tag_size_m: _Optional[float] = ..., depth_policy: _Optional[_Union[_touch_screen_common_config_pb2.TouchScreenDepthPolicy, str]] = ..., target_point_method: _Optional[_Union[_touch_screen_common_config_pb2.TouchScreenTargetPointMethod, str]] = ...) -> None: ... + +class TouchScreenIbvsConfig(_message.Message): + __slots__ = ("camera_link", "mu", "qdot_max", "vmax6", "amax6", "twist_filter_alpha", "enable_joint_limit_avoidance", "joint_limit_avoidance_gain", "joint_limit_avoidance_margin_ratio", "joint_limit_avoidance_max_push", "r_camera_to_visp", "r_camera_to_urdf", "control_joint_names") + CAMERA_LINK_FIELD_NUMBER: _ClassVar[int] + LAMBDA_FIELD_NUMBER: _ClassVar[int] + MU_FIELD_NUMBER: _ClassVar[int] + QDOT_MAX_FIELD_NUMBER: _ClassVar[int] + VMAX6_FIELD_NUMBER: _ClassVar[int] + AMAX6_FIELD_NUMBER: _ClassVar[int] + TWIST_FILTER_ALPHA_FIELD_NUMBER: _ClassVar[int] + ENABLE_JOINT_LIMIT_AVOIDANCE_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMIT_AVOIDANCE_GAIN_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMIT_AVOIDANCE_MARGIN_RATIO_FIELD_NUMBER: _ClassVar[int] + JOINT_LIMIT_AVOIDANCE_MAX_PUSH_FIELD_NUMBER: _ClassVar[int] + R_CAMERA_TO_VISP_FIELD_NUMBER: _ClassVar[int] + R_CAMERA_TO_URDF_FIELD_NUMBER: _ClassVar[int] + CONTROL_JOINT_NAMES_FIELD_NUMBER: _ClassVar[int] + camera_link: str + mu: float + qdot_max: float + vmax6: _geometry_pb2.Vec6 + amax6: _geometry_pb2.Vec6 + twist_filter_alpha: float + enable_joint_limit_avoidance: bool + joint_limit_avoidance_gain: float + joint_limit_avoidance_margin_ratio: float + joint_limit_avoidance_max_push: float + r_camera_to_visp: _geometry_pb2.Mat3 + r_camera_to_urdf: _geometry_pb2.Mat3 + control_joint_names: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, camera_link: _Optional[str] = ..., mu: _Optional[float] = ..., qdot_max: _Optional[float] = ..., vmax6: _Optional[_Union[_geometry_pb2.Vec6, _Mapping]] = ..., amax6: _Optional[_Union[_geometry_pb2.Vec6, _Mapping]] = ..., twist_filter_alpha: _Optional[float] = ..., enable_joint_limit_avoidance: _Optional[bool] = ..., joint_limit_avoidance_gain: _Optional[float] = ..., joint_limit_avoidance_margin_ratio: _Optional[float] = ..., joint_limit_avoidance_max_push: _Optional[float] = ..., r_camera_to_visp: _Optional[_Union[_geometry_pb2.Mat3, _Mapping]] = ..., r_camera_to_urdf: _Optional[_Union[_geometry_pb2.Mat3, _Mapping]] = ..., control_joint_names: _Optional[_Iterable[str]] = ..., **kwargs) -> None: ... diff --git a/src/cmvr/config/touch_screen_algorithm_config_pb2_grpc.py b/src/cmvr/config/touch_screen_algorithm_config_pb2_grpc.py new file mode 100644 index 0000000..139f733 --- /dev/null +++ b/src/cmvr/config/touch_screen_algorithm_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/touch_screen_algorithm_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/touch_screen_common_config_pb2.py b/src/cmvr/config/touch_screen_common_config_pb2.py new file mode 100644 index 0000000..2b9d8e9 --- /dev/null +++ b/src/cmvr/config/touch_screen_common_config_pb2.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/touch_screen_common_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/touch_screen_common_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,cmvr/config/touch_screen_common_config.proto\x12\x0b\x63mvr.config*\x89\x01\n\x16TouchScreenDepthPolicy\x12\"\n\x1eTOUCH_SCREEN_DEPTH_POLICY_NONE\x10\x00\x12$\n TOUCH_SCREEN_DEPTH_POLICY_PREFER\x10\x01\x12%\n!TOUCH_SCREEN_DEPTH_POLICY_REQUIRE\x10\x02*\x80\x01\n\x1cTouchScreenTargetPointMethod\x12.\n*TOUCH_SCREEN_TARGET_POINT_METHOD_TAG_PLANE\x10\x00\x12\x30\n,TOUCH_SCREEN_TARGET_POINT_METHOD_DEPTH_IMAGE\x10\x01*\xcb\x01\n\x15TouchScreenFingerType\x12\"\n\x1eTOUCH_SCREEN_FINGER_TYPE_PINKY\x10\x00\x12!\n\x1dTOUCH_SCREEN_FINGER_TYPE_RING\x10\x01\x12#\n\x1fTOUCH_SCREEN_FINGER_TYPE_MIDDLE\x10\x02\x12\"\n\x1eTOUCH_SCREEN_FINGER_TYPE_INDEX\x10\x03\x12\"\n\x1eTOUCH_SCREEN_FINGER_TYPE_THUMB\x10\x04*\xba\x01\n\x18TouchScreenTactileRegion\x12#\n\x1fTOUCH_SCREEN_TACTILE_REGION_TIP\x10\x00\x12&\n\"TOUCH_SCREEN_TACTILE_REGION_FINGER\x10\x01\x12#\n\x1fTOUCH_SCREEN_TACTILE_REGION_PAD\x10\x02\x12,\n(TOUCH_SCREEN_TACTILE_REGION_THUMB_MIDDLE\x10\x04*r\n\x1bTouchScreenTactileCriterion\x12%\n!TOUCH_SCREEN_TACTILE_CRITERION_FZ\x10\x00\x12,\n(TOUCH_SCREEN_TACTILE_CRITERION_MAGNITUDE\x10\x01*\xa0\x01\n\x14TouchScreenAlignMode\x12-\n)TOUCH_SCREEN_ALIGN_MODE_POSE_AND_POSITION\x10\x00\x12.\n*TOUCH_SCREEN_ALIGN_MODE_RX_RY_AND_POSITION\x10\x01\x12)\n%TOUCH_SCREEN_ALIGN_MODE_POSITION_ONLY\x10\x02\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.touch_screen_common_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TOUCHSCREENDEPTHPOLICY']._serialized_start=62 + _globals['_TOUCHSCREENDEPTHPOLICY']._serialized_end=199 + _globals['_TOUCHSCREENTARGETPOINTMETHOD']._serialized_start=202 + _globals['_TOUCHSCREENTARGETPOINTMETHOD']._serialized_end=330 + _globals['_TOUCHSCREENFINGERTYPE']._serialized_start=333 + _globals['_TOUCHSCREENFINGERTYPE']._serialized_end=536 + _globals['_TOUCHSCREENTACTILEREGION']._serialized_start=539 + _globals['_TOUCHSCREENTACTILEREGION']._serialized_end=725 + _globals['_TOUCHSCREENTACTILECRITERION']._serialized_start=727 + _globals['_TOUCHSCREENTACTILECRITERION']._serialized_end=841 + _globals['_TOUCHSCREENALIGNMODE']._serialized_start=844 + _globals['_TOUCHSCREENALIGNMODE']._serialized_end=1004 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/touch_screen_common_config_pb2.pyi b/src/cmvr/config/touch_screen_common_config_pb2.pyi new file mode 100644 index 0000000..dbbcdb3 --- /dev/null +++ b/src/cmvr/config/touch_screen_common_config_pb2.pyi @@ -0,0 +1,61 @@ +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor + +class TouchScreenDepthPolicy(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TOUCH_SCREEN_DEPTH_POLICY_NONE: _ClassVar[TouchScreenDepthPolicy] + TOUCH_SCREEN_DEPTH_POLICY_PREFER: _ClassVar[TouchScreenDepthPolicy] + TOUCH_SCREEN_DEPTH_POLICY_REQUIRE: _ClassVar[TouchScreenDepthPolicy] + +class TouchScreenTargetPointMethod(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TOUCH_SCREEN_TARGET_POINT_METHOD_TAG_PLANE: _ClassVar[TouchScreenTargetPointMethod] + TOUCH_SCREEN_TARGET_POINT_METHOD_DEPTH_IMAGE: _ClassVar[TouchScreenTargetPointMethod] + +class TouchScreenFingerType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TOUCH_SCREEN_FINGER_TYPE_PINKY: _ClassVar[TouchScreenFingerType] + TOUCH_SCREEN_FINGER_TYPE_RING: _ClassVar[TouchScreenFingerType] + TOUCH_SCREEN_FINGER_TYPE_MIDDLE: _ClassVar[TouchScreenFingerType] + TOUCH_SCREEN_FINGER_TYPE_INDEX: _ClassVar[TouchScreenFingerType] + TOUCH_SCREEN_FINGER_TYPE_THUMB: _ClassVar[TouchScreenFingerType] + +class TouchScreenTactileRegion(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TOUCH_SCREEN_TACTILE_REGION_TIP: _ClassVar[TouchScreenTactileRegion] + TOUCH_SCREEN_TACTILE_REGION_FINGER: _ClassVar[TouchScreenTactileRegion] + TOUCH_SCREEN_TACTILE_REGION_PAD: _ClassVar[TouchScreenTactileRegion] + TOUCH_SCREEN_TACTILE_REGION_THUMB_MIDDLE: _ClassVar[TouchScreenTactileRegion] + +class TouchScreenTactileCriterion(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TOUCH_SCREEN_TACTILE_CRITERION_FZ: _ClassVar[TouchScreenTactileCriterion] + TOUCH_SCREEN_TACTILE_CRITERION_MAGNITUDE: _ClassVar[TouchScreenTactileCriterion] + +class TouchScreenAlignMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TOUCH_SCREEN_ALIGN_MODE_POSE_AND_POSITION: _ClassVar[TouchScreenAlignMode] + TOUCH_SCREEN_ALIGN_MODE_RX_RY_AND_POSITION: _ClassVar[TouchScreenAlignMode] + TOUCH_SCREEN_ALIGN_MODE_POSITION_ONLY: _ClassVar[TouchScreenAlignMode] +TOUCH_SCREEN_DEPTH_POLICY_NONE: TouchScreenDepthPolicy +TOUCH_SCREEN_DEPTH_POLICY_PREFER: TouchScreenDepthPolicy +TOUCH_SCREEN_DEPTH_POLICY_REQUIRE: TouchScreenDepthPolicy +TOUCH_SCREEN_TARGET_POINT_METHOD_TAG_PLANE: TouchScreenTargetPointMethod +TOUCH_SCREEN_TARGET_POINT_METHOD_DEPTH_IMAGE: TouchScreenTargetPointMethod +TOUCH_SCREEN_FINGER_TYPE_PINKY: TouchScreenFingerType +TOUCH_SCREEN_FINGER_TYPE_RING: TouchScreenFingerType +TOUCH_SCREEN_FINGER_TYPE_MIDDLE: TouchScreenFingerType +TOUCH_SCREEN_FINGER_TYPE_INDEX: TouchScreenFingerType +TOUCH_SCREEN_FINGER_TYPE_THUMB: TouchScreenFingerType +TOUCH_SCREEN_TACTILE_REGION_TIP: TouchScreenTactileRegion +TOUCH_SCREEN_TACTILE_REGION_FINGER: TouchScreenTactileRegion +TOUCH_SCREEN_TACTILE_REGION_PAD: TouchScreenTactileRegion +TOUCH_SCREEN_TACTILE_REGION_THUMB_MIDDLE: TouchScreenTactileRegion +TOUCH_SCREEN_TACTILE_CRITERION_FZ: TouchScreenTactileCriterion +TOUCH_SCREEN_TACTILE_CRITERION_MAGNITUDE: TouchScreenTactileCriterion +TOUCH_SCREEN_ALIGN_MODE_POSE_AND_POSITION: TouchScreenAlignMode +TOUCH_SCREEN_ALIGN_MODE_RX_RY_AND_POSITION: TouchScreenAlignMode +TOUCH_SCREEN_ALIGN_MODE_POSITION_ONLY: TouchScreenAlignMode diff --git a/src/cmvr/config/touch_screen_common_config_pb2_grpc.py b/src/cmvr/config/touch_screen_common_config_pb2_grpc.py new file mode 100644 index 0000000..332da69 --- /dev/null +++ b/src/cmvr/config/touch_screen_common_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/touch_screen_common_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-310.pyc b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-310.pyc new file mode 100644 index 0000000..c16fa08 Binary files /dev/null and b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-310.pyc differ diff --git a/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-312.pyc b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-312.pyc new file mode 100644 index 0000000..1b14773 Binary files /dev/null and b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-312.pyc differ diff --git a/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-310.pyc b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..306860f Binary files /dev/null and b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-312.pyc b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..8a1cbf8 Binary files /dev/null and b/src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.py b/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.py new file mode 100644 index 0000000..b2c02fb --- /dev/null +++ b/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/config/touch_screen_task_config/touch_screen_task_config.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/config/touch_screen_task_config/touch_screen_task_config.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.common import geometry_pb2 as cmvr_dot_common_dot_geometry__pb2 +from cmvr.config import touch_screen_common_config_pb2 as cmvr_dot_config_dot_touch__screen__common__config__pb2 +from cmvr.config import touch_screen_algorithm_config_pb2 as cmvr_dot_config_dot_touch__screen__algorithm__config__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\nCcmvr/config/touch_screen_task_config/touch_screen_task_config.proto\x12\x0b\x63mvr.config\x1a\x1a\x63mvr/common/geometry.proto\x1a,cmvr/config/touch_screen_common_config.proto\x1a/cmvr/config/touch_screen_algorithm_config.proto\"]\n\x19TouchScreenInitJointPoint\x12\x17\n\njoint_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x03rad\x18\x02 \x01(\x01H\x01\x88\x01\x01\x42\r\n\x0b_joint_nameB\x06\n\x04_rad\"\x9c\x01\n\x1cTouchScreenTaskDevicesConfig\x12\x13\n\x06\x61rm_id\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x17\n\ndexhand_id\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x16\n\tcamera_id\x18\x03 \x01(\tH\x02\x88\x01\x01\x42\t\n\x07_arm_idB\r\n\x0b_dexhand_idB\x0c\n\n_camera_idJ\x04\x08\x01\x10\x02R\x08robot_id\"\x8e\x02\n#TouchScreenTaskInitializationConfig\x12\x19\n\x0c\x62\x65\x66ore_start\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x19\n\x0c\x61\x66ter_finish\x18\x02 \x01(\x08H\x01\x88\x01\x01\x12?\n\x0fjoint_positions\x18\x03 \x03(\x0b\x32&.cmvr.config.TouchScreenInitJointPoint\x12\x15\n\x08velocity\x18\x04 \x01(\x01H\x02\x88\x01\x01\x12\x19\n\x0c\x61\x63\x63\x65leration\x18\x05 \x01(\x01H\x03\x88\x01\x01\x42\x0f\n\r_before_startB\x0f\n\r_after_finishB\x0b\n\t_velocityB\x0f\n\r_acceleration\"[\n\x1fTouchScreenTaskPerceptionConfig\x12\x38\n\x08\x61priltag\x18\x01 \x01(\x0b\x32&.cmvr.config.TouchScreenApriltagConfig\"\xbc\x01\n TouchScreenAlignmentTargetConfig\x12-\n\x12position_in_camera\x18\x01 \x01(\x0b\x32\x11.cmvr.common.Vec3\x12*\n\x0frotation_vector\x18\x02 \x01(\x0b\x32\x11.cmvr.common.Vec3\x12\x34\n\x04mode\x18\x03 \x01(\x0e\x32!.cmvr.config.TouchScreenAlignModeH\x00\x88\x01\x01\x42\x07\n\x05_mode\"\xdb\x02\n\x1eTouchScreenTaskAlignmentConfig\x12\x30\n\x04ibvs\x18\x02 \x01(\x0b\x32\".cmvr.config.TouchScreenIbvsConfig\x12=\n\x06target\x18\x03 \x01(\x0b\x32-.cmvr.config.TouchScreenAlignmentTargetConfig\x12*\n\x0f\x65rror_threshold\x18\x04 \x01(\x0b\x32\x11.cmvr.common.Vec6\x12\x1a\n\rstable_frames\x18\x05 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\ttimeout_s\x18\x06 \x01(\x01H\x01\x88\x01\x01\x12\x1f\n\x12pause_when_reached\x18\x07 \x01(\x08H\x02\x88\x01\x01\x42\x10\n\x0e_stable_framesB\x0c\n\n_timeout_sB\x15\n\x13_pause_when_reachedJ\x04\x08\x01\x10\x02R\nkinematics\"\xa1\x01\n\x1cTouchScreenTouchSpeedLConfig\x12%\n\ntwist_tool\x18\x01 \x01(\x0b\x32\x11.cmvr.common.Vec6\x12\x19\n\x0c\x61\x63\x63\x65leration\x18\x02 \x01(\x01H\x00\x88\x01\x01\x12\x1b\n\x0emax_distance_m\x18\x03 \x01(\x01H\x01\x88\x01\x01\x42\x0f\n\r_accelerationB\x11\n\x0f_max_distance_m\"\xfb\x01\n\x1bTouchScreenTouchMoveLConfig\x12)\n\x0e\x64irection_tool\x18\x01 \x01(\x0b\x32\x11.cmvr.common.Vec6\x12\x17\n\ndistance_m\x18\x02 \x01(\x01H\x00\x88\x01\x01\x12\x15\n\x08velocity\x18\x03 \x01(\x01H\x01\x88\x01\x01\x12\x19\n\x0c\x61\x63\x63\x65leration\x18\x04 \x01(\x01H\x02\x88\x01\x01\x12\x11\n\x04jerk\x18\x05 \x01(\x01H\x03\x88\x01\x01\x12\x1d\n\x15joint_velocity_limits\x18\x06 \x03(\x01\x42\r\n\x0b_distance_mB\x0b\n\t_velocityB\x0f\n\r_accelerationB\x07\n\x05_jerk\"\xae\x02\n\x1fTouchScreenTactileTriggerConfig\x12\x37\n\x06\x66inger\x18\x01 \x01(\x0e\x32\".cmvr.config.TouchScreenFingerTypeH\x00\x88\x01\x01\x12:\n\x06region\x18\x02 \x01(\x0e\x32%.cmvr.config.TouchScreenTactileRegionH\x01\x88\x01\x01\x12@\n\tcriterion\x18\x03 \x01(\x0e\x32(.cmvr.config.TouchScreenTactileCriterionH\x02\x88\x01\x01\x12\x1c\n\x0f\x66orce_threshold\x18\x04 \x01(\x01H\x03\x88\x01\x01\x42\t\n\x07_fingerB\t\n\x07_regionB\x0c\n\n_criterionB\x12\n\x10_force_threshold\"\x8b\x02\n\x1aTouchScreenTaskTouchConfig\x12<\n\x07speed_l\x18\x01 \x01(\x0b\x32).cmvr.config.TouchScreenTouchSpeedLConfigH\x00\x12:\n\x06move_l\x18\x02 \x01(\x0b\x32(.cmvr.config.TouchScreenTouchMoveLConfigH\x00\x12=\n\x07tactile\x18\x03 \x01(\x0b\x32,.cmvr.config.TouchScreenTactileTriggerConfig\x12\x19\n\x0c\x64well_time_s\x18\x04 \x01(\x01H\x01\x88\x01\x01\x42\x08\n\x06motionB\x0f\n\r_dwell_time_s\"\x99\x01\n\x1cTouchScreenTaskRetractConfig\x12%\n\ntwist_tool\x18\x01 \x01(\x0b\x32\x11.cmvr.common.Vec6\x12\x19\n\x0c\x61\x63\x63\x65leration\x18\x02 \x01(\x01H\x00\x88\x01\x01\x12\x17\n\nduration_s\x18\x03 \x01(\x01H\x01\x88\x01\x01\x42\x0f\n\r_accelerationB\r\n\x0b_duration_s\"\xab\x03\n\x15TouchScreenTaskConfig\x12:\n\x07\x64\x65vices\x18\x01 \x01(\x0b\x32).cmvr.config.TouchScreenTaskDevicesConfig\x12H\n\x0einitialization\x18\x02 \x01(\x0b\x32\x30.cmvr.config.TouchScreenTaskInitializationConfig\x12@\n\nperception\x18\x03 \x01(\x0b\x32,.cmvr.config.TouchScreenTaskPerceptionConfig\x12>\n\talignment\x18\x04 \x01(\x0b\x32+.cmvr.config.TouchScreenTaskAlignmentConfig\x12\x36\n\x05touch\x18\x05 \x01(\x0b\x32\'.cmvr.config.TouchScreenTaskTouchConfig\x12:\n\x07retract\x18\x06 \x01(\x0b\x32).cmvr.config.TouchScreenTaskRetractConfig\x12\x0f\n\x02id\x18\x07 \x01(\tH\x00\x88\x01\x01\x42\x05\n\x03_id\"Z\n\x19TouchScreenTaskRootConfig\x12=\n\x11touch_screen_task\x18\x01 \x01(\x0b\x32\".cmvr.config.TouchScreenTaskConfigb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.touch_screen_task_config.touch_screen_task_config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TOUCHSCREENINITJOINTPOINT']._serialized_start=207 + _globals['_TOUCHSCREENINITJOINTPOINT']._serialized_end=300 + _globals['_TOUCHSCREENTASKDEVICESCONFIG']._serialized_start=303 + _globals['_TOUCHSCREENTASKDEVICESCONFIG']._serialized_end=459 + _globals['_TOUCHSCREENTASKINITIALIZATIONCONFIG']._serialized_start=462 + _globals['_TOUCHSCREENTASKINITIALIZATIONCONFIG']._serialized_end=732 + _globals['_TOUCHSCREENTASKPERCEPTIONCONFIG']._serialized_start=734 + _globals['_TOUCHSCREENTASKPERCEPTIONCONFIG']._serialized_end=825 + _globals['_TOUCHSCREENALIGNMENTTARGETCONFIG']._serialized_start=828 + _globals['_TOUCHSCREENALIGNMENTTARGETCONFIG']._serialized_end=1016 + _globals['_TOUCHSCREENTASKALIGNMENTCONFIG']._serialized_start=1019 + _globals['_TOUCHSCREENTASKALIGNMENTCONFIG']._serialized_end=1366 + _globals['_TOUCHSCREENTOUCHSPEEDLCONFIG']._serialized_start=1369 + _globals['_TOUCHSCREENTOUCHSPEEDLCONFIG']._serialized_end=1530 + _globals['_TOUCHSCREENTOUCHMOVELCONFIG']._serialized_start=1533 + _globals['_TOUCHSCREENTOUCHMOVELCONFIG']._serialized_end=1784 + _globals['_TOUCHSCREENTACTILETRIGGERCONFIG']._serialized_start=1787 + _globals['_TOUCHSCREENTACTILETRIGGERCONFIG']._serialized_end=2089 + _globals['_TOUCHSCREENTASKTOUCHCONFIG']._serialized_start=2092 + _globals['_TOUCHSCREENTASKTOUCHCONFIG']._serialized_end=2359 + _globals['_TOUCHSCREENTASKRETRACTCONFIG']._serialized_start=2362 + _globals['_TOUCHSCREENTASKRETRACTCONFIG']._serialized_end=2515 + _globals['_TOUCHSCREENTASKCONFIG']._serialized_start=2518 + _globals['_TOUCHSCREENTASKCONFIG']._serialized_end=2945 + _globals['_TOUCHSCREENTASKROOTCONFIG']._serialized_start=2947 + _globals['_TOUCHSCREENTASKROOTCONFIG']._serialized_end=3037 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.pyi b/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.pyi new file mode 100644 index 0000000..aa1f378 --- /dev/null +++ b/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.pyi @@ -0,0 +1,158 @@ +from cmvr.common import geometry_pb2 as _geometry_pb2 +from cmvr.config import touch_screen_common_config_pb2 as _touch_screen_common_config_pb2 +from cmvr.config import touch_screen_algorithm_config_pb2 as _touch_screen_algorithm_config_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class TouchScreenInitJointPoint(_message.Message): + __slots__ = ("joint_name", "rad") + JOINT_NAME_FIELD_NUMBER: _ClassVar[int] + RAD_FIELD_NUMBER: _ClassVar[int] + joint_name: str + rad: float + def __init__(self, joint_name: _Optional[str] = ..., rad: _Optional[float] = ...) -> None: ... + +class TouchScreenTaskDevicesConfig(_message.Message): + __slots__ = ("arm_id", "dexhand_id", "camera_id") + ARM_ID_FIELD_NUMBER: _ClassVar[int] + DEXHAND_ID_FIELD_NUMBER: _ClassVar[int] + CAMERA_ID_FIELD_NUMBER: _ClassVar[int] + arm_id: str + dexhand_id: str + camera_id: str + def __init__(self, arm_id: _Optional[str] = ..., dexhand_id: _Optional[str] = ..., camera_id: _Optional[str] = ...) -> None: ... + +class TouchScreenTaskInitializationConfig(_message.Message): + __slots__ = ("before_start", "after_finish", "joint_positions", "velocity", "acceleration") + BEFORE_START_FIELD_NUMBER: _ClassVar[int] + AFTER_FINISH_FIELD_NUMBER: _ClassVar[int] + JOINT_POSITIONS_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + ACCELERATION_FIELD_NUMBER: _ClassVar[int] + before_start: bool + after_finish: bool + joint_positions: _containers.RepeatedCompositeFieldContainer[TouchScreenInitJointPoint] + velocity: float + acceleration: float + def __init__(self, before_start: _Optional[bool] = ..., after_finish: _Optional[bool] = ..., joint_positions: _Optional[_Iterable[_Union[TouchScreenInitJointPoint, _Mapping]]] = ..., velocity: _Optional[float] = ..., acceleration: _Optional[float] = ...) -> None: ... + +class TouchScreenTaskPerceptionConfig(_message.Message): + __slots__ = ("apriltag",) + APRILTAG_FIELD_NUMBER: _ClassVar[int] + apriltag: _touch_screen_algorithm_config_pb2.TouchScreenApriltagConfig + def __init__(self, apriltag: _Optional[_Union[_touch_screen_algorithm_config_pb2.TouchScreenApriltagConfig, _Mapping]] = ...) -> None: ... + +class TouchScreenAlignmentTargetConfig(_message.Message): + __slots__ = ("position_in_camera", "rotation_vector", "mode") + POSITION_IN_CAMERA_FIELD_NUMBER: _ClassVar[int] + ROTATION_VECTOR_FIELD_NUMBER: _ClassVar[int] + MODE_FIELD_NUMBER: _ClassVar[int] + position_in_camera: _geometry_pb2.Vec3 + rotation_vector: _geometry_pb2.Vec3 + mode: _touch_screen_common_config_pb2.TouchScreenAlignMode + def __init__(self, position_in_camera: _Optional[_Union[_geometry_pb2.Vec3, _Mapping]] = ..., rotation_vector: _Optional[_Union[_geometry_pb2.Vec3, _Mapping]] = ..., mode: _Optional[_Union[_touch_screen_common_config_pb2.TouchScreenAlignMode, str]] = ...) -> None: ... + +class TouchScreenTaskAlignmentConfig(_message.Message): + __slots__ = ("ibvs", "target", "error_threshold", "stable_frames", "timeout_s", "pause_when_reached") + IBVS_FIELD_NUMBER: _ClassVar[int] + TARGET_FIELD_NUMBER: _ClassVar[int] + ERROR_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + STABLE_FRAMES_FIELD_NUMBER: _ClassVar[int] + TIMEOUT_S_FIELD_NUMBER: _ClassVar[int] + PAUSE_WHEN_REACHED_FIELD_NUMBER: _ClassVar[int] + ibvs: _touch_screen_algorithm_config_pb2.TouchScreenIbvsConfig + target: TouchScreenAlignmentTargetConfig + error_threshold: _geometry_pb2.Vec6 + stable_frames: int + timeout_s: float + pause_when_reached: bool + def __init__(self, ibvs: _Optional[_Union[_touch_screen_algorithm_config_pb2.TouchScreenIbvsConfig, _Mapping]] = ..., target: _Optional[_Union[TouchScreenAlignmentTargetConfig, _Mapping]] = ..., error_threshold: _Optional[_Union[_geometry_pb2.Vec6, _Mapping]] = ..., stable_frames: _Optional[int] = ..., timeout_s: _Optional[float] = ..., pause_when_reached: _Optional[bool] = ...) -> None: ... + +class TouchScreenTouchSpeedLConfig(_message.Message): + __slots__ = ("twist_tool", "acceleration", "max_distance_m") + TWIST_TOOL_FIELD_NUMBER: _ClassVar[int] + ACCELERATION_FIELD_NUMBER: _ClassVar[int] + MAX_DISTANCE_M_FIELD_NUMBER: _ClassVar[int] + twist_tool: _geometry_pb2.Vec6 + acceleration: float + max_distance_m: float + def __init__(self, twist_tool: _Optional[_Union[_geometry_pb2.Vec6, _Mapping]] = ..., acceleration: _Optional[float] = ..., max_distance_m: _Optional[float] = ...) -> None: ... + +class TouchScreenTouchMoveLConfig(_message.Message): + __slots__ = ("direction_tool", "distance_m", "velocity", "acceleration", "jerk", "joint_velocity_limits") + DIRECTION_TOOL_FIELD_NUMBER: _ClassVar[int] + DISTANCE_M_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + ACCELERATION_FIELD_NUMBER: _ClassVar[int] + JERK_FIELD_NUMBER: _ClassVar[int] + JOINT_VELOCITY_LIMITS_FIELD_NUMBER: _ClassVar[int] + direction_tool: _geometry_pb2.Vec6 + distance_m: float + velocity: float + acceleration: float + jerk: float + joint_velocity_limits: _containers.RepeatedScalarFieldContainer[float] + def __init__(self, direction_tool: _Optional[_Union[_geometry_pb2.Vec6, _Mapping]] = ..., distance_m: _Optional[float] = ..., velocity: _Optional[float] = ..., acceleration: _Optional[float] = ..., jerk: _Optional[float] = ..., joint_velocity_limits: _Optional[_Iterable[float]] = ...) -> None: ... + +class TouchScreenTactileTriggerConfig(_message.Message): + __slots__ = ("finger", "region", "criterion", "force_threshold") + FINGER_FIELD_NUMBER: _ClassVar[int] + REGION_FIELD_NUMBER: _ClassVar[int] + CRITERION_FIELD_NUMBER: _ClassVar[int] + FORCE_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + finger: _touch_screen_common_config_pb2.TouchScreenFingerType + region: _touch_screen_common_config_pb2.TouchScreenTactileRegion + criterion: _touch_screen_common_config_pb2.TouchScreenTactileCriterion + force_threshold: float + def __init__(self, finger: _Optional[_Union[_touch_screen_common_config_pb2.TouchScreenFingerType, str]] = ..., region: _Optional[_Union[_touch_screen_common_config_pb2.TouchScreenTactileRegion, str]] = ..., criterion: _Optional[_Union[_touch_screen_common_config_pb2.TouchScreenTactileCriterion, str]] = ..., force_threshold: _Optional[float] = ...) -> None: ... + +class TouchScreenTaskTouchConfig(_message.Message): + __slots__ = ("speed_l", "move_l", "tactile", "dwell_time_s") + SPEED_L_FIELD_NUMBER: _ClassVar[int] + MOVE_L_FIELD_NUMBER: _ClassVar[int] + TACTILE_FIELD_NUMBER: _ClassVar[int] + DWELL_TIME_S_FIELD_NUMBER: _ClassVar[int] + speed_l: TouchScreenTouchSpeedLConfig + move_l: TouchScreenTouchMoveLConfig + tactile: TouchScreenTactileTriggerConfig + dwell_time_s: float + def __init__(self, speed_l: _Optional[_Union[TouchScreenTouchSpeedLConfig, _Mapping]] = ..., move_l: _Optional[_Union[TouchScreenTouchMoveLConfig, _Mapping]] = ..., tactile: _Optional[_Union[TouchScreenTactileTriggerConfig, _Mapping]] = ..., dwell_time_s: _Optional[float] = ...) -> None: ... + +class TouchScreenTaskRetractConfig(_message.Message): + __slots__ = ("twist_tool", "acceleration", "duration_s") + TWIST_TOOL_FIELD_NUMBER: _ClassVar[int] + ACCELERATION_FIELD_NUMBER: _ClassVar[int] + DURATION_S_FIELD_NUMBER: _ClassVar[int] + twist_tool: _geometry_pb2.Vec6 + acceleration: float + duration_s: float + def __init__(self, twist_tool: _Optional[_Union[_geometry_pb2.Vec6, _Mapping]] = ..., acceleration: _Optional[float] = ..., duration_s: _Optional[float] = ...) -> None: ... + +class TouchScreenTaskConfig(_message.Message): + __slots__ = ("devices", "initialization", "perception", "alignment", "touch", "retract", "id") + DEVICES_FIELD_NUMBER: _ClassVar[int] + INITIALIZATION_FIELD_NUMBER: _ClassVar[int] + PERCEPTION_FIELD_NUMBER: _ClassVar[int] + ALIGNMENT_FIELD_NUMBER: _ClassVar[int] + TOUCH_FIELD_NUMBER: _ClassVar[int] + RETRACT_FIELD_NUMBER: _ClassVar[int] + ID_FIELD_NUMBER: _ClassVar[int] + devices: TouchScreenTaskDevicesConfig + initialization: TouchScreenTaskInitializationConfig + perception: TouchScreenTaskPerceptionConfig + alignment: TouchScreenTaskAlignmentConfig + touch: TouchScreenTaskTouchConfig + retract: TouchScreenTaskRetractConfig + id: str + def __init__(self, devices: _Optional[_Union[TouchScreenTaskDevicesConfig, _Mapping]] = ..., initialization: _Optional[_Union[TouchScreenTaskInitializationConfig, _Mapping]] = ..., perception: _Optional[_Union[TouchScreenTaskPerceptionConfig, _Mapping]] = ..., alignment: _Optional[_Union[TouchScreenTaskAlignmentConfig, _Mapping]] = ..., touch: _Optional[_Union[TouchScreenTaskTouchConfig, _Mapping]] = ..., retract: _Optional[_Union[TouchScreenTaskRetractConfig, _Mapping]] = ..., id: _Optional[str] = ...) -> None: ... + +class TouchScreenTaskRootConfig(_message.Message): + __slots__ = ("touch_screen_task",) + TOUCH_SCREEN_TASK_FIELD_NUMBER: _ClassVar[int] + touch_screen_task: TouchScreenTaskConfig + def __init__(self, touch_screen_task: _Optional[_Union[TouchScreenTaskConfig, _Mapping]] = ...) -> None: ... diff --git a/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2_grpc.py b/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2_grpc.py new file mode 100644 index 0000000..d82d483 --- /dev/null +++ b/src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/msgs/__pycache__/agv_pb2.cpython-310.pyc b/src/cmvr/msgs/__pycache__/agv_pb2.cpython-310.pyc new file mode 100644 index 0000000..84b6dce Binary files /dev/null and b/src/cmvr/msgs/__pycache__/agv_pb2.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/agv_pb2.cpython-312.pyc b/src/cmvr/msgs/__pycache__/agv_pb2.cpython-312.pyc new file mode 100644 index 0000000..d29391b Binary files /dev/null and b/src/cmvr/msgs/__pycache__/agv_pb2.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-310.pyc b/src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..d86387e Binary files /dev/null and b/src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-312.pyc b/src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..6368637 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-310.pyc b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-310.pyc new file mode 100644 index 0000000..6582c0d Binary files /dev/null and b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-312.pyc b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-312.pyc new file mode 100644 index 0000000..658adc2 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-310.pyc b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..6c6ac91 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-312.pyc b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..f4df27d Binary files /dev/null and b/src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/canopen_pb2.cpython-310.pyc b/src/cmvr/msgs/__pycache__/canopen_pb2.cpython-310.pyc new file mode 100644 index 0000000..bcf1f42 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/canopen_pb2.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/canopen_pb2.cpython-312.pyc b/src/cmvr/msgs/__pycache__/canopen_pb2.cpython-312.pyc new file mode 100644 index 0000000..ce35b32 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/canopen_pb2.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-310.pyc b/src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..fbb6d75 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-312.pyc b/src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..3e3ef29 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/error_code_pb2.cpython-310.pyc b/src/cmvr/msgs/__pycache__/error_code_pb2.cpython-310.pyc new file mode 100644 index 0000000..e4c264e Binary files /dev/null and b/src/cmvr/msgs/__pycache__/error_code_pb2.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/error_code_pb2.cpython-312.pyc b/src/cmvr/msgs/__pycache__/error_code_pb2.cpython-312.pyc new file mode 100644 index 0000000..f2d3c72 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/error_code_pb2.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-310.pyc b/src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..4302bdf Binary files /dev/null and b/src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-312.pyc b/src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..907fb66 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/motor_pb2.cpython-310.pyc b/src/cmvr/msgs/__pycache__/motor_pb2.cpython-310.pyc new file mode 100644 index 0000000..35ad367 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/motor_pb2.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/motor_pb2.cpython-312.pyc b/src/cmvr/msgs/__pycache__/motor_pb2.cpython-312.pyc new file mode 100644 index 0000000..f0bb8d3 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/motor_pb2.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-310.pyc b/src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..5452f55 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-312.pyc b/src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..eb6128c Binary files /dev/null and b/src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-310.pyc b/src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-310.pyc new file mode 100644 index 0000000..8bf5a30 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-312.pyc b/src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-312.pyc new file mode 100644 index 0000000..c3e20eb Binary files /dev/null and b/src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-312.pyc differ diff --git a/src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-310.pyc b/src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..1a572e9 Binary files /dev/null and b/src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-310.pyc differ diff --git a/src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-312.pyc b/src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-312.pyc new file mode 100644 index 0000000..336061f Binary files /dev/null and b/src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-312.pyc differ diff --git a/src/cmvr/msgs/agv_pb2.py b/src/cmvr/msgs/agv_pb2.py new file mode 100644 index 0000000..1da853e --- /dev/null +++ b/src/cmvr/msgs/agv_pb2.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/msgs/agv.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/msgs/agv.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13\x63mvr/msgs/agv.proto\x12\tcmvr.msgs\"0\n\tAgvPose2d\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\r\n\x05theta\x18\x03 \x01(\x01\"1\n\x0b\x41gvVelocity\x12\n\n\x02vx\x18\x01 \x01(\x01\x12\n\n\x02vy\x18\x02 \x01(\x01\x12\n\n\x02wz\x18\x03 \x01(\x01\"n\n\x0f\x41gvBatteryState\x12\x12\n\npercentage\x18\x01 \x01(\x01\x12\x0f\n\x07voltage\x18\x02 \x01(\x01\x12\x0f\n\x07\x63urrent\x18\x03 \x01(\x01\x12\x13\n\x0btemperature\x18\x04 \x01(\x01\x12\x10\n\x08\x63harging\x18\x05 \x01(\x08\"\xd4\x01\n\x10\x41gvMotionOptions\x12\x11\n\tmax_speed\x18\x01 \x01(\x01\x12\x19\n\x11max_angular_speed\x18\x02 \x01(\x01\x12\x18\n\x10max_acceleration\x18\x03 \x01(\x01\x12 \n\x18max_angular_acceleration\x18\x04 \x01(\x01\x12\x16\n\x0ereach_distance\x18\x05 \x01(\x01\x12\x13\n\x0breach_angle\x18\x06 \x01(\x01\x12\x13\n\x0bspeed_ratio\x18\x07 \x01(\x01\x12\x14\n\x0c\x61synchronous\x18\x08 \x01(\x08\"z\n\x10\x41gvAdapterParams\x12\x37\n\x06values\x18\x01 \x03(\x0b\x32\'.cmvr.msgs.AgvAdapterParams.ValuesEntry\x1a-\n\x0bValuesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xcf\x02\n\x0f\x41gvRuntimeState\x12\x11\n\ttimestamp\x18\x01 \x01(\x01\x12\x0c\n\x04mode\x18\x02 \x01(\x05\x12\x11\n\tconnected\x18\x03 \x01(\x08\x12\x11\n\tlocalized\x18\x04 \x01(\x08\x12\x0e\n\x06moving\x18\x05 \x01(\x08\x12\r\n\x05\x66\x61ult\x18\x06 \x01(\x08\x12\x19\n\x11\x65mergency_stopped\x18\x07 \x01(\x08\x12\"\n\x04pose\x18\x08 \x01(\x0b\x32\x14.cmvr.msgs.AgvPose2d\x12(\n\x08velocity\x18\t \x01(\x0b\x32\x16.cmvr.msgs.AgvVelocity\x12+\n\x07\x62\x61ttery\x18\n \x01(\x0b\x32\x1a.cmvr.msgs.AgvBatteryState\x12\x13\n\x0b\x63urrent_map\x18\x0b \x01(\t\x12\x17\n\x0f\x63urrent_station\x18\x0c \x01(\t\x12\x12\n\nlast_error\x18\r \x01(\t\"_\n\nAgvStation\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\"\n\x04pose\x18\x03 \x01(\x0b\x32\x14.cmvr.msgs.AgvPose2d\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\"@\n\x0e\x41gvPathSegment\x12\x16\n\x0esource_station\x18\x01 \x01(\t\x12\x16\n\x0etarget_station\x18\x02 \x01(\t\"0\n\rAgvMapPoint3D\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\t\n\x01z\x18\x03 \x01(\x01\"\xc7\x01\n\x0f\x41gvUnifiedMap2D\x12\x10\n\x08\x66rame_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\x01\x12\x12\n\nresolution\x18\x03 \x01(\x01\x12\r\n\x05width\x18\x04 \x01(\r\x12\x0e\n\x06height\x18\x05 \x01(\r\x12$\n\x06origin\x18\x06 \x01(\x0b\x32\x14.cmvr.msgs.AgvPose2d\x12\x0c\n\x04\x64\x61ta\x18\x07 \x03(\x05\x12(\n\x07objects\x18\x08 \x03(\x0b\x32\x17.cmvr.msgs.AgvMapObject\"l\n\x13\x41gvMapPointSample3D\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\t\n\x01z\x18\x03 \x01(\x01\x12\x11\n\tintensity\x18\x04 \x01(\x02\x12\x0c\n\x04ring\x18\x05 \x01(\r\x12\x13\n\x0btime_offset\x18\x06 \x01(\x01\"E\n\rAgvMapVoxel3D\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05\x12\t\n\x01z\x18\x03 \x01(\x05\x12\x13\n\x0bprobability\x18\x04 \x01(\x02\"~\n\rAgvMapPlane3D\x12(\n\x06\x63\x65nter\x18\x01 \x01(\x0b\x32\x18.cmvr.msgs.AgvMapPoint3D\x12(\n\x06normal\x18\x02 \x01(\x0b\x32\x18.cmvr.msgs.AgvMapPoint3D\x12\t\n\x01\x64\x18\x03 \x01(\x01\x12\x0e\n\x06radius\x18\x04 \x01(\x01\"\xf0\x01\n\x0c\x41gvMapObject\x12\n\n\x02id\x18\x01 \x01(\t\x12)\n\x04type\x18\x02 \x01(\x0e\x32\x1b.cmvr.msgs.AgvMapObjectType\x12(\n\x06points\x18\x03 \x03(\x0b\x32\x18.cmvr.msgs.AgvMapPoint3D\x12\x0f\n\x07heading\x18\x04 \x01(\x01\x12;\n\nproperties\x18\x05 \x03(\x0b\x32\'.cmvr.msgs.AgvMapObject.PropertiesEntry\x1a\x31\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xfe\x01\n\x0f\x41gvUnifiedMap3D\x12\x10\n\x08\x66rame_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\x01\x12\x18\n\x10voxel_resolution\x18\x03 \x01(\x01\x12.\n\x06points\x18\x04 \x03(\x0b\x32\x1e.cmvr.msgs.AgvMapPointSample3D\x12(\n\x06voxels\x18\x05 \x03(\x0b\x32\x18.cmvr.msgs.AgvMapVoxel3D\x12(\n\x06planes\x18\x06 \x03(\x0b\x32\x18.cmvr.msgs.AgvMapPlane3D\x12(\n\x07objects\x18\x07 \x03(\x0b\x32\x17.cmvr.msgs.AgvMapObject\"\xa6\x03\n\x13\x41gvUnifiedMapUpdate\x12\x0e\n\x06map_id\x18\x01 \x01(\t\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12\x10\n\x08sequence\x18\x03 \x01(\x04\x12\x14\n\x0cresume_token\x18\x04 \x01(\t\x12-\n\tdimension\x18\x05 \x01(\x0e\x32\x1a.cmvr.msgs.AgvMapDimension\x12\x30\n\x0bupdate_type\x18\x06 \x01(\x0e\x32\x1b.cmvr.msgs.AgvMapUpdateType\x12\x10\n\x08\x66rame_id\x18\x07 \x01(\t\x12\x11\n\ttimestamp\x18\x08 \x01(\x01\x12\x16\n\x0esnapshot_begin\x18\t \x01(\x08\x12\x14\n\x0csnapshot_end\x18\n \x01(\x08\x12\x13\n\x0b\x63hunk_index\x18\x0b \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x0c \x01(\r\x12,\n\x06map_2d\x18\x14 \x01(\x0b\x32\x1a.cmvr.msgs.AgvUnifiedMap2DH\x00\x12,\n\x06map_3d\x18\x15 \x01(\x0b\x32\x1a.cmvr.msgs.AgvUnifiedMap3DH\x00\x42\t\n\x07payload\"U\n\x13\x41gvNavigationStatus\x12\r\n\x05state\x18\x01 \x01(\x05\x12\x0c\n\x04type\x18\x02 \x01(\x05\x12\x10\n\x08progress\x18\x03 \x01(\x01\x12\x0f\n\x07message\x18\x04 \x01(\t*k\n\x0f\x41gvMapDimension\x12!\n\x1d\x41GV_MAP_DIMENSION_UNSPECIFIED\x10\x00\x12\x0e\n\nAGV_MAP_2D\x10\x01\x12\x0e\n\nAGV_MAP_3D\x10\x02\x12\x15\n\x11\x41GV_MAP_2D_AND_3D\x10\x03*\x89\x01\n\x10\x41gvMapUpdateType\x12\x1e\n\x1a\x41GV_MAP_UPDATE_UNSPECIFIED\x10\x00\x12\x1b\n\x17\x41GV_MAP_UPDATE_SNAPSHOT\x10\x01\x12\x1e\n\x1a\x41GV_MAP_UPDATE_INCREMENTAL\x10\x02\x12\x18\n\x14\x41GV_MAP_UPDATE_RESET\x10\x03*\xfe\x01\n\x10\x41gvMapObjectType\x12\x1e\n\x1a\x41GV_MAP_OBJECT_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x41GV_MAP_OBJECT_STATION\x10\x01\x12\x17\n\x13\x41GV_MAP_OBJECT_LINE\x10\x02\x12\x17\n\x13\x41GV_MAP_OBJECT_AREA\x10\x03\x12\x19\n\x15\x41GV_MAP_OBJECT_QR_TAG\x10\x04\x12\x1c\n\x18\x41GV_MAP_OBJECT_REFLECTOR\x10\x05\x12\x1f\n\x1b\x41GV_MAP_OBJECT_BIN_LOCATION\x10\x06\x12\"\n\x1e\x41GV_MAP_OBJECT_EXTERNAL_DEVICE\x10\x07\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.msgs.agv_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_AGVADAPTERPARAMS_VALUESENTRY']._loaded_options = None + _globals['_AGVADAPTERPARAMS_VALUESENTRY']._serialized_options = b'8\001' + _globals['_AGVMAPOBJECT_PROPERTIESENTRY']._loaded_options = None + _globals['_AGVMAPOBJECT_PROPERTIESENTRY']._serialized_options = b'8\001' + _globals['_AGVMAPDIMENSION']._serialized_start=2660 + _globals['_AGVMAPDIMENSION']._serialized_end=2767 + _globals['_AGVMAPUPDATETYPE']._serialized_start=2770 + _globals['_AGVMAPUPDATETYPE']._serialized_end=2907 + _globals['_AGVMAPOBJECTTYPE']._serialized_start=2910 + _globals['_AGVMAPOBJECTTYPE']._serialized_end=3164 + _globals['_AGVPOSE2D']._serialized_start=34 + _globals['_AGVPOSE2D']._serialized_end=82 + _globals['_AGVVELOCITY']._serialized_start=84 + _globals['_AGVVELOCITY']._serialized_end=133 + _globals['_AGVBATTERYSTATE']._serialized_start=135 + _globals['_AGVBATTERYSTATE']._serialized_end=245 + _globals['_AGVMOTIONOPTIONS']._serialized_start=248 + _globals['_AGVMOTIONOPTIONS']._serialized_end=460 + _globals['_AGVADAPTERPARAMS']._serialized_start=462 + _globals['_AGVADAPTERPARAMS']._serialized_end=584 + _globals['_AGVADAPTERPARAMS_VALUESENTRY']._serialized_start=539 + _globals['_AGVADAPTERPARAMS_VALUESENTRY']._serialized_end=584 + _globals['_AGVRUNTIMESTATE']._serialized_start=587 + _globals['_AGVRUNTIMESTATE']._serialized_end=922 + _globals['_AGVSTATION']._serialized_start=924 + _globals['_AGVSTATION']._serialized_end=1019 + _globals['_AGVPATHSEGMENT']._serialized_start=1021 + _globals['_AGVPATHSEGMENT']._serialized_end=1085 + _globals['_AGVMAPPOINT3D']._serialized_start=1087 + _globals['_AGVMAPPOINT3D']._serialized_end=1135 + _globals['_AGVUNIFIEDMAP2D']._serialized_start=1138 + _globals['_AGVUNIFIEDMAP2D']._serialized_end=1337 + _globals['_AGVMAPPOINTSAMPLE3D']._serialized_start=1339 + _globals['_AGVMAPPOINTSAMPLE3D']._serialized_end=1447 + _globals['_AGVMAPVOXEL3D']._serialized_start=1449 + _globals['_AGVMAPVOXEL3D']._serialized_end=1518 + _globals['_AGVMAPPLANE3D']._serialized_start=1520 + _globals['_AGVMAPPLANE3D']._serialized_end=1646 + _globals['_AGVMAPOBJECT']._serialized_start=1649 + _globals['_AGVMAPOBJECT']._serialized_end=1889 + _globals['_AGVMAPOBJECT_PROPERTIESENTRY']._serialized_start=1840 + _globals['_AGVMAPOBJECT_PROPERTIESENTRY']._serialized_end=1889 + _globals['_AGVUNIFIEDMAP3D']._serialized_start=1892 + _globals['_AGVUNIFIEDMAP3D']._serialized_end=2146 + _globals['_AGVUNIFIEDMAPUPDATE']._serialized_start=2149 + _globals['_AGVUNIFIEDMAPUPDATE']._serialized_end=2571 + _globals['_AGVNAVIGATIONSTATUS']._serialized_start=2573 + _globals['_AGVNAVIGATIONSTATUS']._serialized_end=2658 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/msgs/agv_pb2.pyi b/src/cmvr/msgs/agv_pb2.pyi new file mode 100644 index 0000000..a69d3c6 --- /dev/null +++ b/src/cmvr/msgs/agv_pb2.pyi @@ -0,0 +1,319 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class AgvMapDimension(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + AGV_MAP_DIMENSION_UNSPECIFIED: _ClassVar[AgvMapDimension] + AGV_MAP_2D: _ClassVar[AgvMapDimension] + AGV_MAP_3D: _ClassVar[AgvMapDimension] + AGV_MAP_2D_AND_3D: _ClassVar[AgvMapDimension] + +class AgvMapUpdateType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + AGV_MAP_UPDATE_UNSPECIFIED: _ClassVar[AgvMapUpdateType] + AGV_MAP_UPDATE_SNAPSHOT: _ClassVar[AgvMapUpdateType] + AGV_MAP_UPDATE_INCREMENTAL: _ClassVar[AgvMapUpdateType] + AGV_MAP_UPDATE_RESET: _ClassVar[AgvMapUpdateType] + +class AgvMapObjectType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + AGV_MAP_OBJECT_UNSPECIFIED: _ClassVar[AgvMapObjectType] + AGV_MAP_OBJECT_STATION: _ClassVar[AgvMapObjectType] + AGV_MAP_OBJECT_LINE: _ClassVar[AgvMapObjectType] + AGV_MAP_OBJECT_AREA: _ClassVar[AgvMapObjectType] + AGV_MAP_OBJECT_QR_TAG: _ClassVar[AgvMapObjectType] + AGV_MAP_OBJECT_REFLECTOR: _ClassVar[AgvMapObjectType] + AGV_MAP_OBJECT_BIN_LOCATION: _ClassVar[AgvMapObjectType] + AGV_MAP_OBJECT_EXTERNAL_DEVICE: _ClassVar[AgvMapObjectType] +AGV_MAP_DIMENSION_UNSPECIFIED: AgvMapDimension +AGV_MAP_2D: AgvMapDimension +AGV_MAP_3D: AgvMapDimension +AGV_MAP_2D_AND_3D: AgvMapDimension +AGV_MAP_UPDATE_UNSPECIFIED: AgvMapUpdateType +AGV_MAP_UPDATE_SNAPSHOT: AgvMapUpdateType +AGV_MAP_UPDATE_INCREMENTAL: AgvMapUpdateType +AGV_MAP_UPDATE_RESET: AgvMapUpdateType +AGV_MAP_OBJECT_UNSPECIFIED: AgvMapObjectType +AGV_MAP_OBJECT_STATION: AgvMapObjectType +AGV_MAP_OBJECT_LINE: AgvMapObjectType +AGV_MAP_OBJECT_AREA: AgvMapObjectType +AGV_MAP_OBJECT_QR_TAG: AgvMapObjectType +AGV_MAP_OBJECT_REFLECTOR: AgvMapObjectType +AGV_MAP_OBJECT_BIN_LOCATION: AgvMapObjectType +AGV_MAP_OBJECT_EXTERNAL_DEVICE: AgvMapObjectType + +class AgvPose2d(_message.Message): + __slots__ = ("x", "y", "theta") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + THETA_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + theta: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., theta: _Optional[float] = ...) -> None: ... + +class AgvVelocity(_message.Message): + __slots__ = ("vx", "vy", "wz") + VX_FIELD_NUMBER: _ClassVar[int] + VY_FIELD_NUMBER: _ClassVar[int] + WZ_FIELD_NUMBER: _ClassVar[int] + vx: float + vy: float + wz: float + def __init__(self, vx: _Optional[float] = ..., vy: _Optional[float] = ..., wz: _Optional[float] = ...) -> None: ... + +class AgvBatteryState(_message.Message): + __slots__ = ("percentage", "voltage", "current", "temperature", "charging") + PERCENTAGE_FIELD_NUMBER: _ClassVar[int] + VOLTAGE_FIELD_NUMBER: _ClassVar[int] + CURRENT_FIELD_NUMBER: _ClassVar[int] + TEMPERATURE_FIELD_NUMBER: _ClassVar[int] + CHARGING_FIELD_NUMBER: _ClassVar[int] + percentage: float + voltage: float + current: float + temperature: float + charging: bool + def __init__(self, percentage: _Optional[float] = ..., voltage: _Optional[float] = ..., current: _Optional[float] = ..., temperature: _Optional[float] = ..., charging: _Optional[bool] = ...) -> None: ... + +class AgvMotionOptions(_message.Message): + __slots__ = ("max_speed", "max_angular_speed", "max_acceleration", "max_angular_acceleration", "reach_distance", "reach_angle", "speed_ratio", "asynchronous") + MAX_SPEED_FIELD_NUMBER: _ClassVar[int] + MAX_ANGULAR_SPEED_FIELD_NUMBER: _ClassVar[int] + MAX_ACCELERATION_FIELD_NUMBER: _ClassVar[int] + MAX_ANGULAR_ACCELERATION_FIELD_NUMBER: _ClassVar[int] + REACH_DISTANCE_FIELD_NUMBER: _ClassVar[int] + REACH_ANGLE_FIELD_NUMBER: _ClassVar[int] + SPEED_RATIO_FIELD_NUMBER: _ClassVar[int] + ASYNCHRONOUS_FIELD_NUMBER: _ClassVar[int] + max_speed: float + max_angular_speed: float + max_acceleration: float + max_angular_acceleration: float + reach_distance: float + reach_angle: float + speed_ratio: float + asynchronous: bool + def __init__(self, max_speed: _Optional[float] = ..., max_angular_speed: _Optional[float] = ..., max_acceleration: _Optional[float] = ..., max_angular_acceleration: _Optional[float] = ..., reach_distance: _Optional[float] = ..., reach_angle: _Optional[float] = ..., speed_ratio: _Optional[float] = ..., asynchronous: _Optional[bool] = ...) -> None: ... + +class AgvAdapterParams(_message.Message): + __slots__ = ("values",) + class ValuesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + VALUES_FIELD_NUMBER: _ClassVar[int] + values: _containers.ScalarMap[str, str] + def __init__(self, values: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class AgvRuntimeState(_message.Message): + __slots__ = ("timestamp", "mode", "connected", "localized", "moving", "fault", "emergency_stopped", "pose", "velocity", "battery", "current_map", "current_station", "last_error") + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + MODE_FIELD_NUMBER: _ClassVar[int] + CONNECTED_FIELD_NUMBER: _ClassVar[int] + LOCALIZED_FIELD_NUMBER: _ClassVar[int] + MOVING_FIELD_NUMBER: _ClassVar[int] + FAULT_FIELD_NUMBER: _ClassVar[int] + EMERGENCY_STOPPED_FIELD_NUMBER: _ClassVar[int] + POSE_FIELD_NUMBER: _ClassVar[int] + VELOCITY_FIELD_NUMBER: _ClassVar[int] + BATTERY_FIELD_NUMBER: _ClassVar[int] + CURRENT_MAP_FIELD_NUMBER: _ClassVar[int] + CURRENT_STATION_FIELD_NUMBER: _ClassVar[int] + LAST_ERROR_FIELD_NUMBER: _ClassVar[int] + timestamp: float + mode: int + connected: bool + localized: bool + moving: bool + fault: bool + emergency_stopped: bool + pose: AgvPose2d + velocity: AgvVelocity + battery: AgvBatteryState + current_map: str + current_station: str + last_error: str + def __init__(self, timestamp: _Optional[float] = ..., mode: _Optional[int] = ..., connected: _Optional[bool] = ..., localized: _Optional[bool] = ..., moving: _Optional[bool] = ..., fault: _Optional[bool] = ..., emergency_stopped: _Optional[bool] = ..., pose: _Optional[_Union[AgvPose2d, _Mapping]] = ..., velocity: _Optional[_Union[AgvVelocity, _Mapping]] = ..., battery: _Optional[_Union[AgvBatteryState, _Mapping]] = ..., current_map: _Optional[str] = ..., current_station: _Optional[str] = ..., last_error: _Optional[str] = ...) -> None: ... + +class AgvStation(_message.Message): + __slots__ = ("id", "type", "pose", "description") + ID_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + POSE_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + id: str + type: str + pose: AgvPose2d + description: str + def __init__(self, id: _Optional[str] = ..., type: _Optional[str] = ..., pose: _Optional[_Union[AgvPose2d, _Mapping]] = ..., description: _Optional[str] = ...) -> None: ... + +class AgvPathSegment(_message.Message): + __slots__ = ("source_station", "target_station") + SOURCE_STATION_FIELD_NUMBER: _ClassVar[int] + TARGET_STATION_FIELD_NUMBER: _ClassVar[int] + source_station: str + target_station: str + def __init__(self, source_station: _Optional[str] = ..., target_station: _Optional[str] = ...) -> None: ... + +class AgvMapPoint3D(_message.Message): + __slots__ = ("x", "y", "z") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + z: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ...) -> None: ... + +class AgvUnifiedMap2D(_message.Message): + __slots__ = ("frame_id", "timestamp", "resolution", "width", "height", "origin", "data", "objects") + FRAME_ID_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + RESOLUTION_FIELD_NUMBER: _ClassVar[int] + WIDTH_FIELD_NUMBER: _ClassVar[int] + HEIGHT_FIELD_NUMBER: _ClassVar[int] + ORIGIN_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + OBJECTS_FIELD_NUMBER: _ClassVar[int] + frame_id: str + timestamp: float + resolution: float + width: int + height: int + origin: AgvPose2d + data: _containers.RepeatedScalarFieldContainer[int] + objects: _containers.RepeatedCompositeFieldContainer[AgvMapObject] + def __init__(self, frame_id: _Optional[str] = ..., timestamp: _Optional[float] = ..., resolution: _Optional[float] = ..., width: _Optional[int] = ..., height: _Optional[int] = ..., origin: _Optional[_Union[AgvPose2d, _Mapping]] = ..., data: _Optional[_Iterable[int]] = ..., objects: _Optional[_Iterable[_Union[AgvMapObject, _Mapping]]] = ...) -> None: ... + +class AgvMapPointSample3D(_message.Message): + __slots__ = ("x", "y", "z", "intensity", "ring", "time_offset") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + INTENSITY_FIELD_NUMBER: _ClassVar[int] + RING_FIELD_NUMBER: _ClassVar[int] + TIME_OFFSET_FIELD_NUMBER: _ClassVar[int] + x: float + y: float + z: float + intensity: float + ring: int + time_offset: float + def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ..., intensity: _Optional[float] = ..., ring: _Optional[int] = ..., time_offset: _Optional[float] = ...) -> None: ... + +class AgvMapVoxel3D(_message.Message): + __slots__ = ("x", "y", "z", "probability") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + PROBABILITY_FIELD_NUMBER: _ClassVar[int] + x: int + y: int + z: int + probability: float + def __init__(self, x: _Optional[int] = ..., y: _Optional[int] = ..., z: _Optional[int] = ..., probability: _Optional[float] = ...) -> None: ... + +class AgvMapPlane3D(_message.Message): + __slots__ = ("center", "normal", "d", "radius") + CENTER_FIELD_NUMBER: _ClassVar[int] + NORMAL_FIELD_NUMBER: _ClassVar[int] + D_FIELD_NUMBER: _ClassVar[int] + RADIUS_FIELD_NUMBER: _ClassVar[int] + center: AgvMapPoint3D + normal: AgvMapPoint3D + d: float + radius: float + def __init__(self, center: _Optional[_Union[AgvMapPoint3D, _Mapping]] = ..., normal: _Optional[_Union[AgvMapPoint3D, _Mapping]] = ..., d: _Optional[float] = ..., radius: _Optional[float] = ...) -> None: ... + +class AgvMapObject(_message.Message): + __slots__ = ("id", "type", "points", "heading", "properties") + class PropertiesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + POINTS_FIELD_NUMBER: _ClassVar[int] + HEADING_FIELD_NUMBER: _ClassVar[int] + PROPERTIES_FIELD_NUMBER: _ClassVar[int] + id: str + type: AgvMapObjectType + points: _containers.RepeatedCompositeFieldContainer[AgvMapPoint3D] + heading: float + properties: _containers.ScalarMap[str, str] + def __init__(self, id: _Optional[str] = ..., type: _Optional[_Union[AgvMapObjectType, str]] = ..., points: _Optional[_Iterable[_Union[AgvMapPoint3D, _Mapping]]] = ..., heading: _Optional[float] = ..., properties: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class AgvUnifiedMap3D(_message.Message): + __slots__ = ("frame_id", "timestamp", "voxel_resolution", "points", "voxels", "planes", "objects") + FRAME_ID_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + VOXEL_RESOLUTION_FIELD_NUMBER: _ClassVar[int] + POINTS_FIELD_NUMBER: _ClassVar[int] + VOXELS_FIELD_NUMBER: _ClassVar[int] + PLANES_FIELD_NUMBER: _ClassVar[int] + OBJECTS_FIELD_NUMBER: _ClassVar[int] + frame_id: str + timestamp: float + voxel_resolution: float + points: _containers.RepeatedCompositeFieldContainer[AgvMapPointSample3D] + voxels: _containers.RepeatedCompositeFieldContainer[AgvMapVoxel3D] + planes: _containers.RepeatedCompositeFieldContainer[AgvMapPlane3D] + objects: _containers.RepeatedCompositeFieldContainer[AgvMapObject] + def __init__(self, frame_id: _Optional[str] = ..., timestamp: _Optional[float] = ..., voxel_resolution: _Optional[float] = ..., points: _Optional[_Iterable[_Union[AgvMapPointSample3D, _Mapping]]] = ..., voxels: _Optional[_Iterable[_Union[AgvMapVoxel3D, _Mapping]]] = ..., planes: _Optional[_Iterable[_Union[AgvMapPlane3D, _Mapping]]] = ..., objects: _Optional[_Iterable[_Union[AgvMapObject, _Mapping]]] = ...) -> None: ... + +class AgvUnifiedMapUpdate(_message.Message): + __slots__ = ("map_id", "session_id", "sequence", "resume_token", "dimension", "update_type", "frame_id", "timestamp", "snapshot_begin", "snapshot_end", "chunk_index", "chunk_count", "map_2d", "map_3d") + MAP_ID_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + SEQUENCE_FIELD_NUMBER: _ClassVar[int] + RESUME_TOKEN_FIELD_NUMBER: _ClassVar[int] + DIMENSION_FIELD_NUMBER: _ClassVar[int] + UPDATE_TYPE_FIELD_NUMBER: _ClassVar[int] + FRAME_ID_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + SNAPSHOT_BEGIN_FIELD_NUMBER: _ClassVar[int] + SNAPSHOT_END_FIELD_NUMBER: _ClassVar[int] + CHUNK_INDEX_FIELD_NUMBER: _ClassVar[int] + CHUNK_COUNT_FIELD_NUMBER: _ClassVar[int] + MAP_2D_FIELD_NUMBER: _ClassVar[int] + MAP_3D_FIELD_NUMBER: _ClassVar[int] + map_id: str + session_id: str + sequence: int + resume_token: str + dimension: AgvMapDimension + update_type: AgvMapUpdateType + frame_id: str + timestamp: float + snapshot_begin: bool + snapshot_end: bool + chunk_index: int + chunk_count: int + map_2d: AgvUnifiedMap2D + map_3d: AgvUnifiedMap3D + def __init__(self, map_id: _Optional[str] = ..., session_id: _Optional[str] = ..., sequence: _Optional[int] = ..., resume_token: _Optional[str] = ..., dimension: _Optional[_Union[AgvMapDimension, str]] = ..., update_type: _Optional[_Union[AgvMapUpdateType, str]] = ..., frame_id: _Optional[str] = ..., timestamp: _Optional[float] = ..., snapshot_begin: _Optional[bool] = ..., snapshot_end: _Optional[bool] = ..., chunk_index: _Optional[int] = ..., chunk_count: _Optional[int] = ..., map_2d: _Optional[_Union[AgvUnifiedMap2D, _Mapping]] = ..., map_3d: _Optional[_Union[AgvUnifiedMap3D, _Mapping]] = ...) -> None: ... + +class AgvNavigationStatus(_message.Message): + __slots__ = ("state", "type", "progress", "message") + STATE_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + PROGRESS_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + state: int + type: int + progress: float + message: str + def __init__(self, state: _Optional[int] = ..., type: _Optional[int] = ..., progress: _Optional[float] = ..., message: _Optional[str] = ...) -> None: ... diff --git a/src/cmvr/msgs/agv_pb2_grpc.py b/src/cmvr/msgs/agv_pb2_grpc.py new file mode 100644 index 0000000..13ec237 --- /dev/null +++ b/src/cmvr/msgs/agv_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/msgs/agv_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/msgs/can_card_parameter_pb2.py b/src/cmvr/msgs/can_card_parameter_pb2.py new file mode 100644 index 0000000..6f1d211 --- /dev/null +++ b/src/cmvr/msgs/can_card_parameter_pb2.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/msgs/can_card_parameter.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/msgs/can_card_parameter.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"cmvr/msgs/can_card_parameter.proto\x12\tcmvr.msgs\"\x98\x07\n\x10\x43\x41NCardParameter\x12<\n\x05\x62rand\x18\x01 \x01(\x0e\x32(.cmvr.msgs.CANCardParameter.CANCardBrandH\x00\x88\x01\x01\x12:\n\x04type\x18\x02 \x01(\x0e\x32\'.cmvr.msgs.CANCardParameter.CANCardTypeH\x01\x88\x01\x01\x12\x41\n\nchannel_id\x18\x03 \x01(\x0e\x32(.cmvr.msgs.CANCardParameter.CANChannelIdH\x02\x88\x01\x01\x12@\n\tinterface\x18\x04 \x01(\x0e\x32(.cmvr.msgs.CANCardParameter.CANInterfaceH\x03\x88\x01\x01\x12\x16\n\tnum_ports\x18\x05 \x01(\rH\x04\x88\x01\x01\x12;\n\x08\x62\x61udrate\x18\x06 \x01(\x0e\x32$.cmvr.msgs.CANCardParameter.BAUDRATEH\x05\x88\x01\x01\"M\n\x0c\x43\x41NCardBrand\x12\x0c\n\x08\x46\x41KE_CAN\x10\x00\x12\x0b\n\x07\x45SD_CAN\x10\x01\x12\x12\n\x0eSOCKET_CAN_RAW\x10\x02\x12\x0e\n\nHERMES_CAN\x10\x03\")\n\x0b\x43\x41NCardType\x12\x0c\n\x08PCI_CARD\x10\x00\x12\x0c\n\x08USB_CARD\x10\x01\"\xb5\x01\n\x0c\x43\x41NChannelId\x12\x13\n\x0f\x43HANNEL_ID_ZERO\x10\x00\x12\x12\n\x0e\x43HANNEL_ID_ONE\x10\x01\x12\x12\n\x0e\x43HANNEL_ID_TWO\x10\x02\x12\x14\n\x10\x43HANNEL_ID_THREE\x10\x03\x12\x13\n\x0f\x43HANNEL_ID_FOUR\x10\x04\x12\x13\n\x0f\x43HANNEL_ID_FIVE\x10\x05\x12\x12\n\x0e\x43HANNEL_ID_SIX\x10\x06\x12\x14\n\x10\x43HANNEL_ID_SEVEN\x10\x07\"2\n\x0c\x43\x41NInterface\x12\n\n\x06NATIVE\x10\x00\x12\x0b\n\x07VIRTUAL\x10\x01\x12\t\n\x05SLCAN\x10\x02\"\x7f\n\x08\x42\x41UDRATE\x12\x14\n\x10\x42\x43\x41N_BAUDRATE_1M\x10\x00\x12\x16\n\x12\x42\x43\x41N_BAUDRATE_500K\x10\x01\x12\x16\n\x12\x42\x43\x41N_BAUDRATE_250K\x10\x02\x12\x16\n\x12\x42\x43\x41N_BAUDRATE_150K\x10\x03\x12\x15\n\x11\x42\x43\x41N_BAUDRATE_NUM\x10\x04\x42\x08\n\x06_brandB\x07\n\x05_typeB\r\n\x0b_channel_idB\x0c\n\n_interfaceB\x0c\n\n_num_portsB\x0b\n\t_baudrateb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.msgs.can_card_parameter_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_CANCARDPARAMETER']._serialized_start=50 + _globals['_CANCARDPARAMETER']._serialized_end=970 + _globals['_CANCARDPARAMETER_CANCARDBRAND']._serialized_start=410 + _globals['_CANCARDPARAMETER_CANCARDBRAND']._serialized_end=487 + _globals['_CANCARDPARAMETER_CANCARDTYPE']._serialized_start=489 + _globals['_CANCARDPARAMETER_CANCARDTYPE']._serialized_end=530 + _globals['_CANCARDPARAMETER_CANCHANNELID']._serialized_start=533 + _globals['_CANCARDPARAMETER_CANCHANNELID']._serialized_end=714 + _globals['_CANCARDPARAMETER_CANINTERFACE']._serialized_start=716 + _globals['_CANCARDPARAMETER_CANINTERFACE']._serialized_end=766 + _globals['_CANCARDPARAMETER_BAUDRATE']._serialized_start=768 + _globals['_CANCARDPARAMETER_BAUDRATE']._serialized_end=895 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/msgs/can_card_parameter_pb2.pyi b/src/cmvr/msgs/can_card_parameter_pb2.pyi new file mode 100644 index 0000000..97e06f1 --- /dev/null +++ b/src/cmvr/msgs/can_card_parameter_pb2.pyi @@ -0,0 +1,76 @@ +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class CANCardParameter(_message.Message): + __slots__ = ("brand", "type", "channel_id", "interface", "num_ports", "baudrate") + class CANCardBrand(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + FAKE_CAN: _ClassVar[CANCardParameter.CANCardBrand] + ESD_CAN: _ClassVar[CANCardParameter.CANCardBrand] + SOCKET_CAN_RAW: _ClassVar[CANCardParameter.CANCardBrand] + HERMES_CAN: _ClassVar[CANCardParameter.CANCardBrand] + FAKE_CAN: CANCardParameter.CANCardBrand + ESD_CAN: CANCardParameter.CANCardBrand + SOCKET_CAN_RAW: CANCardParameter.CANCardBrand + HERMES_CAN: CANCardParameter.CANCardBrand + class CANCardType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PCI_CARD: _ClassVar[CANCardParameter.CANCardType] + USB_CARD: _ClassVar[CANCardParameter.CANCardType] + PCI_CARD: CANCardParameter.CANCardType + USB_CARD: CANCardParameter.CANCardType + class CANChannelId(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + CHANNEL_ID_ZERO: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_ONE: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_TWO: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_THREE: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_FOUR: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_FIVE: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_SIX: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_SEVEN: _ClassVar[CANCardParameter.CANChannelId] + CHANNEL_ID_ZERO: CANCardParameter.CANChannelId + CHANNEL_ID_ONE: CANCardParameter.CANChannelId + CHANNEL_ID_TWO: CANCardParameter.CANChannelId + CHANNEL_ID_THREE: CANCardParameter.CANChannelId + CHANNEL_ID_FOUR: CANCardParameter.CANChannelId + CHANNEL_ID_FIVE: CANCardParameter.CANChannelId + CHANNEL_ID_SIX: CANCardParameter.CANChannelId + CHANNEL_ID_SEVEN: CANCardParameter.CANChannelId + class CANInterface(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + NATIVE: _ClassVar[CANCardParameter.CANInterface] + VIRTUAL: _ClassVar[CANCardParameter.CANInterface] + SLCAN: _ClassVar[CANCardParameter.CANInterface] + NATIVE: CANCardParameter.CANInterface + VIRTUAL: CANCardParameter.CANInterface + SLCAN: CANCardParameter.CANInterface + class BAUDRATE(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + BCAN_BAUDRATE_1M: _ClassVar[CANCardParameter.BAUDRATE] + BCAN_BAUDRATE_500K: _ClassVar[CANCardParameter.BAUDRATE] + BCAN_BAUDRATE_250K: _ClassVar[CANCardParameter.BAUDRATE] + BCAN_BAUDRATE_150K: _ClassVar[CANCardParameter.BAUDRATE] + BCAN_BAUDRATE_NUM: _ClassVar[CANCardParameter.BAUDRATE] + BCAN_BAUDRATE_1M: CANCardParameter.BAUDRATE + BCAN_BAUDRATE_500K: CANCardParameter.BAUDRATE + BCAN_BAUDRATE_250K: CANCardParameter.BAUDRATE + BCAN_BAUDRATE_150K: CANCardParameter.BAUDRATE + BCAN_BAUDRATE_NUM: CANCardParameter.BAUDRATE + BRAND_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + CHANNEL_ID_FIELD_NUMBER: _ClassVar[int] + INTERFACE_FIELD_NUMBER: _ClassVar[int] + NUM_PORTS_FIELD_NUMBER: _ClassVar[int] + BAUDRATE_FIELD_NUMBER: _ClassVar[int] + brand: CANCardParameter.CANCardBrand + type: CANCardParameter.CANCardType + channel_id: CANCardParameter.CANChannelId + interface: CANCardParameter.CANInterface + num_ports: int + baudrate: CANCardParameter.BAUDRATE + def __init__(self, brand: _Optional[_Union[CANCardParameter.CANCardBrand, str]] = ..., type: _Optional[_Union[CANCardParameter.CANCardType, str]] = ..., channel_id: _Optional[_Union[CANCardParameter.CANChannelId, str]] = ..., interface: _Optional[_Union[CANCardParameter.CANInterface, str]] = ..., num_ports: _Optional[int] = ..., baudrate: _Optional[_Union[CANCardParameter.BAUDRATE, str]] = ...) -> None: ... diff --git a/src/cmvr/msgs/can_card_parameter_pb2_grpc.py b/src/cmvr/msgs/can_card_parameter_pb2_grpc.py new file mode 100644 index 0000000..dbac691 --- /dev/null +++ b/src/cmvr/msgs/can_card_parameter_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/msgs/can_card_parameter_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/msgs/canopen_pb2.py b/src/cmvr/msgs/canopen_pb2.py new file mode 100644 index 0000000..2efb4b8 --- /dev/null +++ b/src/cmvr/msgs/canopen_pb2.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/msgs/canopen.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/msgs/canopen.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x63mvr/msgs/canopen.proto\x12\tcmvr.msgs\"\x9f\x01\n\x08SdoFrame\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\'\n\x02\x63s\x18\x02 \x01(\x0e\x32\x1b.cmvr.msgs.CommandSpecifier\x12!\n\x05index\x18\x03 \x01(\x0e\x32\x12.cmvr.msgs.ObIndex\x12(\n\tsub_index\x18\x04 \x01(\x0e\x32\x15.cmvr.msgs.ObSubIndex\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\r*\xe8\x01\n\tPdoBaseId\x12\x1b\n\x17PDO_BASE_ID_UNSPECIFIED\x10\x00\x12\x16\n\x11RPDO1_BASE_ID_200\x10\x80\x04\x12\x16\n\x11RPDO2_BASE_ID_300\x10\x80\x06\x12\x16\n\x11RPDO3_BASE_ID_400\x10\x80\x08\x12\x16\n\x11RPDO4_BASE_ID_500\x10\x80\n\x12\x16\n\x11TPDO1_BASE_ID_180\x10\x80\x03\x12\x16\n\x11TPDO2_BASE_ID_280\x10\x80\x05\x12\x16\n\x11TPDO3_BASE_ID_380\x10\x80\x07\x12\x16\n\x11TPDO4_BASE_ID_480\x10\x80\t*\x9d\x01\n\x10TransmissionType\x12\x15\n\x11SYNC_EVENT_DRIVEN\x10\x00\x12\x0f\n\x0bSYNC_CYCLIC\x10\x01\x12\x10\n\x0bREMOTE_SYNC\x10\xfc\x01\x12\x11\n\x0cREMOTE_ASYNC\x10\xfd\x01\x12 \n\x1b\x41SYNC_MANUFACTURER_SPECIFIC\x10\xfe\x01\x12\x1a\n\x15\x41SYNC_DEVICE_SPECIFIC\x10\xff\x01*\xd1\x02\n\x10\x43ommandSpecifier\x12\t\n\x05\x43S_NO\x10\x00\x12\x15\n\x11\x43S_WRITE_ONE_BYTE\x10/\x12\x16\n\x12\x43S_WRITE_TWO_BYTES\x10+\x12\x18\n\x14\x43S_WRITE_THREE_BYTES\x10\'\x12\x17\n\x13\x43S_WRITE_FOUR_BYTES\x10#\x12\x1d\n\x19\x43S_WRITE_SUCCESS_RESPONSE\x10`\x12\x13\n\x0f\x43S_READ_REQUEST\x10@\x12\x1d\n\x19\x43S_READ_RESPONSE_ONE_BYTE\x10O\x12\x1e\n\x1a\x43S_READ_RESPONSE_TWO_BYTES\x10K\x12 \n\x1c\x43S_READ_RESPONSE_THREE_BYTES\x10G\x12\x1f\n\x1b\x43S_READ_RESPONSE_FOUR_BYTES\x10\x43\x12\x1a\n\x15\x43S_EXCEPTION_RESPONSE\x10\x80\x01*\xa1\x01\n\x08NmtState\x12\x14\n\x10NMT_INITIALIZING\x10\x00\x12\x19\n\x15NMT_RESET_APPLICATION\x10\x01\x12\x12\n\x0eNMT_CONNECTING\x10\x02\x12\x11\n\rNMT_PREPARING\x10\x03\x12\x0f\n\x0bNMT_STOPPED\x10\x04\x12\x13\n\x0fNMT_OPERATIONAL\x10\x05\x12\x17\n\x13NMT_PRE_OPERATIONAL\x10\x7f*\xb1\x01\n\nNmtCommand\x12\x1b\n\x17NMT_COMMAND_UNSPECIFIED\x10\x00\x12\x19\n\x15NMT_START_REMOTE_NODE\x10\x01\x12\x18\n\x14NMT_STOP_REMOTE_NODE\x10\x02\x12\x1e\n\x19NMT_ENTER_PRE_OPERATIONAL\x10\x80\x01\x12\x13\n\x0eNMT_RESET_NODE\x10\x81\x01\x12\x1c\n\x17NMT_RESET_COMMUNICATION\x10\x82\x01*\xce\x08\n\x07ObIndex\x12\x0e\n\nINDEX_ZERO\x10\x00\x12\x18\n\x13USER_SAVE_PARA_2000\x10\x80@\x12\x19\n\x14POSITION_OFFSET_2008\x10\x88@\x12\x15\n\x0f\x45RROR_CODE_6007\x10\x87\xc0\x01\x12\x15\n\x0f\x45RROR_CODE_603F\x10\xbf\xc0\x01\x12\x17\n\x11\x43ONTROL_WORD_6040\x10\xc0\xc0\x01\x12\x16\n\x10STATUS_WORD_6041\x10\xc1\xc0\x01\x12\x19\n\x13OPERATION_MODE_6060\x10\xe0\xc0\x01\x12\x17\n\x11MODE_DISPLAY_6061\x10\xe1\xc0\x01\x12\x1a\n\x14\x41\x43TUAL_POSITION_6064\x10\xe4\xc0\x01\x12\x17\n\x11\x41\x43TUAL_SPEED_606C\x10\xec\xc0\x01\x12\x19\n\x13\x41\x43TUAL_CURRENT_6078\x10\xf8\xc0\x01\x12\x18\n\x12TARGET_TORQUE_6071\x10\xf1\xc0\x01\x12\x15\n\x0fMAX_TORQUE_6072\x10\xf2\xc0\x01\x12\x18\n\x12\x44\x45MAND_TORQUE_6074\x10\xf4\xc0\x01\x12\x1a\n\x14TARGET_POSITION_607A\x10\xfa\xc0\x01\x12\"\n\x1cSOFTWARE_POSITION_LIMIT_607D\x10\xfd\xc0\x01\x12\x14\n\x0eMAX_SPEED_607F\x10\xff\xc0\x01\x12\x18\n\x12PROFILE_SPEED_6081\x10\x81\xc1\x01\x12\x1f\n\x19PROFILE_ACCELERATION_6083\x10\x83\xc1\x01\x12\x1f\n\x19PROFILE_DECELERATION_6084\x10\x84\xc1\x01\x12\x1b\n\x15\x43URRENT_LOOP_PID_60F6\x10\xf6\xc1\x01\x12\x19\n\x13SPEED_LOOP_PID_60F9\x10\xf9\xc1\x01\x12\x1c\n\x16POSITION_LOOP_PID_60FB\x10\xfb\xc1\x01\x12\x17\n\x11TARGET_SPEED_60FF\x10\xff\xc1\x01\x12\x1c\n\x16QUICK_STOP_OPTION_605A\x10\xda\xc0\x01\x12\x1b\n\x15QUICK_STOP_DECEL_6085\x10\x85\xc1\x01\x12\x14\n\x0fRPDO1_COMM_1400\x10\x80(\x12\x14\n\x0fRPDO2_COMM_1401\x10\x81(\x12\x14\n\x0fRPDO3_COMM_1402\x10\x82(\x12\x14\n\x0fRPDO4_COMM_1403\x10\x83(\x12\x14\n\x0fTPDO1_COMM_1800\x10\x80\x30\x12\x14\n\x0fTPDO2_COMM_1801\x10\x81\x30\x12\x14\n\x0fTPDO3_COMM_1802\x10\x82\x30\x12\x14\n\x0fTPDO4_COMM_1803\x10\x83\x30\x12\x13\n\x0eRPDO1_MAP_1600\x10\x80,\x12\x13\n\x0eRPDO2_MAP_1601\x10\x81,\x12\x13\n\x0eRPDO3_MAP_1602\x10\x82,\x12\x13\n\x0eRPDO4_MAP_1603\x10\x83,\x12\x13\n\x0eTPDO1_MAP_1A00\x10\x80\x34\x12\x13\n\x0eTPDO2_MAP_1A01\x10\x81\x34\x12\x13\n\x0eTPDO3_MAP_1A02\x10\x82\x34\x12\x13\n\x0eTPDO4_MAP_1A03\x10\x83\x34\x12\x1c\n\x17PRODUCER_HEARTBEAT_TIME\x10\x97 *\x94\x01\n\nObSubIndex\x12\x0f\n\x0bSUB_INDEX_0\x10\x00\x12\x0f\n\x0bSUB_INDEX_1\x10\x01\x12\x0f\n\x0bSUB_INDEX_2\x10\x02\x12\x0f\n\x0bSUB_INDEX_3\x10\x03\x12\x0f\n\x0bSUB_INDEX_4\x10\x04\x12\x0f\n\x0bSUB_INDEX_5\x10\x05\x12\x0f\n\x0bSUB_INDEX_6\x10\x06\x12\x0f\n\x0bSUB_INDEX_7\x10\x07\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.msgs.canopen_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_PDOBASEID']._serialized_start=201 + _globals['_PDOBASEID']._serialized_end=433 + _globals['_TRANSMISSIONTYPE']._serialized_start=436 + _globals['_TRANSMISSIONTYPE']._serialized_end=593 + _globals['_COMMANDSPECIFIER']._serialized_start=596 + _globals['_COMMANDSPECIFIER']._serialized_end=933 + _globals['_NMTSTATE']._serialized_start=936 + _globals['_NMTSTATE']._serialized_end=1097 + _globals['_NMTCOMMAND']._serialized_start=1100 + _globals['_NMTCOMMAND']._serialized_end=1277 + _globals['_OBINDEX']._serialized_start=1280 + _globals['_OBINDEX']._serialized_end=2382 + _globals['_OBSUBINDEX']._serialized_start=2385 + _globals['_OBSUBINDEX']._serialized_end=2533 + _globals['_SDOFRAME']._serialized_start=39 + _globals['_SDOFRAME']._serialized_end=198 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/msgs/canopen_pb2.pyi b/src/cmvr/msgs/canopen_pb2.pyi new file mode 100644 index 0000000..6e64f17 --- /dev/null +++ b/src/cmvr/msgs/canopen_pb2.pyi @@ -0,0 +1,225 @@ +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class PdoBaseId(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PDO_BASE_ID_UNSPECIFIED: _ClassVar[PdoBaseId] + RPDO1_BASE_ID_200: _ClassVar[PdoBaseId] + RPDO2_BASE_ID_300: _ClassVar[PdoBaseId] + RPDO3_BASE_ID_400: _ClassVar[PdoBaseId] + RPDO4_BASE_ID_500: _ClassVar[PdoBaseId] + TPDO1_BASE_ID_180: _ClassVar[PdoBaseId] + TPDO2_BASE_ID_280: _ClassVar[PdoBaseId] + TPDO3_BASE_ID_380: _ClassVar[PdoBaseId] + TPDO4_BASE_ID_480: _ClassVar[PdoBaseId] + +class TransmissionType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + SYNC_EVENT_DRIVEN: _ClassVar[TransmissionType] + SYNC_CYCLIC: _ClassVar[TransmissionType] + REMOTE_SYNC: _ClassVar[TransmissionType] + REMOTE_ASYNC: _ClassVar[TransmissionType] + ASYNC_MANUFACTURER_SPECIFIC: _ClassVar[TransmissionType] + ASYNC_DEVICE_SPECIFIC: _ClassVar[TransmissionType] + +class CommandSpecifier(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + CS_NO: _ClassVar[CommandSpecifier] + CS_WRITE_ONE_BYTE: _ClassVar[CommandSpecifier] + CS_WRITE_TWO_BYTES: _ClassVar[CommandSpecifier] + CS_WRITE_THREE_BYTES: _ClassVar[CommandSpecifier] + CS_WRITE_FOUR_BYTES: _ClassVar[CommandSpecifier] + CS_WRITE_SUCCESS_RESPONSE: _ClassVar[CommandSpecifier] + CS_READ_REQUEST: _ClassVar[CommandSpecifier] + CS_READ_RESPONSE_ONE_BYTE: _ClassVar[CommandSpecifier] + CS_READ_RESPONSE_TWO_BYTES: _ClassVar[CommandSpecifier] + CS_READ_RESPONSE_THREE_BYTES: _ClassVar[CommandSpecifier] + CS_READ_RESPONSE_FOUR_BYTES: _ClassVar[CommandSpecifier] + CS_EXCEPTION_RESPONSE: _ClassVar[CommandSpecifier] + +class NmtState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + NMT_INITIALIZING: _ClassVar[NmtState] + NMT_RESET_APPLICATION: _ClassVar[NmtState] + NMT_CONNECTING: _ClassVar[NmtState] + NMT_PREPARING: _ClassVar[NmtState] + NMT_STOPPED: _ClassVar[NmtState] + NMT_OPERATIONAL: _ClassVar[NmtState] + NMT_PRE_OPERATIONAL: _ClassVar[NmtState] + +class NmtCommand(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + NMT_COMMAND_UNSPECIFIED: _ClassVar[NmtCommand] + NMT_START_REMOTE_NODE: _ClassVar[NmtCommand] + NMT_STOP_REMOTE_NODE: _ClassVar[NmtCommand] + NMT_ENTER_PRE_OPERATIONAL: _ClassVar[NmtCommand] + NMT_RESET_NODE: _ClassVar[NmtCommand] + NMT_RESET_COMMUNICATION: _ClassVar[NmtCommand] + +class ObIndex(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + INDEX_ZERO: _ClassVar[ObIndex] + USER_SAVE_PARA_2000: _ClassVar[ObIndex] + POSITION_OFFSET_2008: _ClassVar[ObIndex] + ERROR_CODE_6007: _ClassVar[ObIndex] + ERROR_CODE_603F: _ClassVar[ObIndex] + CONTROL_WORD_6040: _ClassVar[ObIndex] + STATUS_WORD_6041: _ClassVar[ObIndex] + OPERATION_MODE_6060: _ClassVar[ObIndex] + MODE_DISPLAY_6061: _ClassVar[ObIndex] + ACTUAL_POSITION_6064: _ClassVar[ObIndex] + ACTUAL_SPEED_606C: _ClassVar[ObIndex] + ACTUAL_CURRENT_6078: _ClassVar[ObIndex] + TARGET_TORQUE_6071: _ClassVar[ObIndex] + MAX_TORQUE_6072: _ClassVar[ObIndex] + DEMAND_TORQUE_6074: _ClassVar[ObIndex] + TARGET_POSITION_607A: _ClassVar[ObIndex] + SOFTWARE_POSITION_LIMIT_607D: _ClassVar[ObIndex] + MAX_SPEED_607F: _ClassVar[ObIndex] + PROFILE_SPEED_6081: _ClassVar[ObIndex] + PROFILE_ACCELERATION_6083: _ClassVar[ObIndex] + PROFILE_DECELERATION_6084: _ClassVar[ObIndex] + CURRENT_LOOP_PID_60F6: _ClassVar[ObIndex] + SPEED_LOOP_PID_60F9: _ClassVar[ObIndex] + POSITION_LOOP_PID_60FB: _ClassVar[ObIndex] + TARGET_SPEED_60FF: _ClassVar[ObIndex] + QUICK_STOP_OPTION_605A: _ClassVar[ObIndex] + QUICK_STOP_DECEL_6085: _ClassVar[ObIndex] + RPDO1_COMM_1400: _ClassVar[ObIndex] + RPDO2_COMM_1401: _ClassVar[ObIndex] + RPDO3_COMM_1402: _ClassVar[ObIndex] + RPDO4_COMM_1403: _ClassVar[ObIndex] + TPDO1_COMM_1800: _ClassVar[ObIndex] + TPDO2_COMM_1801: _ClassVar[ObIndex] + TPDO3_COMM_1802: _ClassVar[ObIndex] + TPDO4_COMM_1803: _ClassVar[ObIndex] + RPDO1_MAP_1600: _ClassVar[ObIndex] + RPDO2_MAP_1601: _ClassVar[ObIndex] + RPDO3_MAP_1602: _ClassVar[ObIndex] + RPDO4_MAP_1603: _ClassVar[ObIndex] + TPDO1_MAP_1A00: _ClassVar[ObIndex] + TPDO2_MAP_1A01: _ClassVar[ObIndex] + TPDO3_MAP_1A02: _ClassVar[ObIndex] + TPDO4_MAP_1A03: _ClassVar[ObIndex] + PRODUCER_HEARTBEAT_TIME: _ClassVar[ObIndex] + +class ObSubIndex(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + SUB_INDEX_0: _ClassVar[ObSubIndex] + SUB_INDEX_1: _ClassVar[ObSubIndex] + SUB_INDEX_2: _ClassVar[ObSubIndex] + SUB_INDEX_3: _ClassVar[ObSubIndex] + SUB_INDEX_4: _ClassVar[ObSubIndex] + SUB_INDEX_5: _ClassVar[ObSubIndex] + SUB_INDEX_6: _ClassVar[ObSubIndex] + SUB_INDEX_7: _ClassVar[ObSubIndex] +PDO_BASE_ID_UNSPECIFIED: PdoBaseId +RPDO1_BASE_ID_200: PdoBaseId +RPDO2_BASE_ID_300: PdoBaseId +RPDO3_BASE_ID_400: PdoBaseId +RPDO4_BASE_ID_500: PdoBaseId +TPDO1_BASE_ID_180: PdoBaseId +TPDO2_BASE_ID_280: PdoBaseId +TPDO3_BASE_ID_380: PdoBaseId +TPDO4_BASE_ID_480: PdoBaseId +SYNC_EVENT_DRIVEN: TransmissionType +SYNC_CYCLIC: TransmissionType +REMOTE_SYNC: TransmissionType +REMOTE_ASYNC: TransmissionType +ASYNC_MANUFACTURER_SPECIFIC: TransmissionType +ASYNC_DEVICE_SPECIFIC: TransmissionType +CS_NO: CommandSpecifier +CS_WRITE_ONE_BYTE: CommandSpecifier +CS_WRITE_TWO_BYTES: CommandSpecifier +CS_WRITE_THREE_BYTES: CommandSpecifier +CS_WRITE_FOUR_BYTES: CommandSpecifier +CS_WRITE_SUCCESS_RESPONSE: CommandSpecifier +CS_READ_REQUEST: CommandSpecifier +CS_READ_RESPONSE_ONE_BYTE: CommandSpecifier +CS_READ_RESPONSE_TWO_BYTES: CommandSpecifier +CS_READ_RESPONSE_THREE_BYTES: CommandSpecifier +CS_READ_RESPONSE_FOUR_BYTES: CommandSpecifier +CS_EXCEPTION_RESPONSE: CommandSpecifier +NMT_INITIALIZING: NmtState +NMT_RESET_APPLICATION: NmtState +NMT_CONNECTING: NmtState +NMT_PREPARING: NmtState +NMT_STOPPED: NmtState +NMT_OPERATIONAL: NmtState +NMT_PRE_OPERATIONAL: NmtState +NMT_COMMAND_UNSPECIFIED: NmtCommand +NMT_START_REMOTE_NODE: NmtCommand +NMT_STOP_REMOTE_NODE: NmtCommand +NMT_ENTER_PRE_OPERATIONAL: NmtCommand +NMT_RESET_NODE: NmtCommand +NMT_RESET_COMMUNICATION: NmtCommand +INDEX_ZERO: ObIndex +USER_SAVE_PARA_2000: ObIndex +POSITION_OFFSET_2008: ObIndex +ERROR_CODE_6007: ObIndex +ERROR_CODE_603F: ObIndex +CONTROL_WORD_6040: ObIndex +STATUS_WORD_6041: ObIndex +OPERATION_MODE_6060: ObIndex +MODE_DISPLAY_6061: ObIndex +ACTUAL_POSITION_6064: ObIndex +ACTUAL_SPEED_606C: ObIndex +ACTUAL_CURRENT_6078: ObIndex +TARGET_TORQUE_6071: ObIndex +MAX_TORQUE_6072: ObIndex +DEMAND_TORQUE_6074: ObIndex +TARGET_POSITION_607A: ObIndex +SOFTWARE_POSITION_LIMIT_607D: ObIndex +MAX_SPEED_607F: ObIndex +PROFILE_SPEED_6081: ObIndex +PROFILE_ACCELERATION_6083: ObIndex +PROFILE_DECELERATION_6084: ObIndex +CURRENT_LOOP_PID_60F6: ObIndex +SPEED_LOOP_PID_60F9: ObIndex +POSITION_LOOP_PID_60FB: ObIndex +TARGET_SPEED_60FF: ObIndex +QUICK_STOP_OPTION_605A: ObIndex +QUICK_STOP_DECEL_6085: ObIndex +RPDO1_COMM_1400: ObIndex +RPDO2_COMM_1401: ObIndex +RPDO3_COMM_1402: ObIndex +RPDO4_COMM_1403: ObIndex +TPDO1_COMM_1800: ObIndex +TPDO2_COMM_1801: ObIndex +TPDO3_COMM_1802: ObIndex +TPDO4_COMM_1803: ObIndex +RPDO1_MAP_1600: ObIndex +RPDO2_MAP_1601: ObIndex +RPDO3_MAP_1602: ObIndex +RPDO4_MAP_1603: ObIndex +TPDO1_MAP_1A00: ObIndex +TPDO2_MAP_1A01: ObIndex +TPDO3_MAP_1A02: ObIndex +TPDO4_MAP_1A03: ObIndex +PRODUCER_HEARTBEAT_TIME: ObIndex +SUB_INDEX_0: ObSubIndex +SUB_INDEX_1: ObSubIndex +SUB_INDEX_2: ObSubIndex +SUB_INDEX_3: ObSubIndex +SUB_INDEX_4: ObSubIndex +SUB_INDEX_5: ObSubIndex +SUB_INDEX_6: ObSubIndex +SUB_INDEX_7: ObSubIndex + +class SdoFrame(_message.Message): + __slots__ = ("node_id", "cs", "index", "sub_index", "data") + NODE_ID_FIELD_NUMBER: _ClassVar[int] + CS_FIELD_NUMBER: _ClassVar[int] + INDEX_FIELD_NUMBER: _ClassVar[int] + SUB_INDEX_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + node_id: int + cs: CommandSpecifier + index: ObIndex + sub_index: ObSubIndex + data: int + def __init__(self, node_id: _Optional[int] = ..., cs: _Optional[_Union[CommandSpecifier, str]] = ..., index: _Optional[_Union[ObIndex, str]] = ..., sub_index: _Optional[_Union[ObSubIndex, str]] = ..., data: _Optional[int] = ...) -> None: ... diff --git a/src/cmvr/msgs/canopen_pb2_grpc.py b/src/cmvr/msgs/canopen_pb2_grpc.py new file mode 100644 index 0000000..08208c9 --- /dev/null +++ b/src/cmvr/msgs/canopen_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/msgs/canopen_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/msgs/error_code_pb2.py b/src/cmvr/msgs/error_code_pb2.py new file mode 100644 index 0000000..5ca814d --- /dev/null +++ b/src/cmvr/msgs/error_code_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/msgs/error_code.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/msgs/error_code.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63mvr/msgs/error_code.proto\x12\tcmvr.msgs\"A\n\x08StatusPb\x12(\n\nerror_code\x18\x01 \x01(\x0e\x32\x14.cmvr.msgs.ErrorCode\x12\x0b\n\x03msg\x18\x02 \x01(\t*\x80\x02\n\tErrorCode\x12\x06\n\x02OK\x10\x00\x12\x11\n\x0c\x43\x41NBUS_ERROR\x10\xd0\x0f\x12\x1a\n\x15\x43\x41N_CLIENT_ERROR_BASE\x10\xb4\x10\x12(\n#CAN_CLIENT_ERROR_OPEN_DEVICE_FAILED\x10\xb5\x10\x12\x1f\n\x1a\x43\x41N_CLIENT_ERROR_FRAME_NUM\x10\xb6\x10\x12!\n\x1c\x43\x41N_CLIENT_ERROR_SEND_FAILED\x10\xb7\x10\x12!\n\x1c\x43\x41N_CLIENT_ERROR_RECV_FAILED\x10\xb8\x10\x12\x10\n\x0bMOTOR_ERROR\x10\xb8\x17\x12\x19\n\x14MOTOR_ERROR_SET_ZERO\x10\xb9\x17\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.msgs.error_code_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_ERRORCODE']._serialized_start=109 + _globals['_ERRORCODE']._serialized_end=365 + _globals['_STATUSPB']._serialized_start=41 + _globals['_STATUSPB']._serialized_end=106 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/msgs/error_code_pb2.pyi b/src/cmvr/msgs/error_code_pb2.pyi new file mode 100644 index 0000000..54166c3 --- /dev/null +++ b/src/cmvr/msgs/error_code_pb2.pyi @@ -0,0 +1,35 @@ +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ErrorCode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + OK: _ClassVar[ErrorCode] + CANBUS_ERROR: _ClassVar[ErrorCode] + CAN_CLIENT_ERROR_BASE: _ClassVar[ErrorCode] + CAN_CLIENT_ERROR_OPEN_DEVICE_FAILED: _ClassVar[ErrorCode] + CAN_CLIENT_ERROR_FRAME_NUM: _ClassVar[ErrorCode] + CAN_CLIENT_ERROR_SEND_FAILED: _ClassVar[ErrorCode] + CAN_CLIENT_ERROR_RECV_FAILED: _ClassVar[ErrorCode] + MOTOR_ERROR: _ClassVar[ErrorCode] + MOTOR_ERROR_SET_ZERO: _ClassVar[ErrorCode] +OK: ErrorCode +CANBUS_ERROR: ErrorCode +CAN_CLIENT_ERROR_BASE: ErrorCode +CAN_CLIENT_ERROR_OPEN_DEVICE_FAILED: ErrorCode +CAN_CLIENT_ERROR_FRAME_NUM: ErrorCode +CAN_CLIENT_ERROR_SEND_FAILED: ErrorCode +CAN_CLIENT_ERROR_RECV_FAILED: ErrorCode +MOTOR_ERROR: ErrorCode +MOTOR_ERROR_SET_ZERO: ErrorCode + +class StatusPb(_message.Message): + __slots__ = ("error_code", "msg") + ERROR_CODE_FIELD_NUMBER: _ClassVar[int] + MSG_FIELD_NUMBER: _ClassVar[int] + error_code: ErrorCode + msg: str + def __init__(self, error_code: _Optional[_Union[ErrorCode, str]] = ..., msg: _Optional[str] = ...) -> None: ... diff --git a/src/cmvr/msgs/error_code_pb2_grpc.py b/src/cmvr/msgs/error_code_pb2_grpc.py new file mode 100644 index 0000000..36bd44d --- /dev/null +++ b/src/cmvr/msgs/error_code_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/msgs/error_code_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/msgs/motor_pb2.py b/src/cmvr/msgs/motor_pb2.py new file mode 100644 index 0000000..fc7fc95 --- /dev/null +++ b/src/cmvr/msgs/motor_pb2.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/msgs/motor.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/msgs/motor.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.msgs import canopen_pb2 as cmvr_dot_msgs_dot_canopen__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x63mvr/msgs/motor.proto\x12\tcmvr.msgs\x1a\x17\x63mvr/msgs/canopen.proto\"\x94\x08\n\x0bMotorStatus\x12$\n\x08run_mode\x18\x01 \x01(\x0e\x32\x12.cmvr.msgs.RunMode\x12\x0f\n\x07\x63urrent\x18\x02 \x01(\x05\x12\x16\n\x0etarget_current\x18\x03 \x01(\x05\x12\r\n\x05speed\x18\x04 \x01(\x05\x12\x14\n\x0ctarget_speed\x18\x05 \x01(\x05\x12\x10\n\x08position\x18\x06 \x01(\x05\x12\x17\n\x0ftarget_position\x18\x07 \x01(\x05\x12\x13\n\x0b\x65rror_state\x18\x08 \x01(\r\x12\x10\n\x08speed_kp\x18\t \x01(\x05\x12\x10\n\x08speed_ki\x18\n \x01(\x05\x12\x10\n\x08speed_kd\x18\x0b \x01(\x05\x12\x13\n\x0bposition_kp\x18\x0c \x01(\x05\x12\x13\n\x0bposition_ki\x18\r \x01(\x05\x12\x13\n\x0bposition_kd\x18\x0e \x01(\x05\x12\x13\n\x0b\x62us_voltage\x18\x0f \x01(\x05\x12\x17\n\x0fmax_abs_current\x18\x10 \x01(\x05\x12\x17\n\x0fmax_pos_current\x18\x11 \x01(\x05\x12\x17\n\x0fmin_neg_current\x18\x12 \x01(\x05\x12\x15\n\rmax_pos_accel\x18\x13 \x01(\x05\x12\x15\n\rmin_neg_accel\x18\x14 \x01(\x05\x12\x18\n\x10max_pos_velocity\x18\x15 \x01(\x05\x12\x18\n\x10min_neg_velocity\x18\x16 \x01(\x05\x12\x18\n\x10max_pos_position\x18\x17 \x01(\x05\x12\x18\n\x10min_neg_position\x18\x18 \x01(\x05\x12\x12\n\nmotor_temp\x18\x19 \x01(\x05\x12\x12\n\nboard_temp\x18\x1a \x01(\x05\x12\x12\n\ncurrent_kp\x18\x1b \x01(\x05\x12\x12\n\ncurrent_ki\x18\x1c \x01(\x05\x12\x12\n\ncurrent_kd\x18\x1d \x01(\x05\x12\x12\n\nmotor_type\x18\x1e \x01(\x05\x12\x15\n\rmotor_version\x18\x1f \x01(\x05\x12\x18\n\x10software_version\x18 \x01(\x05\x12\x17\n\x0fposition_offset\x18! \x01(\x05\x12\x10\n\x08\x63sp_data\x18\" \x01(\x0c\x12\x17\n\x0f\x65ncoder_voltage\x18# \x01(\x05\x12\x15\n\rencoder_state\x18$ \x01(\x05\x12\x19\n\x11overvoltage_limit\x18% \x01(\x05\x12\x1a\n\x12undervoltage_limit\x18& \x01(\x05\x12\x16\n\x0e\x63oil_over_temp\x18\' \x01(\x05\x12\x18\n\x10\x64river_over_temp\x18( \x01(\x05\x12)\n\x0csdo_response\x18) \x01(\x0b\x32\x13.cmvr.msgs.SdoFrame\x12&\n\tnmt_state\x18* \x01(\x0e\x32\x13.cmvr.msgs.NmtState\x12\x11\n\tctrl_word\x18+ \x01(\r\x12\x13\n\x0bstatus_word\x18, \x01(\r*\xae\x02\n\x07RunMode\x12\x18\n\x14RUN_MODE_UNSPECIFIED\x10\x00\x12\x1d\n\x19RUN_MODE_PROFILE_POSITION\x10\x01\x12\x15\n\x11RUN_MODE_VELOCITY\x10\x02\x12\x1d\n\x19RUN_MODE_PROFILE_VELOCITY\x10\x03\x12\x13\n\x0fRUN_MODE_TORQUE\x10\x04\x12\x13\n\x0fRUN_MODE_HOMING\x10\x05\x12\"\n\x1eRUN_MODE_INTERPOLATED_POSITION\x10\x07\x12!\n\x1dRUN_MODE_CYCLIC_SYNC_POSITION\x10\x08\x12!\n\x1dRUN_MODE_CYCLIC_SYNC_VELOCITY\x10\t\x12 \n\x1cRUN_MODE_CYCLIC_SYNC_CURRENT\x10\nb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.msgs.motor_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_RUNMODE']._serialized_start=1109 + _globals['_RUNMODE']._serialized_end=1411 + _globals['_MOTORSTATUS']._serialized_start=62 + _globals['_MOTORSTATUS']._serialized_end=1106 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/msgs/motor_pb2.pyi b/src/cmvr/msgs/motor_pb2.pyi new file mode 100644 index 0000000..23cae5a --- /dev/null +++ b/src/cmvr/msgs/motor_pb2.pyi @@ -0,0 +1,123 @@ +from cmvr.msgs import canopen_pb2 as _canopen_pb2 +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class RunMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + RUN_MODE_UNSPECIFIED: _ClassVar[RunMode] + RUN_MODE_PROFILE_POSITION: _ClassVar[RunMode] + RUN_MODE_VELOCITY: _ClassVar[RunMode] + RUN_MODE_PROFILE_VELOCITY: _ClassVar[RunMode] + RUN_MODE_TORQUE: _ClassVar[RunMode] + RUN_MODE_HOMING: _ClassVar[RunMode] + RUN_MODE_INTERPOLATED_POSITION: _ClassVar[RunMode] + RUN_MODE_CYCLIC_SYNC_POSITION: _ClassVar[RunMode] + RUN_MODE_CYCLIC_SYNC_VELOCITY: _ClassVar[RunMode] + RUN_MODE_CYCLIC_SYNC_CURRENT: _ClassVar[RunMode] +RUN_MODE_UNSPECIFIED: RunMode +RUN_MODE_PROFILE_POSITION: RunMode +RUN_MODE_VELOCITY: RunMode +RUN_MODE_PROFILE_VELOCITY: RunMode +RUN_MODE_TORQUE: RunMode +RUN_MODE_HOMING: RunMode +RUN_MODE_INTERPOLATED_POSITION: RunMode +RUN_MODE_CYCLIC_SYNC_POSITION: RunMode +RUN_MODE_CYCLIC_SYNC_VELOCITY: RunMode +RUN_MODE_CYCLIC_SYNC_CURRENT: RunMode + +class MotorStatus(_message.Message): + __slots__ = ("run_mode", "current", "target_current", "speed", "target_speed", "position", "target_position", "error_state", "speed_kp", "speed_ki", "speed_kd", "position_kp", "position_ki", "position_kd", "bus_voltage", "max_abs_current", "max_pos_current", "min_neg_current", "max_pos_accel", "min_neg_accel", "max_pos_velocity", "min_neg_velocity", "max_pos_position", "min_neg_position", "motor_temp", "board_temp", "current_kp", "current_ki", "current_kd", "motor_type", "motor_version", "software_version", "position_offset", "csp_data", "encoder_voltage", "encoder_state", "overvoltage_limit", "undervoltage_limit", "coil_over_temp", "driver_over_temp", "sdo_response", "nmt_state", "ctrl_word", "status_word") + RUN_MODE_FIELD_NUMBER: _ClassVar[int] + CURRENT_FIELD_NUMBER: _ClassVar[int] + TARGET_CURRENT_FIELD_NUMBER: _ClassVar[int] + SPEED_FIELD_NUMBER: _ClassVar[int] + TARGET_SPEED_FIELD_NUMBER: _ClassVar[int] + POSITION_FIELD_NUMBER: _ClassVar[int] + TARGET_POSITION_FIELD_NUMBER: _ClassVar[int] + ERROR_STATE_FIELD_NUMBER: _ClassVar[int] + SPEED_KP_FIELD_NUMBER: _ClassVar[int] + SPEED_KI_FIELD_NUMBER: _ClassVar[int] + SPEED_KD_FIELD_NUMBER: _ClassVar[int] + POSITION_KP_FIELD_NUMBER: _ClassVar[int] + POSITION_KI_FIELD_NUMBER: _ClassVar[int] + POSITION_KD_FIELD_NUMBER: _ClassVar[int] + BUS_VOLTAGE_FIELD_NUMBER: _ClassVar[int] + MAX_ABS_CURRENT_FIELD_NUMBER: _ClassVar[int] + MAX_POS_CURRENT_FIELD_NUMBER: _ClassVar[int] + MIN_NEG_CURRENT_FIELD_NUMBER: _ClassVar[int] + MAX_POS_ACCEL_FIELD_NUMBER: _ClassVar[int] + MIN_NEG_ACCEL_FIELD_NUMBER: _ClassVar[int] + MAX_POS_VELOCITY_FIELD_NUMBER: _ClassVar[int] + MIN_NEG_VELOCITY_FIELD_NUMBER: _ClassVar[int] + MAX_POS_POSITION_FIELD_NUMBER: _ClassVar[int] + MIN_NEG_POSITION_FIELD_NUMBER: _ClassVar[int] + MOTOR_TEMP_FIELD_NUMBER: _ClassVar[int] + BOARD_TEMP_FIELD_NUMBER: _ClassVar[int] + CURRENT_KP_FIELD_NUMBER: _ClassVar[int] + CURRENT_KI_FIELD_NUMBER: _ClassVar[int] + CURRENT_KD_FIELD_NUMBER: _ClassVar[int] + MOTOR_TYPE_FIELD_NUMBER: _ClassVar[int] + MOTOR_VERSION_FIELD_NUMBER: _ClassVar[int] + SOFTWARE_VERSION_FIELD_NUMBER: _ClassVar[int] + POSITION_OFFSET_FIELD_NUMBER: _ClassVar[int] + CSP_DATA_FIELD_NUMBER: _ClassVar[int] + ENCODER_VOLTAGE_FIELD_NUMBER: _ClassVar[int] + ENCODER_STATE_FIELD_NUMBER: _ClassVar[int] + OVERVOLTAGE_LIMIT_FIELD_NUMBER: _ClassVar[int] + UNDERVOLTAGE_LIMIT_FIELD_NUMBER: _ClassVar[int] + COIL_OVER_TEMP_FIELD_NUMBER: _ClassVar[int] + DRIVER_OVER_TEMP_FIELD_NUMBER: _ClassVar[int] + SDO_RESPONSE_FIELD_NUMBER: _ClassVar[int] + NMT_STATE_FIELD_NUMBER: _ClassVar[int] + CTRL_WORD_FIELD_NUMBER: _ClassVar[int] + STATUS_WORD_FIELD_NUMBER: _ClassVar[int] + run_mode: RunMode + current: int + target_current: int + speed: int + target_speed: int + position: int + target_position: int + error_state: int + speed_kp: int + speed_ki: int + speed_kd: int + position_kp: int + position_ki: int + position_kd: int + bus_voltage: int + max_abs_current: int + max_pos_current: int + min_neg_current: int + max_pos_accel: int + min_neg_accel: int + max_pos_velocity: int + min_neg_velocity: int + max_pos_position: int + min_neg_position: int + motor_temp: int + board_temp: int + current_kp: int + current_ki: int + current_kd: int + motor_type: int + motor_version: int + software_version: int + position_offset: int + csp_data: bytes + encoder_voltage: int + encoder_state: int + overvoltage_limit: int + undervoltage_limit: int + coil_over_temp: int + driver_over_temp: int + sdo_response: _canopen_pb2.SdoFrame + nmt_state: _canopen_pb2.NmtState + ctrl_word: int + status_word: int + def __init__(self, run_mode: _Optional[_Union[RunMode, str]] = ..., current: _Optional[int] = ..., target_current: _Optional[int] = ..., speed: _Optional[int] = ..., target_speed: _Optional[int] = ..., position: _Optional[int] = ..., target_position: _Optional[int] = ..., error_state: _Optional[int] = ..., speed_kp: _Optional[int] = ..., speed_ki: _Optional[int] = ..., speed_kd: _Optional[int] = ..., position_kp: _Optional[int] = ..., position_ki: _Optional[int] = ..., position_kd: _Optional[int] = ..., bus_voltage: _Optional[int] = ..., max_abs_current: _Optional[int] = ..., max_pos_current: _Optional[int] = ..., min_neg_current: _Optional[int] = ..., max_pos_accel: _Optional[int] = ..., min_neg_accel: _Optional[int] = ..., max_pos_velocity: _Optional[int] = ..., min_neg_velocity: _Optional[int] = ..., max_pos_position: _Optional[int] = ..., min_neg_position: _Optional[int] = ..., motor_temp: _Optional[int] = ..., board_temp: _Optional[int] = ..., current_kp: _Optional[int] = ..., current_ki: _Optional[int] = ..., current_kd: _Optional[int] = ..., motor_type: _Optional[int] = ..., motor_version: _Optional[int] = ..., software_version: _Optional[int] = ..., position_offset: _Optional[int] = ..., csp_data: _Optional[bytes] = ..., encoder_voltage: _Optional[int] = ..., encoder_state: _Optional[int] = ..., overvoltage_limit: _Optional[int] = ..., undervoltage_limit: _Optional[int] = ..., coil_over_temp: _Optional[int] = ..., driver_over_temp: _Optional[int] = ..., sdo_response: _Optional[_Union[_canopen_pb2.SdoFrame, _Mapping]] = ..., nmt_state: _Optional[_Union[_canopen_pb2.NmtState, str]] = ..., ctrl_word: _Optional[int] = ..., status_word: _Optional[int] = ...) -> None: ... diff --git a/src/cmvr/msgs/motor_pb2_grpc.py b/src/cmvr/msgs/motor_pb2_grpc.py new file mode 100644 index 0000000..e54659b --- /dev/null +++ b/src/cmvr/msgs/motor_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/msgs/motor_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr/msgs/robot_detail_pb2.py b/src/cmvr/msgs/robot_detail_pb2.py new file mode 100644 index 0000000..071df18 --- /dev/null +++ b/src/cmvr/msgs/robot_detail_pb2.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: cmvr/msgs/robot_detail.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'cmvr/msgs/robot_detail.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from cmvr.msgs import canopen_pb2 as cmvr_dot_msgs_dot_canopen__pb2 +from cmvr.msgs import motor_pb2 as cmvr_dot_msgs_dot_motor__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x63mvr/msgs/robot_detail.proto\x12\tcmvr.msgs\x1a\x17\x63mvr/msgs/canopen.proto\x1a\x15\x63mvr/msgs/motor.proto\"\x88\x01\n\x0bRobotDetail\x12\x32\n\x06motors\x18\x01 \x03(\x0b\x32\".cmvr.msgs.RobotDetail.MotorsEntry\x1a\x45\n\x0bMotorsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.cmvr.msgs.MotorStatus:\x02\x38\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.msgs.robot_detail_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_ROBOTDETAIL_MOTORSENTRY']._loaded_options = None + _globals['_ROBOTDETAIL_MOTORSENTRY']._serialized_options = b'8\001' + _globals['_ROBOTDETAIL']._serialized_start=92 + _globals['_ROBOTDETAIL']._serialized_end=228 + _globals['_ROBOTDETAIL_MOTORSENTRY']._serialized_start=159 + _globals['_ROBOTDETAIL_MOTORSENTRY']._serialized_end=228 +# @@protoc_insertion_point(module_scope) diff --git a/src/cmvr/msgs/robot_detail_pb2.pyi b/src/cmvr/msgs/robot_detail_pb2.pyi new file mode 100644 index 0000000..d662e6e --- /dev/null +++ b/src/cmvr/msgs/robot_detail_pb2.pyi @@ -0,0 +1,22 @@ +from cmvr.msgs import canopen_pb2 as _canopen_pb2 +from cmvr.msgs import motor_pb2 as _motor_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class RobotDetail(_message.Message): + __slots__ = ("motors",) + class MotorsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: int + value: _motor_pb2.MotorStatus + def __init__(self, key: _Optional[int] = ..., value: _Optional[_Union[_motor_pb2.MotorStatus, _Mapping]] = ...) -> None: ... + MOTORS_FIELD_NUMBER: _ClassVar[int] + motors: _containers.MessageMap[int, _motor_pb2.MotorStatus] + def __init__(self, motors: _Optional[_Mapping[int, _motor_pb2.MotorStatus]] = ...) -> None: ... diff --git a/src/cmvr/msgs/robot_detail_pb2_grpc.py b/src/cmvr/msgs/robot_detail_pb2_grpc.py new file mode 100644 index 0000000..feff5ce --- /dev/null +++ b/src/cmvr/msgs/robot_detail_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in cmvr/msgs/robot_detail_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/src/cmvr_edge_ai.egg-info/PKG-INFO b/src/cmvr_edge_ai.egg-info/PKG-INFO new file mode 100644 index 0000000..2bd0ddf --- /dev/null +++ b/src/cmvr_edge_ai.egg-info/PKG-INFO @@ -0,0 +1,405 @@ +Metadata-Version: 2.4 +Name: cmvr-edge-ai +Version: 0.1.0 +Summary: Configuration-driven AI pipeline runtime for CMVR robot edge devices +Requires-Python: <3.13,>=3.10 +Description-Content-Type: text/markdown +Requires-Dist: pydantic<3,>=2.8 +Requires-Dist: PyYAML<7,>=6 +Provides-Extra: grpc +Requires-Dist: grpcio<2,>=1.76; extra == "grpc" +Requires-Dist: protobuf<7,>=6.31.1; extra == "grpc" +Provides-Extra: http +Requires-Dist: httpx<1,>=0.27; extra == "http" +Provides-Extra: video +Requires-Dist: av<17,>=12; extra == "video" +Provides-Extra: image +Requires-Dist: Pillow<13,>=10; extra == "image" +Provides-Extra: yolo +Requires-Dist: numpy<3,>=1.24; extra == "yolo" +Requires-Dist: ultralytics==8.4.31; extra == "yolo" +Provides-Extra: yolo-cpu +Requires-Dist: numpy<3,>=1.24; extra == "yolo-cpu" +Requires-Dist: torch==2.7.0; extra == "yolo-cpu" +Requires-Dist: torchvision==0.22.0; extra == "yolo-cpu" +Requires-Dist: ultralytics==8.4.31; extra == "yolo-cpu" + +# cmvr-edge-ai + +`cmvr-edge-ai` 是部署在机器人边缘端的、配置驱动的 AI 流水线运行时。它把上游传感器、AI 算法和下游机器人/平台接口拆成可组合节点,通过 YAML 把节点连接成有向无环图(DAG)。协议只出现在边界连接器中,算法节点使用统一的内部消息,因此以后增加 HTTP、gRPC、UDP 或 QUIC 时不需要改动算法本身。 + +当前版本已经包含可运行的检测链路和框架基础设施: + +- 严格 YAML 配置校验、环境变量展开和 DAG 编译检查; +- `Source -> Operator -> Sink` 异步运行时; +- 每条边独立的有界队列和明确的溢出策略; +- 版本化插件注册表、检测模型注册表和 Python entry point 扩展机制; +- cmvr-es RGB 相机 gRPC Source(带退避上限的指数重连)、AGV gRPC Sink; +- PyAV H264/H265 有状态解码、通用检测模型节点和重复命中规则节点; +- Construction PPE YOLOv8 模型注册,以及按标签、置信度和最大 FPS 的部署配置; +- 平台 HTTP JSON Sink,支持有限重试并使用告警 `event_id` 作为幂等键; +- `RobotCommand -> ApprovedRobotCommand` 安全门和无重试的类型化 AGV 命令映射; +- 文本/JSON 日志、共享 gRPC Channel 与 HTTP Client; +- 可直接执行的 smoke、PPE 检测和对话占位配置。 + +`detect_server/pipeline.yaml` 已实现“相机编码流 -> PyAV 解码 -> 注册模型推理 -> 时间窗口规则 -> HTTP 告警”。VAD/ASR/LLM/TTS 尚未内置;`talk_server/pipeline.yaml` 仍使用模拟音频数据,等待 cmvr-es 音频双向流 proto 落地。 + +## 架构概览 + +```mermaid +flowchart LR + A["cmvr-es / 平台
gRPC、HTTP、未来 UDP/QUIC"] --> B["Source 连接器"] + B --> C["每条边独立的有界队列"] + C --> D["Operator DAG
解码、检测、VAD、ASR、LLM、TTS、策略"] + D --> E["Safety Gate"] + D --> F["平台 Sink"] + E --> G["机器人执行器 Sink"] +``` + +内部节点传递 `Envelope[T]`,其中包含载荷、`schema/version`、采集时间、序号、截止时间、`trace_id` 和 `session_id`。连接器负责 protobuf/HTTP JSON 与内部契约之间的转换,AI 插件不应直接依赖 cmvr-es protobuf。 + +完整设计和配置字段见 [docs/architecture.md](docs/architecture.md)。 + +## 目录 + +```text +cmvr_edge_ai/ +├── .python-version # uv 默认 Python 3.10 +├── uv.lock # 所有 profile 的可复现依赖锁 +├── configs/ +│ └── smoke.yaml # 不依赖外部服务的最小运行验证 +├── detect_server/ +│ └── pipeline.yaml # 相机 -> 解码 -> PPE 检测 -> 重复规则 -> HTTP +├── talk_server/ +│ ├── nodes/ # 对话插件预留目录 +│ └── pipeline.yaml # 模拟音频 -> 对话占位 -> 日志 +├── scripts/ +│ ├── bootstrap.sh # 一键创建 uv 环境、生成 bindings 并自检 +│ └── generate_cmvr_stubs.py # 从 cmvr-es proto 生成 Python bindings +├── src/cmvr_edge_ai/ +│ ├── config/ # 配置模型、加载与环境变量展开 +│ ├── core/ # Envelope、组件接口、队列和 DAG 运行时 +│ ├── contracts/ # 协议无关的图像、音频、AI 和控制契约 +│ ├── detection/ # 模型注册、视频解码、推理与时间窗口规则 +│ ├── plugins/ # 插件注册、发现和内置基础插件 +│ ├── connectors/ # cmvr-es 与平台边界连接器 +│ ├── transports/ # gRPC/HTTP 连接池;UDP/QUIC 扩展位置 +│ ├── workers/ # 显式线程 offload 与常驻进程 Worker 工具 +│ ├── observability/ # 低开销文本/JSON 日志 +│ ├── application.py # 多 Pipeline 与共享网络客户端的所有者 +│ ├── compiler.py # 配置到可执行 DAG 的编译器 +│ └── cli.py # validate/run/plugins/models +└── tests/ +``` + +## 快速开始 + +项目使用 `uv` 管理 Python、`.venv` 和锁定依赖。`.python-version` 默认选择 +Python 3.10,支持范围是 3.10~3.12;首次执行时,uv 会在本机没有合适解释器时 +自动安装。先确认已经安装 uv: + +```bash +uv --version +``` + +未安装时可使用 uv 官方安装器: + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +只验证框架和模拟对话链路时,一条命令创建最小环境并自检: + +```bash +cd /home/xtkuang/Projects/cmvr/cmvr_edge_ai +bash scripts/bootstrap.sh --profile core +``` + +`uv.lock` 是安装的唯一版本来源;bootstrap 使用 `uv sync --locked`,不会在用户 +机器上重新选择依赖版本。无需 `source .venv/bin/activate`,统一通过 +`uv run --no-sync` 使用已经安装好的环境: + +```bash +uv run --no-sync cmvr-edge-ai validate --config configs/smoke.yaml +uv run --no-sync cmvr-edge-ai plugins +uv run --no-sync cmvr-edge-ai run \ + --config configs/smoke.yaml \ + --log-level INFO \ + --log-format text +``` + +预期会看到两条日志,内容分别包含 `framework-ready` 和 `bounded-dag-running`。该 Source 是有限数据源,数据处理完后进程会自然退出。 + +bootstrap 支持以下环境: + +| Profile | 安装内容 | 命令 | +|---|---|---| +| `core` | 框架核心和模拟 smoke/talk 链路 | `bash scripts/bootstrap.sh --profile core` | +| `detection-cpu` | gRPC、HTTP、PyAV、Pillow 告警图片和固定版本 CPU YOLO;默认值 | `bash scripts/bootstrap.sh` | +| `dev` | `detection-cpu` 加测试和 protobuf codegen 工具,并运行完整测试 | `bash scripts/bootstrap.sh --profile dev` | + +如果只希望安装、不执行自检,可加 `--skip-check`。完整参数通过以下命令查看: + +```bash +bash scripts/bootstrap.sh --help +``` + +不要使用 `uv sync --all-extras`:`yolo` 与 `yolo-cpu` 是为不同 PyTorch 来源准备的 +互斥环境。请使用 bootstrap profile,或显式只选择其中一个 extra。 +告警图片的画框和 JPEG 编码由独立的 `image` extra 提供;默认的 +`detection-cpu`/`dev` profile 已安装它,手动组合检测环境时也必须选择 +`--extra image`。 + +CLI 的四个子命令如下: + +| 命令 | 用途 | +|---|---| +| `validate -c FILE [--pipeline ID]` | 加载配置、展开环境变量、构造插件并校验 DAG;不启动 Pipeline | +| `run -c FILE [--pipeline ID]` | 启动选中的 Pipeline;未指定时启动所有 `enabled: true` 的 Pipeline | +| `plugins` | 列出内置插件和已安装 entry point 插件 | +| `models` | 列出检测模型 ID、名称、backend 和注册的全部标签 | + +`--pipeline` 可以重复传入。`run` 还支持 `--log-level` 和 `--log-format text|json`。配置错误退出码为 `2`,运行错误为 `1`,键盘中断为 `130`。 + +## 运行 PPE 检测链路 + +默认 bootstrap 就是当前 YAML 使用的 CPU 检测环境。它会从相邻的 +`../cmvr-es` 读取 proto、用锁定的 `grpcio-tools` 生成 bindings,然后安装完整 +检测依赖并校验 smoke 和 PPE 配置: + +```bash +cd /home/xtkuang/Projects/cmvr/cmvr_edge_ai +bash scripts/bootstrap.sh +``` + +如果 cmvr-es 不在相邻目录,显式指定路径: + +```bash +bash scripts/bootstrap.sh \ + --cmvr-es-root /home/xtkuang/Projects/cmvr/cmvr-es +``` + +bootstrap 默认使用 portable codegen,所以不要求 cmvr-es 已经编译出 `protoc`。 +要跳过生成(例如部署包已经包含匹配版本的 bindings),使用 +`--skip-codegen`。手动生成时可以执行: + +```bash +uv sync --locked --only-group codegen +.venv/bin/python scripts/generate_cmvr_stubs.py \ + --cmvr-es-root /home/xtkuang/Projects/cmvr/cmvr-es \ + --portable +``` + +生成文件默认写入 `src/cmvr/...`,使 `cmvr.api.*_pb2` 可以被连接器导入。 +生成后应再次执行目标 profile 的 `uv sync --locked`,让可编辑安装识别新包;bootstrap +已经按这个顺序处理。 + +在 `detect_server/pipeline.yaml` 中配置部署参数: + +```yaml +endpoints: + cmvr_es: + target: 127.0.0.1:50052 + platform: + base_url: http://127.0.0.1:8081 + +pipelines: + detection: + nodes: + detector: + with: + attach_frame: true + model_options: + weights: /home/xtkuang/Projects/cmvr/changan_robot/construction-ppe-yolov8/best.pt + device: cpu + repeat_gate: + with: + alert_image: + enabled: true + jpeg_quality: 85 +``` + +然后验证并运行检测链路,不再要求预先导出环境变量: + +```bash +uv run --no-sync cmvr-edge-ai models +uv run --no-sync cmvr-edge-ai validate \ + --config detect_server/pipeline.yaml \ + --pipeline detection +uv run --no-sync cmvr-edge-ai run --config detect_server/pipeline.yaml \ + --pipeline detection \ + --log-level INFO \ + --log-format json +``` + +运行前需要确认权重文件存在、cmvr-es 已启用 `right_hand_cam`,并且平台的 `/v1/detection-alerts` 可访问。默认 `yolo-cpu` profile 将 PyTorch 2.7.0 和 torchvision 0.22.0 绑定到官方 CPU wheel,并固定 checkpoint 记录的 Ultralytics 8.4.31。`model_options.device` 在该环境中应保持 `cpu`,`half` 应保持 `false`。 + +x86 CUDA 和 Jetson/JetPack 的 PyTorch wheel 与驱动强绑定,不能复用这个 CPU +profile。项目保留了不绑定 CPU index 的 `yolo` extra 作为设备专用环境的基础,但 +GPU 部署前应为目标设备建立单独的 uv source/lock(或使用 NVIDIA 容器),再把 YAML +中的 `device` 改为 `cuda:0`;不要只改 YAML 就认为 CUDA 环境已经就绪。 + +`detection.model@1` 根据 `model` 从 `DetectionModelRegistry` 解析模型。`detect_labels` 只选择需要检测的标签,省略时检测注册模型的全部标签;`confidence` 是全局阈值,也可以用 `label_confidence` 为个别标签覆盖。内置模型 `construction-ppe-yolov8@1` 的名称和 19 个标签可以通过 `cmvr-edge-ai models` 查看。`attach_frame: true` 让检测结果临时携带对应的解码帧,供后续告警节点使用;因此 detector 到 repeat gate 的队列应保持较小,避免堆积未压缩图像。 + +`detection.repeat_gate@1` 只在一个规则的 `window_ms` 内看到至少 `min_hits` 个不同帧后生成 `DetectionAlert/v1`。同一帧有多个同类框仍只算一次;触发后进入 `cooldown_ms`,冷却期间不累计,结束后必须重新满足次数。`scope: source` 按相机统计;`scope: track` 按 `track_id` 统计,但当前 YOLO adapter 只做逐帧检测,不产生 `track_id`,因此使用 track 规则前必须增加跟踪/关联节点。 + +启用 `alert_image` 后,repeat gate 只在规则真正触发时使用 Pillow 对阈值帧画框并编码 JPEG,不会给每一帧都生成图片。告警的 `detections` 和图片中的 bounding boxes 都来自达到 `min_hits` 的阈值帧;窗口内更早帧只参与 `hit_count`、时间范围和最大置信度统计。HTTP JSON 中图片位于 `payload.image`: + +```json +{ + "image": { + "media_type": "image/jpeg", + "width": 1280, + "height": 720, + "encoding": "base64", + "data": "..." + } +} +``` + +这里的外层对象是 `DetectionAlert` payload 的片段;HTTP JSON Sink 会把内部 JPEG +`bytes` 转成上述扁平 Base64 图片对象。未启用图片,或运行时因第三方结果未附带 +帧、坏帧等原因渲染失败时,告警仍会发送且 `payload.image` 为 `null`。 +同一阈值帧若同时触发多条规则或多个 track,只编码一次相关框的并集,并让这些 +告警共享同一个不可变 JPEG 对象,以限制边缘端瞬时 CPU 和内存开销。 + +对话占位链路不依赖音频 proto: + +```bash +uv run --no-sync cmvr-edge-ai validate --config talk_server/pipeline.yaml +uv run --no-sync cmvr-edge-ai run --config talk_server/pipeline.yaml +``` + +## 配置最小示例 + +```yaml +api_version: cmvr.edge.ai/v1 + +runtime: + thread_workers: 2 + shutdown_timeout_s: 5 + +pipelines: + example: + enabled: true + nodes: + source: + uses: core.sequence_source@1 + with: + items: [hello] + schema_name: TextEvent + schema_version: 1 + sink: + uses: core.log_sink@1 + edges: + - from: source.output + to: sink.input + qos: + profile: request + capacity: 4 + overflow: block +``` + +配置模型是严格的,多余字段会报错。字符串支持以下环境变量形式: + +- `${NAME}`:变量必须存在; +- `${NAME:-default}`:未设置或为空时使用默认值; +- `env://NAME`:整个字符串取自必填环境变量。 + +v1 支持五个 `qos.profile`,并在编译期约束其溢出策略:编码 H264/H265 在解码前必须使用 `video_contiguous`,只能阻塞或拒绝;解码后的完整图像可使用 `realtime_latest` 丢旧帧控制延迟;`audio_contiguous` 和 `request` 只能阻塞或拒绝;`telemetry` 支持全部策略。未填写 QoS 时使用无损的 `request + block + capacity=1`。`reject` 会抛异常,`error` 是它的兼容别名。`max_age_ms`、`put_timeout_ms` 已预留,设置后 `validate` 会拒绝配置。完整矩阵见架构文档。 + +## 注册新的检测模型 + +检测模型和 DAG 插件是两层注册:流水线固定使用通用的 `detection.model@1`,具体模型通过 `DetectionModelRegistry` 注册 `DetectionModelSpec`。每个 spec 必须给出版本化 `model_id`、面向运维的 `name`、有序且唯一的 `supported_labels`、`backend` 和 factory。factory 返回实现 `load/predict/close` 的 `DetectionModel`;部署 YAML 中的 `model_options` 原样交给它。第三方模型包可以使用 `cmvr_edge_ai.detection_models` entry point 发布 spec 或注册回调。安装后先执行 `cmvr-edge-ai models`,再让配置引用其中的精确模型 ID。 + +模型实际输出的标签仍会在通用 Operator 边界二次校验和过滤;模型返回未注册标签会让节点失败。直接相连的重复规则若引用了 detector 没有选择的标签,也会在 `validate` 阶段被编译器拒绝。 + +## 开发插件 + +插件必须使用带版本的稳定 ID,例如 `example.text_upper@1`,并声明节点种类、输入/输出端口及 schema。工厂签名固定为 `(node_id, params)`;组件分别继承 `Source`、`Operator` 或 `Sink`。 + +```python +from collections.abc import Mapping +from typing import Any + +from cmvr_edge_ai.contracts import TextEvent +from cmvr_edge_ai.core import Emission, Envelope, Operator +from cmvr_edge_ai.plugins import PluginKind, PluginRegistry, PluginSpec + + +class UppercaseOperator(Operator): + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + self.node_id = node_id + + async def process( + self, envelope: Envelope[Any], input_port: str = "input" + ) -> Emission: + event = envelope.payload + if not isinstance(event, TextEvent): + raise TypeError("expected TextEvent") + result = TextEvent(text=event.text.upper(), role=event.role, final=event.final) + return Emission( + "output", + envelope.with_payload(result, schema_name="TextEvent", schema_version=1), + ) + + +def register_plugins(registry: PluginRegistry) -> None: + registry.register( + PluginSpec( + plugin_id="example.text_upper@1", + kind=PluginKind.OPERATOR, + factory=UppercaseOperator, + inputs={"input": "TextEvent/v1"}, + outputs={"output": "TextEvent/v1"}, + description="Uppercase text events", + ) + ) +``` + +在插件包的 `pyproject.toml` 中注册: + +```toml +[project.entry-points."cmvr_edge_ai.plugins"] +example = "my_cmvr_plugin.plugins:register_plugins" +``` + +安装插件包后,用下面的命令确认发现成功: + +```bash +uv run --no-sync cmvr-edge-ai plugins +``` + +配置文件不能通过 `module:Class` 任意导入代码;只会使用内置或已安装 entry point 注册的插件。插件完整生命周期、返回值规范和连接器开发约定见 [docs/architecture.md](docs/architecture.md#5-插件开发约定)。 + +## 机器人控制安全边界 + +控制链固定为: + +```text +Policy -> RobotCommand/v1 + -> safety.robot_command_gate@1 + -> ApprovedRobotCommand/v1 + -> cmvr.grpc.agv_command_sink@1 +``` + +AGV Sink 只接受 `ApprovedRobotCommand`,原始 `RobotCommand` 会在运行时被拒绝。配置编译器还要求安全门是执行器的直接前驱;安全门与 actuator 之间不能插入其他节点,也不能增加绕过安全门的输入边。内置安全门负责过期检查和默认 5 秒的最大 TTL、动作/设备白名单、参数范围、最小发送间隔及默认开启的单调序号检查;不合格命令会被丢弃并记录 warning,不会因为一条业务拒绝停止整条 Pipeline。 + +AGV Sink 必须绑定固定的非空 `device_id`。`set_velocity` 默认禁用;`unsafe_allow_unleased_velocity: true` 只是面向隔离测试场景的显式逃生开关,并且仍要求 Sink 配置 `vx/vy/wz` 的 `velocity_limits`。它只能限制当前客户端发出的数值,并在正常关闭时尽力调用 `stopVelocityControl`;进程崩溃、`SIGKILL`、断电或网络分区时无法保证停车。 + +真实机器人要启用持续速度控制,必须先在 cmvr-es 服务端实现带过期时间的 lease/deadman:客户端停止续租后,由 cmvr-es 在独立于 edge-ai 进程的安全路径中自动清零速度并停车。客户端的安全门、TTL 和 shutdown hook 不能替代这项服务端保护。 + +## 当前实现边界 + +- 音频内部契约 `AudioChunk/v1` 已定义,但 cmvr-es 麦克风/扬声器双向流 proto 和连接器尚未落地。 +- UDP 与 QUIC 目录目前是扩展占位,没有可用传输实现。 +- 运行时 v1 只支持 `execution.mode: async|inline`,两者当前都是单 task、单并发执行。`thread`、`process`、`model_worker` 是保留值;`concurrency != 1`、非空 `max_in_flight/timeout_s`、`ordered: false` 也是保留配置,都会在编译期被拒绝。插件仍可在组件内部显式使用 `workers.run_blocking()`、`PersistentProcessWorker` 或自有模型 Worker,但不能把 `execution` 声明误当作自动调度。 +- v1 对未实现的声明采取 fail-closed:显式设置 `runtime.max_processes/process_start_method/health_bind/reserved_memory_mb`、非默认 pipeline `priority` 或任何非空 `resources` 都会在编译期被拒绝。健康状态和队列统计目前只能通过 Python API 获取。 +- 当前没有配置热更新、配置 overlay、持久化 outbox/spool 或共享内存图像池。HTTP Sink 只有当前进程内的有限重试,重试耗尽会终止 Pipeline,进程退出或断电仍可能丢失未上报告警。 +- 相机 Source 可以重连,但 cmvr-es 当前服务端通过 `getLatestEncodedFrame` 获取最新编码数据;如果上游在 edge-ai 收到之前已跳过 H264/H265 参考包,`video_contiguous` 无法补回数据,解码器只能在错误后重置并等待关键帧。正式部署应验证 cmvr-es 输出的是连续 access unit 流,或改为对 AI 友好的原始/JPEG/可检测不连续性的接口。 +- 内置 YOLO adapter 不运行 tracker,所有检测的 `track_id` 都为空;当前示例因此使用 `scope: source`。 +- PPE 权重仓库对权重的许可说明与 Ultralytics runtime/checkpoint 中的 AGPL 信息需要在商业交付前核对,并同时确认训练数据和权重分发许可。 +- 端口 schema 在编译期按字符串匹配;运行时不会自动验证 Python payload 类型,插件必须在边界处主动检查。 +- AGV 执行器不会自动重试。安全门已提供动作/设备白名单、TTL、参数范围、最小间隔和单调序号检查,但设备状态、command ID 去重、来源仲裁、服务端 lease/deadman 和急停优先级仍需按机器人型号补齐。服务端 deadman 未完成前,禁止把 `set_velocity` 用于真实机器人持续运动。 + +这些限制及推荐演进顺序在 [docs/architecture.md](docs/architecture.md#9-当前限制与演进顺序) 中有更详细说明。 diff --git a/src/cmvr_edge_ai.egg-info/SOURCES.txt b/src/cmvr_edge_ai.egg-info/SOURCES.txt new file mode 100644 index 0000000..96f277b --- /dev/null +++ b/src/cmvr_edge_ai.egg-info/SOURCES.txt @@ -0,0 +1,207 @@ +README.md +pyproject.toml +src/cmvr/api/agv_command_pb2.py +src/cmvr/api/agv_command_pb2.pyi +src/cmvr/api/agv_command_pb2_grpc.py +src/cmvr/api/agv_service_pb2.py +src/cmvr/api/agv_service_pb2.pyi +src/cmvr/api/agv_service_pb2_grpc.py +src/cmvr/api/arm_command_pb2.py +src/cmvr/api/arm_command_pb2.pyi +src/cmvr/api/arm_command_pb2_grpc.py +src/cmvr/api/arm_service_pb2.py +src/cmvr/api/arm_service_pb2.pyi +src/cmvr/api/arm_service_pb2_grpc.py +src/cmvr/api/biohead_command_pb2.py +src/cmvr/api/biohead_command_pb2.pyi +src/cmvr/api/biohead_command_pb2_grpc.py +src/cmvr/api/biohead_service_pb2.py +src/cmvr/api/biohead_service_pb2.pyi +src/cmvr/api/biohead_service_pb2_grpc.py +src/cmvr/api/camera_command_pb2.py +src/cmvr/api/camera_command_pb2.pyi +src/cmvr/api/camera_command_pb2_grpc.py +src/cmvr/api/camera_service_pb2.py +src/cmvr/api/camera_service_pb2.pyi +src/cmvr/api/camera_service_pb2_grpc.py +src/cmvr/api/common_pb2.py +src/cmvr/api/common_pb2.pyi +src/cmvr/api/common_pb2_grpc.py +src/cmvr/api/dexhand_command_pb2.py +src/cmvr/api/dexhand_command_pb2.pyi +src/cmvr/api/dexhand_command_pb2_grpc.py +src/cmvr/api/dexhand_service_pb2.py +src/cmvr/api/dexhand_service_pb2.pyi +src/cmvr/api/dexhand_service_pb2_grpc.py +src/cmvr/api/hlc_command_pb2.py +src/cmvr/api/hlc_command_pb2.pyi +src/cmvr/api/hlc_command_pb2_grpc.py +src/cmvr/api/hlc_service_pb2.py +src/cmvr/api/hlc_service_pb2.pyi +src/cmvr/api/hlc_service_pb2_grpc.py +src/cmvr/api/microphone_command_pb2.py +src/cmvr/api/microphone_command_pb2.pyi +src/cmvr/api/microphone_command_pb2_grpc.py +src/cmvr/api/microphone_service_pb2.py +src/cmvr/api/microphone_service_pb2.pyi +src/cmvr/api/microphone_service_pb2_grpc.py +src/cmvr/api/speaker_command_pb2.py +src/cmvr/api/speaker_command_pb2.pyi +src/cmvr/api/speaker_command_pb2_grpc.py +src/cmvr/api/speaker_service_pb2.py +src/cmvr/api/speaker_service_pb2.pyi +src/cmvr/api/speaker_service_pb2_grpc.py +src/cmvr/api/system_command_pb2.py +src/cmvr/api/system_command_pb2.pyi +src/cmvr/api/system_command_pb2_grpc.py +src/cmvr/api/system_service_pb2.py +src/cmvr/api/system_service_pb2.pyi +src/cmvr/api/system_service_pb2_grpc.py +src/cmvr/api/test_service_pb2.py +src/cmvr/api/test_service_pb2.pyi +src/cmvr/api/test_service_pb2_grpc.py +src/cmvr/common/geometry_pb2.py +src/cmvr/common/geometry_pb2.pyi +src/cmvr/common/geometry_pb2_grpc.py +src/cmvr/config/joint_limits_config_pb2.py +src/cmvr/config/joint_limits_config_pb2.pyi +src/cmvr/config/joint_limits_config_pb2_grpc.py +src/cmvr/config/lawba_ik_config_pb2.py +src/cmvr/config/lawba_ik_config_pb2.pyi +src/cmvr/config/lawba_ik_config_pb2_grpc.py +src/cmvr/config/pinocchio_dls_ik_config_pb2.py +src/cmvr/config/pinocchio_dls_ik_config_pb2.pyi +src/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py +src/cmvr/config/pinocchio_qp_ik_config_pb2.py +src/cmvr/config/pinocchio_qp_ik_config_pb2.pyi +src/cmvr/config/pinocchio_qp_ik_config_pb2_grpc.py +src/cmvr/config/srs_ik_config_pb2.py +src/cmvr/config/srs_ik_config_pb2.pyi +src/cmvr/config/srs_ik_config_pb2_grpc.py +src/cmvr/config/touch_screen_algorithm_config_pb2.py +src/cmvr/config/touch_screen_algorithm_config_pb2.pyi +src/cmvr/config/touch_screen_algorithm_config_pb2_grpc.py +src/cmvr/config/touch_screen_common_config_pb2.py +src/cmvr/config/touch_screen_common_config_pb2.pyi +src/cmvr/config/touch_screen_common_config_pb2_grpc.py +src/cmvr/config/agv_config/agv_config_pb2.py +src/cmvr/config/agv_config/agv_config_pb2.pyi +src/cmvr/config/agv_config/agv_config_pb2_grpc.py +src/cmvr/config/arm_config/arm_config_pb2.py +src/cmvr/config/arm_config/arm_config_pb2.pyi +src/cmvr/config/arm_config/arm_config_pb2_grpc.py +src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.py +src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.pyi +src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2_grpc.py +src/cmvr/config/biohead_config/biohead_config_pb2.py +src/cmvr/config/biohead_config/biohead_config_pb2.pyi +src/cmvr/config/biohead_config/biohead_config_pb2_grpc.py +src/cmvr/config/camera_config/camera_config_pb2.py +src/cmvr/config/camera_config/camera_config_pb2.pyi +src/cmvr/config/camera_config/camera_config_pb2_grpc.py +src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.py +src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.pyi +src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2_grpc.py +src/cmvr/config/device_manager_config/device_manager_config_pb2.py +src/cmvr/config/device_manager_config/device_manager_config_pb2.pyi +src/cmvr/config/device_manager_config/device_manager_config_pb2_grpc.py +src/cmvr/config/dexhand_config/dexhand_config_pb2.py +src/cmvr/config/dexhand_config/dexhand_config_pb2.pyi +src/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py +src/cmvr/config/grpc_server_config/grpc_server_config_pb2.py +src/cmvr/config/grpc_server_config/grpc_server_config_pb2.pyi +src/cmvr/config/grpc_server_config/grpc_server_config_pb2_grpc.py +src/cmvr/config/logger_config/logger_config_pb2.py +src/cmvr/config/logger_config/logger_config_pb2.pyi +src/cmvr/config/logger_config/logger_config_pb2_grpc.py +src/cmvr/config/microphone_config/microphone_config_pb2.py +src/cmvr/config/microphone_config/microphone_config_pb2.pyi +src/cmvr/config/microphone_config/microphone_config_pb2_grpc.py +src/cmvr/config/motor_config/motor_config_pb2.py +src/cmvr/config/motor_config/motor_config_pb2.pyi +src/cmvr/config/motor_config/motor_config_pb2_grpc.py +src/cmvr/config/speaker_config/speaker_config_pb2.py +src/cmvr/config/speaker_config/speaker_config_pb2.pyi +src/cmvr/config/speaker_config/speaker_config_pb2_grpc.py +src/cmvr/config/task_manager_config/task_manager_config_pb2.py +src/cmvr/config/task_manager_config/task_manager_config_pb2.pyi +src/cmvr/config/task_manager_config/task_manager_config_pb2_grpc.py +src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.py +src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.pyi +src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2_grpc.py +src/cmvr/msgs/agv_pb2.py +src/cmvr/msgs/agv_pb2.pyi +src/cmvr/msgs/agv_pb2_grpc.py +src/cmvr/msgs/can_card_parameter_pb2.py +src/cmvr/msgs/can_card_parameter_pb2.pyi +src/cmvr/msgs/can_card_parameter_pb2_grpc.py +src/cmvr/msgs/canopen_pb2.py +src/cmvr/msgs/canopen_pb2.pyi +src/cmvr/msgs/canopen_pb2_grpc.py +src/cmvr/msgs/error_code_pb2.py +src/cmvr/msgs/error_code_pb2.pyi +src/cmvr/msgs/error_code_pb2_grpc.py +src/cmvr/msgs/motor_pb2.py +src/cmvr/msgs/motor_pb2.pyi +src/cmvr/msgs/motor_pb2_grpc.py +src/cmvr/msgs/robot_detail_pb2.py +src/cmvr/msgs/robot_detail_pb2.pyi +src/cmvr/msgs/robot_detail_pb2_grpc.py +src/cmvr_edge_ai/__init__.py +src/cmvr_edge_ai/application.py +src/cmvr_edge_ai/cli.py +src/cmvr_edge_ai/compiler.py +src/cmvr_edge_ai.egg-info/PKG-INFO +src/cmvr_edge_ai.egg-info/SOURCES.txt +src/cmvr_edge_ai.egg-info/dependency_links.txt +src/cmvr_edge_ai.egg-info/entry_points.txt +src/cmvr_edge_ai.egg-info/requires.txt +src/cmvr_edge_ai.egg-info/top_level.txt +src/cmvr_edge_ai/config/__init__.py +src/cmvr_edge_ai/config/loader.py +src/cmvr_edge_ai/config/models.py +src/cmvr_edge_ai/connectors/__init__.py +src/cmvr_edge_ai/connectors/_context.py +src/cmvr_edge_ai/connectors/cmvr_es/__init__.py +src/cmvr_edge_ai/connectors/cmvr_es/agv.py +src/cmvr_edge_ai/connectors/cmvr_es/camera.py +src/cmvr_edge_ai/connectors/cmvr_es/errors.py +src/cmvr_edge_ai/connectors/platform/__init__.py +src/cmvr_edge_ai/connectors/platform/http_json.py +src/cmvr_edge_ai/contracts/__init__.py +src/cmvr_edge_ai/contracts/control.py +src/cmvr_edge_ai/contracts/events.py +src/cmvr_edge_ai/contracts/media.py +src/cmvr_edge_ai/core/__init__.py +src/cmvr_edge_ai/core/component.py +src/cmvr_edge_ai/core/edge_queue.py +src/cmvr_edge_ai/core/envelope.py +src/cmvr_edge_ai/core/runtime.py +src/cmvr_edge_ai/detection/__init__.py +src/cmvr_edge_ai/detection/annotation.py +src/cmvr_edge_ai/detection/base.py +src/cmvr_edge_ai/detection/operator.py +src/cmvr_edge_ai/detection/plugins.py +src/cmvr_edge_ai/detection/registry.py +src/cmvr_edge_ai/detection/rules.py +src/cmvr_edge_ai/detection/video.py +src/cmvr_edge_ai/detection/models/__init__.py +src/cmvr_edge_ai/detection/models/ppe_yolo.py +src/cmvr_edge_ai/detection/models/yolo.py +src/cmvr_edge_ai/observability/__init__.py +src/cmvr_edge_ai/observability/logging.py +src/cmvr_edge_ai/plugins/__init__.py +src/cmvr_edge_ai/plugins/builtin.py +src/cmvr_edge_ai/plugins/registry.py +src/cmvr_edge_ai/transports/__init__.py +src/cmvr_edge_ai/transports/base.py +src/cmvr_edge_ai/transports/grpc/__init__.py +src/cmvr_edge_ai/transports/grpc/bidi.py +src/cmvr_edge_ai/transports/grpc/channel.py +src/cmvr_edge_ai/transports/http/__init__.py +src/cmvr_edge_ai/transports/http/client.py +src/cmvr_edge_ai/transports/quic/__init__.py +src/cmvr_edge_ai/transports/udp/__init__.py +src/cmvr_edge_ai/workers/__init__.py +src/cmvr_edge_ai/workers/process.py +src/cmvr_edge_ai/workers/thread.py \ No newline at end of file diff --git a/src/cmvr_edge_ai.egg-info/dependency_links.txt b/src/cmvr_edge_ai.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/cmvr_edge_ai.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/cmvr_edge_ai.egg-info/entry_points.txt b/src/cmvr_edge_ai.egg-info/entry_points.txt new file mode 100644 index 0000000..bdb5e17 --- /dev/null +++ b/src/cmvr_edge_ai.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +cmvr-edge-ai = cmvr_edge_ai.cli:main diff --git a/src/cmvr_edge_ai.egg-info/requires.txt b/src/cmvr_edge_ai.egg-info/requires.txt new file mode 100644 index 0000000..4f0dabc --- /dev/null +++ b/src/cmvr_edge_ai.egg-info/requires.txt @@ -0,0 +1,25 @@ +pydantic<3,>=2.8 +PyYAML<7,>=6 + +[grpc] +grpcio<2,>=1.76 +protobuf<7,>=6.31.1 + +[http] +httpx<1,>=0.27 + +[image] +Pillow<13,>=10 + +[video] +av<17,>=12 + +[yolo] +numpy<3,>=1.24 +ultralytics==8.4.31 + +[yolo-cpu] +numpy<3,>=1.24 +torch==2.7.0 +torchvision==0.22.0 +ultralytics==8.4.31 diff --git a/src/cmvr_edge_ai.egg-info/top_level.txt b/src/cmvr_edge_ai.egg-info/top_level.txt new file mode 100644 index 0000000..6cd3893 --- /dev/null +++ b/src/cmvr_edge_ai.egg-info/top_level.txt @@ -0,0 +1,2 @@ +cmvr +cmvr_edge_ai diff --git a/src/cmvr_edge_ai/__init__.py b/src/cmvr_edge_ai/__init__.py new file mode 100644 index 0000000..a92af13 --- /dev/null +++ b/src/cmvr_edge_ai/__init__.py @@ -0,0 +1,3 @@ +"""CMVR edge AI pipeline runtime.""" + +__version__ = "0.1.0" diff --git a/src/cmvr_edge_ai/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..1d6abeb Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..11f94e0 Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/__pycache__/application.cpython-310.pyc b/src/cmvr_edge_ai/__pycache__/application.cpython-310.pyc new file mode 100644 index 0000000..d713bf3 Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/application.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/__pycache__/application.cpython-312.pyc b/src/cmvr_edge_ai/__pycache__/application.cpython-312.pyc new file mode 100644 index 0000000..44ac9e0 Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/application.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/__pycache__/cli.cpython-310.pyc b/src/cmvr_edge_ai/__pycache__/cli.cpython-310.pyc new file mode 100644 index 0000000..36a232b Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/cli.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/__pycache__/cli.cpython-312.pyc b/src/cmvr_edge_ai/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000..119a943 Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/cli.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/__pycache__/compiler.cpython-310.pyc b/src/cmvr_edge_ai/__pycache__/compiler.cpython-310.pyc new file mode 100644 index 0000000..4a56ec0 Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/compiler.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/__pycache__/compiler.cpython-312.pyc b/src/cmvr_edge_ai/__pycache__/compiler.cpython-312.pyc new file mode 100644 index 0000000..93c9ce7 Binary files /dev/null and b/src/cmvr_edge_ai/__pycache__/compiler.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/application.py b/src/cmvr_edge_ai/application.py new file mode 100644 index 0000000..c2afc37 --- /dev/null +++ b/src/cmvr_edge_ai/application.py @@ -0,0 +1,199 @@ +"""Application-level ownership for pipelines and shared network clients.""" + +from __future__ import annotations + +import asyncio +from collections.abc import Iterable +from concurrent.futures import ThreadPoolExecutor +from enum import Enum + +from cmvr_edge_ai.compiler import ( + CompiledPipeline, + PipelineCompileError, + compile_pipeline, + enabled_pipeline_ids, +) +from cmvr_edge_ai.config import AppConfig +from cmvr_edge_ai.connectors import register_connector_plugins +from cmvr_edge_ai.detection import ( + DetectionModelRegistry, + register_builtin_detection_models, + register_detection_plugins, +) +from cmvr_edge_ai.plugins import PluginRegistry, register_builtin_plugins +from cmvr_edge_ai.transports.grpc import GrpcChannelPool +from cmvr_edge_ai.transports.http import HttpClientPool + + +class ApplicationState(str, Enum): + CREATED = "created" + STARTING = "starting" + RUNNING = "running" + STOPPING = "stopping" + STOPPED = "stopped" + + +def create_default_model_registry( + *, discover_entry_points: bool = True +) -> DetectionModelRegistry: + """Create the artifact registry used by generic detection nodes.""" + + registry = DetectionModelRegistry() + register_builtin_detection_models(registry) + if discover_entry_points: + registry.load_entry_points() + return registry + + +def create_default_registry( + *, + discover_entry_points: bool = True, + model_registry: DetectionModelRegistry | None = None, +) -> PluginRegistry: + registry = PluginRegistry() + register_builtin_plugins(registry) + register_connector_plugins(registry) + detection_models = model_registry or create_default_model_registry( + discover_entry_points=discover_entry_points + ) + register_detection_plugins(registry, detection_models) + if discover_entry_points: + registry.load_entry_points() + return registry + + +def validate_application( + config: AppConfig, + registry: PluginRegistry, + pipeline_ids: Iterable[str] | None = None, +) -> tuple[CompiledPipeline, ...]: + selected = _select_pipeline_ids(config, pipeline_ids) + return tuple( + compile_pipeline(config, pipeline_id, registry) for pipeline_id in selected + ) + + +class EdgeAIApplication: + """Own shared transports and one or more local pipeline runtimes.""" + + def __init__(self, config: AppConfig, registry: PluginRegistry) -> None: + self.config = config + self.registry = registry + self.state = ApplicationState.CREATED + self.grpc_channels = GrpcChannelPool() + self.http_clients = HttpClientPool() + self._compiled: dict[str, CompiledPipeline] = {} + self._executor: ThreadPoolExecutor | None = None + self._lifecycle_lock = asyncio.Lock() + + @property + def pipelines(self) -> tuple[str, ...]: + return tuple(self._compiled) + + async def start(self, pipeline_ids: Iterable[str] | None = None) -> None: + async with self._lifecycle_lock: + if self.state is not ApplicationState.CREATED: + raise RuntimeError( + f"cannot start application in state {self.state.value}" + ) + selected = _select_pipeline_ids(self.config, pipeline_ids) + self.state = ApplicationState.STARTING + try: + self._executor = ThreadPoolExecutor( + max_workers=self.config.runtime.thread_workers, + thread_name_prefix="cmvr-edge-ai", + ) + metadata = { + "endpoints": self.config.endpoints, + "grpc_channel_pool": self.grpc_channels, + "http_client_pool": self.http_clients, + "thread_executor": self._executor, + } + for pipeline_id in selected: + compiled = compile_pipeline( + self.config, + pipeline_id, + self.registry, + metadata=metadata, + ) + self._compiled[pipeline_id] = compiled + for compiled in self._compiled.values(): + await compiled.runtime.start() + except BaseException: + await self._stop_unlocked(graceful=False) + raise + self.state = ApplicationState.RUNNING + + async def wait(self) -> None: + if self.state is not ApplicationState.RUNNING: + if self.state is ApplicationState.STOPPED: + return + raise RuntimeError("application has not been started") + await asyncio.gather( + *(compiled.runtime.wait() for compiled in self._compiled.values()) + ) + + async def stop(self, *, graceful: bool = True) -> None: + async with self._lifecycle_lock: + await self._stop_unlocked(graceful=graceful) + + async def _stop_unlocked(self, *, graceful: bool) -> None: + if self.state is ApplicationState.STOPPED: + return + self.state = ApplicationState.STOPPING + await asyncio.gather( + *( + compiled.runtime.stop(graceful=graceful) + for compiled in self._compiled.values() + ), + return_exceptions=True, + ) + await asyncio.gather( + self.grpc_channels.close(), + self.http_clients.close(), + return_exceptions=True, + ) + if self._executor is not None: + self._executor.shutdown(wait=False, cancel_futures=True) + self._executor = None + self.state = ApplicationState.STOPPED + + async def health(self) -> dict[str, object]: + reports = { + pipeline_id: await compiled.runtime.health() + for pipeline_id, compiled in self._compiled.items() + } + return {"state": self.state.value, "pipelines": reports} + + +def _select_pipeline_ids( + config: AppConfig, pipeline_ids: Iterable[str] | None +) -> tuple[str, ...]: + if pipeline_ids is None: + selected = enabled_pipeline_ids(config) + else: + selected = tuple(dict.fromkeys(pipeline_ids)) + unknown = sorted(set(selected) - set(config.pipelines)) + if unknown: + raise PipelineCompileError(f"unknown pipeline(s): {', '.join(unknown)}") + disabled = sorted( + pipeline_id + for pipeline_id in selected + if not config.pipelines[pipeline_id].enabled + ) + if disabled: + raise PipelineCompileError( + f"selected pipeline(s) are disabled: {', '.join(disabled)}" + ) + if not selected: + raise PipelineCompileError("no enabled pipelines selected") + return selected + + +__all__ = [ + "ApplicationState", + "EdgeAIApplication", + "create_default_model_registry", + "create_default_registry", + "validate_application", +] diff --git a/src/cmvr_edge_ai/cli.py b/src/cmvr_edge_ai/cli.py new file mode 100644 index 0000000..498ef97 --- /dev/null +++ b/src/cmvr_edge_ai/cli.py @@ -0,0 +1,122 @@ +"""Command-line interface for validating and running edge-AI pipelines.""" + +from __future__ import annotations + +import argparse +import asyncio +import signal +import sys +from pathlib import Path + +from pydantic import ValidationError + +from cmvr_edge_ai.application import ( + EdgeAIApplication, + create_default_model_registry, + create_default_registry, + validate_application, +) +from cmvr_edge_ai.compiler import PipelineCompileError +from cmvr_edge_ai.config import ConfigLoadError, load_config +from cmvr_edge_ai.observability import configure_logging + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="cmvr-edge-ai") + subparsers = parser.add_subparsers(dest="command", required=True) + + validate = subparsers.add_parser("validate", help="validate config and DAG") + _add_config_arguments(validate) + + run = subparsers.add_parser("run", help="run enabled pipelines") + _add_config_arguments(run) + run.add_argument("--log-level", default="INFO") + run.add_argument("--log-format", choices=("text", "json"), default="text") + + subparsers.add_parser("plugins", help="list registered plugins") + subparsers.add_parser("models", help="list registered detection models") + return parser + + +def _add_config_arguments(parser: argparse.ArgumentParser) -> None: + parser.add_argument("--config", "-c", type=Path, required=True) + parser.add_argument( + "--pipeline", + action="append", + dest="pipelines", + help="select a pipeline; repeat for multiple pipelines", + ) + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + try: + model_registry = create_default_model_registry() + registry = create_default_registry(model_registry=model_registry) + if args.command == "plugins": + for plugin_spec in registry.specs(): + print( + f"{plugin_spec.plugin_id}\t{plugin_spec.kind.value}\t" + f"{plugin_spec.description}" + ) + return 0 + if args.command == "models": + for model_spec in model_registry.specs(): + labels = ",".join(model_spec.supported_labels) + print( + f"{model_spec.model_id}\t{model_spec.name}\t" + f"{model_spec.backend}\t{labels}" + ) + return 0 + + config = load_config(args.config) + if args.command == "validate": + compiled = validate_application(config, registry, args.pipelines) + names = ", ".join(item.pipeline_id for item in compiled) + print(f"configuration is valid; pipelines: {names}") + return 0 + + configure_logging(args.log_level, args.log_format) + return asyncio.run(_run_application(config, registry, args.pipelines)) + except (ConfigLoadError, ValidationError, PipelineCompileError, ValueError) as exc: + print(f"configuration error: {exc}", file=sys.stderr) + return 2 + except KeyboardInterrupt: + return 130 + except Exception as exc: + print(f"runtime error: {exc}", file=sys.stderr) + return 1 + + +async def _run_application(config, registry, pipeline_ids) -> int: # type: ignore[no-untyped-def] + application = EdgeAIApplication(config, registry) + stop_event = asyncio.Event() + loop = asyncio.get_running_loop() + for signum in (signal.SIGINT, signal.SIGTERM): + try: + loop.add_signal_handler(signum, stop_event.set) + except NotImplementedError: + pass + + await application.start(pipeline_ids) + wait_task = asyncio.create_task(application.wait(), name="application-wait") + signal_task = asyncio.create_task(stop_event.wait(), name="application-signal") + try: + done, _ = await asyncio.wait( + (wait_task, signal_task), return_when=asyncio.FIRST_COMPLETED + ) + if wait_task in done: + await wait_task + else: + await application.stop(graceful=True) + wait_task.cancel() + await asyncio.gather(wait_task, return_exceptions=True) + finally: + signal_task.cancel() + await asyncio.gather(signal_task, return_exceptions=True) + await application.stop(graceful=True) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/cmvr_edge_ai/compiler.py b/src/cmvr_edge_ai/compiler.py new file mode 100644 index 0000000..f68d8c1 --- /dev/null +++ b/src/cmvr_edge_ai/compiler.py @@ -0,0 +1,375 @@ +"""Compile strict YAML configuration into executable pipeline runtimes.""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass +from typing import Any + +from cmvr_edge_ai.config import AppConfig, PipelineConfig +from cmvr_edge_ai.core import Component, Edge, Operator, OverflowPolicy, Sink, Source +from cmvr_edge_ai.core.runtime import PipelineRuntime +from cmvr_edge_ai.plugins import ( + PluginKind, + PluginRegistry, + PluginSpec, + schemas_compatible, +) + + +SUPPORTED_API_VERSION = "cmvr.edge.ai/v1" + + +class PipelineCompileError(ValueError): + """Raised when a valid YAML document cannot form an executable graph.""" + + +@dataclass(frozen=True, slots=True) +class CompiledPipeline: + pipeline_id: str + runtime: PipelineRuntime + plugin_specs: Mapping[str, PluginSpec] + + +def compile_pipeline( + config: AppConfig, + pipeline_id: str, + registry: PluginRegistry, + *, + metadata: Mapping[str, Any] | None = None, +) -> CompiledPipeline: + if config.api_version != SUPPORTED_API_VERSION: + raise PipelineCompileError( + f"unsupported api_version {config.api_version!r}; " + f"expected {SUPPORTED_API_VERSION!r}" + ) + try: + pipeline = config.pipelines[pipeline_id] + except KeyError as exc: + raise PipelineCompileError(f"unknown pipeline: {pipeline_id}") from exc + if not pipeline.enabled: + raise PipelineCompileError(f"pipeline is disabled: {pipeline_id}") + + _validate_supported_runtime_config(config) + _validate_supported_pipeline_config(pipeline_id, pipeline) + + components: dict[str, Component] = {} + specs: dict[str, PluginSpec] = {} + for node_id, node_config in pipeline.nodes.items(): + if not node_config.enabled: + raise PipelineCompileError( + f"active pipeline {pipeline_id!r} contains disabled node {node_id!r}; " + "disable the whole pipeline or remove its edges" + ) + _validate_supported_node_config(node_id, node_config) + try: + spec = registry.resolve(node_config.uses) + component = registry.create( + node_config.uses, node_id=node_id, params=node_config.params + ) + except Exception as exc: + raise PipelineCompileError( + f"failed to construct node {node_id!r} from {node_config.uses!r}: {exc}" + ) from exc + _validate_component_kind(node_id, component, spec) + _validate_endpoint_reference(config, node_id, node_config.params, spec) + components[node_id] = component + specs[node_id] = spec + + runtime_edges: list[Edge] = [] + for index, edge_config in enumerate(pipeline.edges): + source_node, source_port = edge_config.source.split(".", maxsplit=1) + target_node, target_port = edge_config.target.split(".", maxsplit=1) + source_spec = specs[source_node] + target_spec = specs[target_node] + + source_schema = _port_schema( + source_spec.outputs, source_port, edge_config.source, "output" + ) + target_schema = _port_schema( + target_spec.inputs, target_port, edge_config.target, "input" + ) + if not schemas_compatible(source_schema, target_schema): + raise PipelineCompileError( + f"schema mismatch on edge {edge_config.source!r} -> " + f"{edge_config.target!r}: {source_schema!r} is not compatible " + f"with {target_schema!r}" + ) + + if edge_config.qos.max_age_ms is not None: + raise PipelineCompileError( + f"edge {edge_config.source!r} -> {edge_config.target!r} sets " + "max_age_ms, which is reserved for the deadline-aware runtime" + ) + if edge_config.qos.put_timeout_ms is not None: + raise PipelineCompileError( + f"edge {edge_config.source!r} -> {edge_config.target!r} sets " + "put_timeout_ms, which is not implemented in runtime v1" + ) + _validate_qos_profile(edge_config.source, edge_config.target, edge_config.qos) + + overflow = edge_config.qos.overflow + if overflow == "error": + overflow = OverflowPolicy.REJECT.value + runtime_edges.append( + Edge( + source_node=source_node, + source_port=source_port, + target_node=target_node, + target_port=target_port, + capacity=edge_config.qos.capacity, + overflow=overflow, + name=f"{pipeline_id}:{index}:{edge_config.source}->{edge_config.target}", + ) + ) + + _validate_detection_rule_paths(pipeline, specs, components) + _validate_actuator_paths(pipeline, specs) + runtime_metadata = dict(metadata or {}) + runtime_metadata.setdefault("endpoints", config.endpoints) + runtime_metadata.setdefault("node_configs", pipeline.nodes) + runtime = PipelineRuntime( + components, + runtime_edges, + pipeline_id=pipeline_id, + shutdown_timeout_s=config.runtime.shutdown_timeout_s, + metadata=runtime_metadata, + ) + return CompiledPipeline(pipeline_id, runtime, specs) + + +def enabled_pipeline_ids(config: AppConfig) -> tuple[str, ...]: + return tuple( + pipeline_id + for pipeline_id, pipeline in config.pipelines.items() + if pipeline.enabled + ) + + +def _validate_component_kind(node_id: str, component: Any, spec: PluginSpec) -> None: + expected_type: type[Component] + if spec.kind is PluginKind.SOURCE: + expected_type = Source + elif spec.kind is PluginKind.OPERATOR: + expected_type = Operator + else: + expected_type = Sink + if not isinstance(component, expected_type): + raise PipelineCompileError( + f"plugin {spec.plugin_id!r} declares {spec.kind.value} but node " + f"{node_id!r} created {type(component).__name__}" + ) + + +def _port_schema( + ports: Mapping[str, str], port: str, reference: str, direction: str +) -> str: + try: + return ports[port] + except KeyError as exc: + available = ", ".join(sorted(ports)) or "" + raise PipelineCompileError( + f"unknown {direction} port {reference!r}; available: {available}" + ) from exc + + +def _validate_endpoint_reference( + config: AppConfig, + node_id: str, + params: Mapping[str, Any], + spec: PluginSpec, +) -> None: + endpoint_id = params.get("endpoint") + transport_tags = sorted( + tag.removeprefix("transport:") + for tag in spec.tags + if tag.startswith("transport:") + ) + if endpoint_id is None: + if transport_tags: + raise PipelineCompileError( + f"connector node {node_id!r} requires a named endpoint" + ) + return + if not isinstance(endpoint_id, str) or endpoint_id not in config.endpoints: + raise PipelineCompileError( + f"node {node_id!r} references unknown endpoint {endpoint_id!r}" + ) + endpoint = config.endpoints[endpoint_id] + if transport_tags and endpoint.transport not in transport_tags: + raise PipelineCompileError( + f"node {node_id!r} requires transport {transport_tags}, but endpoint " + f"{endpoint_id!r} uses {endpoint.transport!r}" + ) + + +def _validate_actuator_paths( + pipeline: PipelineConfig, specs: Mapping[str, PluginSpec] +) -> None: + """Require a trusted safety gate immediately before every actuator. + + Requiring only *some* earlier gate lets an arbitrary transform mutate the + approved command after validation. The immediate-boundary rule also makes + the trust boundary visible in the graph. + """ + + predecessors: dict[str, set[str]] = {node_id: set() for node_id in pipeline.nodes} + for edge in pipeline.edges: + predecessors[edge.target_node].add(edge.source_node) + + unsafe = sorted( + node_id + for node_id, spec in specs.items() + if "actuator" in spec.tags + and ( + not predecessors[node_id] + or any( + "safety_gate" not in specs[parent].tags + for parent in predecessors[node_id] + ) + ) + ) + if unsafe: + raise PipelineCompileError( + "every actuator input must pass through a safety gate directly; " + f"unsafe actuator node(s): {', '.join(unsafe)}" + ) + + +def _validate_supported_runtime_config(config: AppConfig) -> None: + reserved = { + "max_processes", + "process_start_method", + "health_bind", + "reserved_memory_mb", + } & config.runtime.model_fields_set + if reserved: + raise PipelineCompileError( + "runtime field(s) are reserved but not enforced in v1; remove them: " + + ", ".join(sorted(reserved)) + ) + + +def _validate_supported_pipeline_config( + pipeline_id: str, pipeline: PipelineConfig +) -> None: + if pipeline.priority != "normal": + raise PipelineCompileError( + f"pipeline {pipeline_id!r} sets priority={pipeline.priority!r}, but " + "runtime v1 has no priority scheduler" + ) + + +def _validate_supported_node_config(node_id: str, node: Any) -> None: + execution = node.execution + unsupported_execution: list[str] = [] + if execution.mode not in {"async", "inline"}: + unsupported_execution.append(f"mode={execution.mode!r}") + if execution.concurrency != 1: + unsupported_execution.append(f"concurrency={execution.concurrency}") + if execution.max_in_flight is not None: + unsupported_execution.append(f"max_in_flight={execution.max_in_flight}") + if execution.timeout_s is not None: + unsupported_execution.append(f"timeout_s={execution.timeout_s}") + if not execution.ordered: + unsupported_execution.append("ordered=false") + if unsupported_execution: + raise PipelineCompileError( + f"node {node_id!r} requests execution settings not implemented by " + "runtime v1: " + ", ".join(unsupported_execution) + ) + + resources = node.resources + declared_resources = [ + name + for name, value in resources.model_dump().items() + if (name == "exclusive_gpu" and value is True) + or (name != "exclusive_gpu" and value is not None) + ] + if declared_resources: + raise PipelineCompileError( + f"node {node_id!r} declares resource limits that runtime v1 cannot " + "enforce: " + ", ".join(declared_resources) + ) + + +def _validate_qos_profile(source: str, target: str, qos: Any) -> None: + allowed_overflow = { + "realtime_latest": {"drop_oldest", "drop_newest"}, + "audio_contiguous": {"block", "reject", "error"}, + # Inter-frame codecs cannot silently lose arbitrary packets before + # decode. Dropping is safe only after complete images are produced. + "video_contiguous": {"block", "reject", "error"}, + "request": {"block", "reject", "error"}, + "telemetry": {"block", "drop_oldest", "drop_newest", "reject", "error"}, + } + try: + allowed = allowed_overflow[qos.profile] + except KeyError as exc: + raise PipelineCompileError( + f"edge {source!r} -> {target!r} uses unknown qos profile " + f"{qos.profile!r}; supported profiles: " + + ", ".join(sorted(allowed_overflow)) + ) from exc + if qos.overflow not in allowed: + raise PipelineCompileError( + f"edge {source!r} -> {target!r} profile {qos.profile!r} does not " + f"allow overflow={qos.overflow!r}; allowed: {', '.join(sorted(allowed))}" + ) + + +def _validate_detection_rule_paths( + pipeline: PipelineConfig, + specs: Mapping[str, PluginSpec], + components: Mapping[str, Component], +) -> None: + """Reject rules that can never match their directly connected detector. + + The generic detector validates labels against model registration metadata. + This edge-level check closes the remaining configuration gap between its + selected output labels and a repeat gate's rules without coupling the core + configuration schema to one model backend. + """ + + for edge in pipeline.edges: + source_id = edge.source_node + target_id = edge.target_node + if specs[source_id].plugin_id != "detection.model@1": + continue + if specs[target_id].plugin_id != "detection.repeat_gate@1": + continue + selected = frozenset( + str(label) + for label in getattr(components[source_id], "selected_labels", ()) + ) + rules = getattr(components[target_id], "rules", ()) + if getattr(components[target_id], "alert_image_enabled", False) and not getattr( + components[source_id], "attach_frame", False + ): + raise PipelineCompileError( + f"detection rule node {target_id!r} enables alert_image, but " + f"detector {source_id!r} does not set attach_frame=true" + ) + unknown_by_rule = { + str(rule.rule_id): sorted(set(rule.labels) - selected) + for rule in rules + if set(rule.labels) - selected + } + if unknown_by_rule: + details = "; ".join( + f"{rule_id}: {', '.join(labels)}" + for rule_id, labels in sorted(unknown_by_rule.items()) + ) + raise PipelineCompileError( + f"detection rule node {target_id!r} references labels not emitted " + f"by detector {source_id!r}: {details}" + ) + + +__all__ = [ + "CompiledPipeline", + "PipelineCompileError", + "SUPPORTED_API_VERSION", + "compile_pipeline", + "enabled_pipeline_ids", +] diff --git a/src/cmvr_edge_ai/config/__init__.py b/src/cmvr_edge_ai/config/__init__.py new file mode 100644 index 0000000..1878ef7 --- /dev/null +++ b/src/cmvr_edge_ai/config/__init__.py @@ -0,0 +1,45 @@ +"""Public configuration API.""" + +from .loader import ( + ConfigLoadError, + MissingEnvironmentVariable, + expand_environment, + load_config, + load_config_data, + load_config_text, +) +from .models import ( + AppConfig, + EdgeAIConfig, + EdgeConfig, + EndpointConfig, + ExecutionConfig, + NodeConfig, + PipelineConfig, + PipelinePriority, + QoSConfig, + ResourceConfig, + RuntimeConfig, + StrictConfigModel, +) + +__all__ = [ + "AppConfig", + "ConfigLoadError", + "EdgeAIConfig", + "EdgeConfig", + "EndpointConfig", + "ExecutionConfig", + "MissingEnvironmentVariable", + "NodeConfig", + "PipelineConfig", + "PipelinePriority", + "QoSConfig", + "ResourceConfig", + "RuntimeConfig", + "StrictConfigModel", + "expand_environment", + "load_config", + "load_config_data", + "load_config_text", +] diff --git a/src/cmvr_edge_ai/config/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/config/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..0e42883 Binary files /dev/null and b/src/cmvr_edge_ai/config/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/config/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..dc90f95 Binary files /dev/null and b/src/cmvr_edge_ai/config/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/config/__pycache__/loader.cpython-310.pyc b/src/cmvr_edge_ai/config/__pycache__/loader.cpython-310.pyc new file mode 100644 index 0000000..e49df8a Binary files /dev/null and b/src/cmvr_edge_ai/config/__pycache__/loader.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/config/__pycache__/loader.cpython-312.pyc b/src/cmvr_edge_ai/config/__pycache__/loader.cpython-312.pyc new file mode 100644 index 0000000..d1437f6 Binary files /dev/null and b/src/cmvr_edge_ai/config/__pycache__/loader.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/config/__pycache__/models.cpython-310.pyc b/src/cmvr_edge_ai/config/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..73cd59d Binary files /dev/null and b/src/cmvr_edge_ai/config/__pycache__/models.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/config/__pycache__/models.cpython-312.pyc b/src/cmvr_edge_ai/config/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..df658b1 Binary files /dev/null and b/src/cmvr_edge_ai/config/__pycache__/models.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/config/loader.py b/src/cmvr_edge_ai/config/loader.py new file mode 100644 index 0000000..351df51 --- /dev/null +++ b/src/cmvr_edge_ai/config/loader.py @@ -0,0 +1,145 @@ +"""YAML loading and environment expansion for edge-AI configuration.""" + +from __future__ import annotations + +import os +import re +from collections.abc import Mapping +from pathlib import Path +from typing import Any + +import yaml # type: ignore[import-untyped] + +from .models import AppConfig + + +_ENV_EXACT_PATTERN = re.compile(r"^env://(?P[A-Za-z_][A-Za-z0-9_]*)$") +_ENV_TEMPLATE_PATTERN = re.compile( + r"\$\{(?P[A-Za-z_][A-Za-z0-9_]*)" r"(?::-(?P[^}]*))?\}" +) + + +class ConfigLoadError(ValueError): + """Raised when a configuration document cannot be read or expanded.""" + + +class MissingEnvironmentVariable(ConfigLoadError): + """Raised when a required environment variable is not available.""" + + +def expand_environment( + value: Any, + environ: Mapping[str, str] | None = None, +) -> Any: + """Recursively expand environment references in a YAML-compatible value. + + Supported string forms are: + + * ``${NAME}`` -- ``NAME`` is required; + * ``${NAME:-default}`` -- use ``default`` when ``NAME`` is unset or empty; + * ``env://NAME`` -- resolve the entire value from required ``NAME``. + + Expansion traverses mappings, lists, and tuples without mutating the input. + Environment substitution deliberately returns strings; Pydantic performs + the final conversion according to the target configuration field. + """ + + environment = os.environ if environ is None else environ + + if isinstance(value, str): + return _expand_string(value, environment) + if isinstance(value, Mapping): + return { + key: expand_environment(item, environment) for key, item in value.items() + } + if isinstance(value, list): + return [expand_environment(item, environment) for item in value] + if isinstance(value, tuple): + return tuple(expand_environment(item, environment) for item in value) + return value + + +def load_config_data( + data: Mapping[str, Any], + *, + environ: Mapping[str, str] | None = None, +) -> AppConfig: + """Expand and validate an already parsed configuration mapping.""" + + if not isinstance(data, Mapping): + raise ConfigLoadError("configuration root must be a YAML mapping") + expanded = expand_environment(data, environ) + return AppConfig.model_validate(expanded) + + +def load_config_text( + text: str, + *, + environ: Mapping[str, str] | None = None, +) -> AppConfig: + """Parse, expand, and validate a YAML configuration string.""" + + try: + data = yaml.safe_load(text) + except yaml.YAMLError as exc: + raise ConfigLoadError(f"invalid YAML configuration: {exc}") from exc + + if not isinstance(data, Mapping): + raise ConfigLoadError("configuration root must be a YAML mapping") + return load_config_data(data, environ=environ) + + +def load_config( + path: str | os.PathLike[str], + *, + environ: Mapping[str, str] | None = None, +) -> AppConfig: + """Load, expand, and validate an edge-AI YAML configuration file.""" + + config_path = Path(path) + try: + text = config_path.read_text(encoding="utf-8") + except OSError as exc: + raise ConfigLoadError( + f"unable to read configuration file {config_path}: {exc}" + ) from exc + + try: + return load_config_text(text, environ=environ) + except ConfigLoadError as exc: + raise ConfigLoadError(f"failed to load {config_path}: {exc}") from exc + + +def _expand_string(value: str, environ: Mapping[str, str]) -> str: + exact_match = _ENV_EXACT_PATTERN.fullmatch(value) + if exact_match: + name = exact_match.group("name") + if name not in environ: + raise MissingEnvironmentVariable( + f"required environment variable {name!r} is not set" + ) + return environ[name] + + def replace(match: re.Match[str]) -> str: + name = match.group("name") + default = match.group("default") + current = environ.get(name) + if current is not None and (current != "" or default is None): + return current + if default is not None: + return default + raise MissingEnvironmentVariable( + f"required environment variable {name!r} is not set" + ) + + return _ENV_TEMPLATE_PATTERN.sub(replace, value) + + +__all__ = [ + "ConfigLoadError", + "MissingEnvironmentVariable", + "expand_environment", + "load_config", + "load_config_data", + "load_config_text", +] diff --git a/src/cmvr_edge_ai/config/models.py b/src/cmvr_edge_ai/config/models.py new file mode 100644 index 0000000..77ac8e1 --- /dev/null +++ b/src/cmvr_edge_ai/config/models.py @@ -0,0 +1,363 @@ +"""Strict configuration models for the edge-AI pipeline runtime. + +The models in this module intentionally do not import the component registry or +the runtime. This keeps configuration validation usable by command-line tools, +tests, and deployment checks without initializing any transports or models. +""" + +from __future__ import annotations + +import re +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator + + +_IDENTIFIER_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_-]*$") +_PORT_REFERENCE_PATTERN = re.compile( + r"^(?P[A-Za-z_][A-Za-z0-9_-]*)\." r"(?P[A-Za-z_][A-Za-z0-9_-]*)$" +) + + +class StrictConfigModel(BaseModel): + """Base class shared by all public configuration models.""" + + model_config = ConfigDict( + extra="forbid", + populate_by_name=True, + validate_default=True, + ) + + +class RuntimeConfig(StrictConfigModel): + """Process-wide limits and lifecycle settings for the local runtime.""" + + thread_workers: int = Field(default=4, ge=1, le=256) + max_processes: int = Field(default=1, ge=0, le=64) + process_start_method: Literal["spawn", "forkserver", "fork"] = "spawn" + shutdown_timeout_s: float = Field(default=10.0, gt=0, le=300.0) + health_bind: str | None = None + reserved_memory_mb: int = Field(default=0, ge=0) + + @field_validator("health_bind") + @classmethod + def validate_health_bind(cls, value: str | None) -> str | None: + if value is not None and not value.strip(): + raise ValueError("health_bind must not be empty") + return value + + +class EndpointConfig(StrictConfigModel): + """A named external endpoint used by one or more connector nodes. + + Transport-specific settings belong under ``options`` so that adding QUIC or + a vendor-specific protocol does not require weakening top-level validation. + """ + + transport: str = Field(min_length=1) + target: str | None = None + base_url: str | None = None + bind: str | None = None + tls: bool = False + timeout_s: float = Field(default=5.0, gt=0, le=3600.0) + metadata: dict[str, str] = Field(default_factory=dict) + options: dict[str, Any] = Field(default_factory=dict) + + @field_validator("transport") + @classmethod + def normalize_transport(cls, value: str) -> str: + value = value.strip().lower() + if not value: + raise ValueError("transport must not be empty") + return value + + @field_validator("target", "base_url", "bind") + @classmethod + def reject_empty_endpoint_values(cls, value: str | None) -> str | None: + if value is not None and not value.strip(): + raise ValueError("endpoint address must not be empty") + return value + + @model_validator(mode="after") + def validate_transport_address(self) -> EndpointConfig: + """Validate the transports implemented by the built-in connectors. + + Unknown transport names remain available to third-party plugins. Once + a transport is one of the built-ins, however, accepting an incomplete + endpoint here would only defer a deterministic error until startup. + """ + + if self.transport == "grpc": + if self.target is None: + raise ValueError("gRPC endpoint requires target") + if self.base_url is not None: + raise ValueError("gRPC endpoint must not set base_url") + channel_options = self.options.get("channel_options", {}) + if not isinstance(channel_options, dict): + raise ValueError("gRPC options.channel_options must be a mapping") + invalid_options = sorted( + str(key) + for key, value in channel_options.items() + if not isinstance(key, str) + or isinstance(value, bool) + or not isinstance(value, (int, str)) + ) + if invalid_options: + raise ValueError( + "gRPC channel option values must be int or str: " + + ", ".join(invalid_options) + ) + elif self.transport == "http": + if self.base_url is None: + raise ValueError("HTTP endpoint requires base_url") + if not self.base_url.startswith(("http://", "https://")): + raise ValueError("HTTP base_url must start with http:// or https://") + if self.target is not None: + raise ValueError("HTTP endpoint must not set target") + if "tls" in self.model_fields_set: + raise ValueError( + "HTTP endpoint must not set tls; use an https:// base_url and " + "options.verify_tls (default true)" + ) + verify_tls = self.options.get("verify_tls", True) + if not isinstance(verify_tls, bool): + raise ValueError("HTTP options.verify_tls must be boolean") + return self + + +class ExecutionConfig(StrictConfigModel): + """How a node is scheduled by the runtime.""" + + mode: Literal["async", "inline", "thread", "process", "model_worker"] = "async" + concurrency: int = Field(default=1, ge=1, le=1024) + max_in_flight: int | None = Field(default=None, ge=1, le=65536) + timeout_s: float | None = Field(default=None, gt=0, le=86400.0) + ordered: bool = True + + +class ResourceConfig(StrictConfigModel): + """Optional resource budget declared by a node.""" + + cpu_cores: float | None = Field(default=None, gt=0) + memory_mb: int | None = Field(default=None, gt=0) + gpu_device: int | str | None = None + gpu_memory_mb: int | None = Field(default=None, gt=0) + exclusive_gpu: bool = False + + @field_validator("gpu_device") + @classmethod + def validate_gpu_device(cls, value: int | str | None) -> int | str | None: + if isinstance(value, int) and value < 0: + raise ValueError("gpu_device must be non-negative") + if isinstance(value, str) and not value.strip(): + raise ValueError("gpu_device must not be empty") + return value + + +class QoSConfig(StrictConfigModel): + """Bounded-queue policy for a single directed edge.""" + + # Lossless backpressure is the safe generic default. Real-time sensor + # pipelines must opt into frame dropping explicitly per edge. + profile: str = Field(default="request", min_length=1) + capacity: int = Field(default=1, ge=1, le=1_000_000) + overflow: Literal["drop_oldest", "drop_newest", "block", "reject", "error"] = ( + "block" + ) + max_age_ms: float | None = Field(default=None, gt=0) + put_timeout_ms: float | None = Field(default=None, ge=0) + + @field_validator("profile") + @classmethod + def validate_profile(cls, value: str) -> str: + value = value.strip() + if not value: + raise ValueError("qos profile must not be empty") + return value + + +class NodeConfig(StrictConfigModel): + """A configured component instance in a pipeline graph.""" + + uses: str = Field(min_length=1) + params: dict[str, Any] = Field(default_factory=dict, alias="with") + execution: ExecutionConfig = Field(default_factory=ExecutionConfig) + resources: ResourceConfig = Field(default_factory=ResourceConfig) + enabled: bool = True + + @field_validator("uses") + @classmethod + def validate_component_id(cls, value: str) -> str: + value = value.strip() + if not value: + raise ValueError("uses must not be empty") + return value + + +class EdgeConfig(StrictConfigModel): + """A connection from one ``node.port`` to another ``node.port``.""" + + source: str = Field(alias="from") + target: str = Field(alias="to") + qos: QoSConfig = Field(default_factory=QoSConfig) + + @field_validator("source", "target") + @classmethod + def validate_port_reference(cls, value: str) -> str: + if not _PORT_REFERENCE_PATTERN.fullmatch(value): + raise ValueError( + "port reference must use 'node.port' format; node and port may " + "contain letters, digits, '_' or '-'" + ) + return value + + @property + def source_node(self) -> str: + return self.source.split(".", maxsplit=1)[0] + + @property + def target_node(self) -> str: + return self.target.split(".", maxsplit=1)[0] + + +PipelinePriority = Literal["low", "normal", "high"] | int + + +class PipelineConfig(StrictConfigModel): + """A validated directed acyclic graph of configured nodes.""" + + enabled: bool = True + priority: PipelinePriority = "normal" + nodes: dict[str, NodeConfig] + edges: list[EdgeConfig] = Field(default_factory=list) + + @field_validator("priority") + @classmethod + def validate_priority(cls, value: PipelinePriority) -> PipelinePriority: + if isinstance(value, int) and not 0 <= value <= 100: + raise ValueError("numeric priority must be between 0 and 100") + return value + + @field_validator("nodes") + @classmethod + def validate_node_names(cls, value: dict[str, NodeConfig]) -> dict[str, NodeConfig]: + if not value: + raise ValueError("pipeline must contain at least one node") + invalid = sorted( + name for name in value if not _IDENTIFIER_PATTERN.fullmatch(name) + ) + if invalid: + raise ValueError( + "invalid node name(s): " + + ", ".join(invalid) + + "; names may contain letters, digits, '_' or '-' and may not " + "start with a digit" + ) + return value + + @model_validator(mode="after") + def validate_graph(self) -> PipelineConfig: + node_names = set(self.nodes) + adjacency: dict[str, set[str]] = {name: set() for name in node_names} + indegree = {name: 0 for name in node_names} + + for edge in self.edges: + missing = { + name + for name in (edge.source_node, edge.target_node) + if name not in node_names + } + if missing: + raise ValueError( + f"edge {edge.source!r} -> {edge.target!r} references unknown " + f"node(s): {', '.join(sorted(missing))}" + ) + + # Multiple ports may connect the same pair of nodes. They represent + # one dependency for cycle detection. + if edge.target_node not in adjacency[edge.source_node]: + adjacency[edge.source_node].add(edge.target_node) + indegree[edge.target_node] += 1 + + ready = [name for name, degree in indegree.items() if degree == 0] + visited = 0 + while ready: + current = ready.pop() + visited += 1 + for target in adjacency[current]: + indegree[target] -= 1 + if indegree[target] == 0: + ready.append(target) + + if visited != len(node_names): + cycle_nodes = sorted( + name for name, degree in indegree.items() if degree > 0 + ) + raise ValueError( + "pipeline graph must be acyclic; cycle involves: " + + ", ".join(cycle_nodes) + ) + return self + + +class AppConfig(StrictConfigModel): + """Root configuration document for a cmvr-edge-ai process.""" + + api_version: str = Field(min_length=1) + runtime: RuntimeConfig = Field(default_factory=RuntimeConfig) + endpoints: dict[str, EndpointConfig] = Field(default_factory=dict) + pipelines: dict[str, PipelineConfig] + + @field_validator("api_version") + @classmethod + def validate_api_version(cls, value: str) -> str: + value = value.strip() + if not value: + raise ValueError("api_version must not be empty") + return value + + @field_validator("endpoints") + @classmethod + def validate_endpoint_names( + cls, value: dict[str, EndpointConfig] + ) -> dict[str, EndpointConfig]: + return _validate_named_mapping("endpoint", value) + + @field_validator("pipelines") + @classmethod + def validate_pipeline_names( + cls, value: dict[str, PipelineConfig] + ) -> dict[str, PipelineConfig]: + if not value: + raise ValueError("configuration must contain at least one pipeline") + return _validate_named_mapping("pipeline", value) + + +def _validate_named_mapping(name: str, value: dict[str, Any]) -> dict[str, Any]: + invalid = sorted(key for key in value if not _IDENTIFIER_PATTERN.fullmatch(key)) + if invalid: + raise ValueError( + f"invalid {name} name(s): {', '.join(invalid)}; names may contain " + "letters, digits, '_' or '-' and may not start with a digit" + ) + return value + + +# A descriptive alias for callers that prefer the project's full name. +EdgeAIConfig = AppConfig + + +__all__ = [ + "AppConfig", + "EdgeAIConfig", + "EdgeConfig", + "EndpointConfig", + "ExecutionConfig", + "NodeConfig", + "PipelineConfig", + "PipelinePriority", + "QoSConfig", + "ResourceConfig", + "RuntimeConfig", + "StrictConfigModel", +] diff --git a/src/cmvr_edge_ai/connectors/__init__.py b/src/cmvr_edge_ai/connectors/__init__.py new file mode 100644 index 0000000..495b5ff --- /dev/null +++ b/src/cmvr_edge_ai/connectors/__init__.py @@ -0,0 +1,42 @@ +"""Protocol-specific boundary connectors and their plugin registrations.""" + +from cmvr_edge_ai.plugins import PluginKind, PluginRegistry, PluginSpec + +from .cmvr_es import CmvrAgvCommandSink, CmvrCameraRgbStreamSource +from .platform import PlatformHttpJsonSink + + +def register_connector_plugins(registry: PluginRegistry) -> None: + registry.register( + PluginSpec( + plugin_id="cmvr.grpc.camera_rgb_stream@1", + kind=PluginKind.SOURCE, + factory=CmvrCameraRgbStreamSource, + outputs={"frames": "ImageFrame/v1"}, + description="Start and subscribe to a cmvr-es encoded RGB camera stream", + tags=frozenset({"sensor", "transport:grpc"}), + ) + ) + registry.register( + PluginSpec( + plugin_id="cmvr.grpc.agv_command_sink@1", + kind=PluginKind.SINK, + factory=CmvrAgvCommandSink, + inputs={"commands": "ApprovedRobotCommand/v1"}, + description="Typed, non-retrying cmvr-es AGV command sink", + tags=frozenset({"actuator", "transport:grpc"}), + ) + ) + registry.register( + PluginSpec( + plugin_id="platform.http_json_sink@1", + kind=PluginKind.SINK, + factory=PlatformHttpJsonSink, + inputs={"input": "*"}, + description="POST protocol-neutral envelopes as JSON", + tags=frozenset({"telemetry", "transport:http"}), + ) + ) + + +__all__ = ["register_connector_plugins"] diff --git a/src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..7aee741 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..26e5629 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-310.pyc b/src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-310.pyc new file mode 100644 index 0000000..20b6e42 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-312.pyc b/src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-312.pyc new file mode 100644 index 0000000..f533d6d Binary files /dev/null and b/src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/_context.py b/src/cmvr_edge_ai/connectors/_context.py new file mode 100644 index 0000000..81c2c40 --- /dev/null +++ b/src/cmvr_edge_ai/connectors/_context.py @@ -0,0 +1,94 @@ +"""Helpers for resolving named endpoints from component context.""" + +from __future__ import annotations + +from collections.abc import Mapping +from pathlib import Path +from typing import Any + +from cmvr_edge_ai.core.component import ComponentContext +from cmvr_edge_ai.transports.grpc import GrpcChannelSettings +from cmvr_edge_ai.transports.http import HttpClientSettings + + +class ConnectorConfigurationError(ValueError): + pass + + +def resolve_endpoint(context: ComponentContext, endpoint_id: str) -> Any: + endpoints = context.metadata.get("endpoints") + if not isinstance(endpoints, Mapping): + raise ConnectorConfigurationError( + "component context does not contain a named endpoints mapping" + ) + try: + return endpoints[endpoint_id] + except KeyError as exc: + raise ConnectorConfigurationError( + f"unknown endpoint referenced by connector: {endpoint_id}" + ) from exc + + +def endpoint_value(endpoint: Any, name: str, default: Any = None) -> Any: + if isinstance(endpoint, Mapping): + return endpoint.get(name, default) + return getattr(endpoint, name, default) + + +def grpc_channel_settings(endpoint: Any) -> GrpcChannelSettings: + target = endpoint_value(endpoint, "target") + if not target: + raise ConnectorConfigurationError("gRPC endpoint requires target") + options = endpoint_value(endpoint, "options", {}) or {} + if not isinstance(options, Mapping): + raise ConnectorConfigurationError("gRPC endpoint options must be a mapping") + + channel_options: list[tuple[str, int | str]] = [] + configured_options = options.get("channel_options", {}) + if isinstance(configured_options, Mapping): + channel_options.extend( + (str(key), value) for key, value in configured_options.items() + ) + max_receive_mb = options.get("max_receive_mb") + if max_receive_mb is not None: + channel_options.append( + ( + "grpc.max_receive_message_length", + int(float(max_receive_mb) * 1024 * 1024), + ) + ) + max_send_mb = options.get("max_send_mb") + if max_send_mb is not None: + channel_options.append( + ("grpc.max_send_message_length", int(float(max_send_mb) * 1024 * 1024)) + ) + + root_certificates = options.get("root_certificates") + return GrpcChannelSettings( + target=str(target), + tls=bool(endpoint_value(endpoint, "tls", False)), + root_certificates=(Path(root_certificates) if root_certificates else None), + options=tuple(channel_options), + ) + + +def http_client_settings(endpoint: Any) -> HttpClientSettings: + base_url = endpoint_value(endpoint, "base_url") + if not base_url: + raise ConnectorConfigurationError("HTTP endpoint requires base_url") + metadata = endpoint_value(endpoint, "metadata", {}) or {} + options = endpoint_value(endpoint, "options", {}) or {} + if not isinstance(options, Mapping): + raise ConnectorConfigurationError("HTTP endpoint options must be a mapping") + verify_tls = options.get("verify_tls", True) + if not isinstance(verify_tls, bool): + raise ConnectorConfigurationError("HTTP options.verify_tls must be boolean") + return HttpClientSettings( + base_url=str(base_url), + timeout_s=float(endpoint_value(endpoint, "timeout_s", 5.0)), + headers=tuple((str(key), str(value)) for key, value in metadata.items()), + # HTTPS verification is secure by default. The generic ``tls`` flag is + # used by gRPC channel construction and must never silently disable + # certificate verification for an https:// URL. + verify_tls=verify_tls, + ) diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__init__.py b/src/cmvr_edge_ai/connectors/cmvr_es/__init__.py new file mode 100644 index 0000000..1114b1f --- /dev/null +++ b/src/cmvr_edge_ai/connectors/cmvr_es/__init__.py @@ -0,0 +1,13 @@ +"""Semantic adapters for cmvr-es services.""" + +from .agv import CmvrAgvCommandSink +from .camera import CmvrCameraRgbStreamSource +from .errors import CmvrApiPackageError, CmvrConnectorError, CmvrServiceError + +__all__ = [ + "CmvrApiPackageError", + "CmvrAgvCommandSink", + "CmvrCameraRgbStreamSource", + "CmvrConnectorError", + "CmvrServiceError", +] diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..2615670 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..37efdbf Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-310.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-310.pyc new file mode 100644 index 0000000..1d96842 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-312.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-312.pyc new file mode 100644 index 0000000..7c4ce01 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-310.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-310.pyc new file mode 100644 index 0000000..5fee445 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-312.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-312.pyc new file mode 100644 index 0000000..6048494 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-310.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-310.pyc new file mode 100644 index 0000000..19a576d Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-312.pyc b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-312.pyc new file mode 100644 index 0000000..c1832ae Binary files /dev/null and b/src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/agv.py b/src/cmvr_edge_ai/connectors/cmvr_es/agv.py new file mode 100644 index 0000000..c2f82ba --- /dev/null +++ b/src/cmvr_edge_ai/connectors/cmvr_es/agv.py @@ -0,0 +1,230 @@ +"""Typed, non-retrying AGV command sink for cmvr-es.""" + +from __future__ import annotations + +import logging +import math +from collections.abc import Mapping +from importlib import import_module +from time import time_ns +from typing import Any + +from cmvr_edge_ai.contracts import ApprovedRobotCommand +from cmvr_edge_ai.core.component import ComponentContext, Sink +from cmvr_edge_ai.core.envelope import Envelope +from cmvr_edge_ai.transports.grpc import GrpcChannelPool + +from .._context import endpoint_value, grpc_channel_settings, resolve_endpoint +from .errors import CmvrApiPackageError, CmvrServiceError + + +class CmvrAgvCommandSink(Sink): + """Map an allowlisted ``RobotCommand`` to explicit AgvService methods. + + The sink deliberately has no generic method-name dispatch and performs no + automatic retry. Motion-command retry requires an idempotency contract + that the current cmvr-es API does not provide. + """ + + _DIRECT_ACTIONS = { + "emergency_stop": "emergencyStop", + "clear_fault": "clearFault", + "pause_navigation": "pauseNavigation", + "resume_navigation": "resumeNavigation", + "cancel_navigation": "cancelNavigation", + "stop_velocity_control": "stopVelocityControl", + "stop_mapping": "stopMapping", + } + + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + self._node_id = node_id + self._endpoint_id = str(params.get("endpoint", "cmvr_es")) + configured_device = params.get("device_id") + if not isinstance(configured_device, str) or not configured_device.strip(): + raise ValueError("AGV command sink requires a fixed, non-empty device_id") + self._device_id = configured_device.strip() + self._timeout_s: float | None = None + self._unsafe_allow_unleased_velocity = _strict_bool( + params, + "unsafe_allow_unleased_velocity", + default=False, + ) + self._velocity_limits = _parse_velocity_limits(params.get("velocity_limits")) + if self._unsafe_allow_unleased_velocity and self._velocity_limits is None: + raise ValueError( + "unsafe_allow_unleased_velocity requires explicit velocity_limits" + ) + self._velocity_active = False + self._logger = logging.getLogger(__name__) + self._stub: Any = None + self._common_pb2: Any = None + self._agv_command_pb2: Any = None + self._agv_pb2: Any = None + + async def setup(self, context: ComponentContext) -> None: + endpoint = resolve_endpoint(context, self._endpoint_id) + pool = context.metadata.get("grpc_channel_pool") + if not isinstance(pool, GrpcChannelPool): + raise TypeError("component context is missing GrpcChannelPool") + channel = await pool.get(self._endpoint_id, grpc_channel_settings(endpoint)) + self._timeout_s = float(endpoint_value(endpoint, "timeout_s", 5.0)) + try: + self._common_pb2 = import_module("cmvr.api.common_pb2") + self._agv_command_pb2 = import_module("cmvr.api.agv_command_pb2") + self._agv_pb2 = import_module("cmvr.msgs.agv_pb2") + service = import_module("cmvr.api.agv_service_pb2_grpc") + except ImportError as exc: + raise CmvrApiPackageError( + "cmvr Python API bindings are missing; run " + "scripts/generate_cmvr_stubs.py or install the cmvr-api wheel" + ) from exc + self._stub = service.AgvServiceStub(channel) + + async def consume( + self, envelope: Envelope[Any], input_port: str = "commands" + ) -> None: + del input_port + approved = envelope.payload + if not isinstance(approved, ApprovedRobotCommand): + raise TypeError( + f"{self._node_id} expected ApprovedRobotCommand, " + f"got {type(approved).__name__}" + ) + command = approved.command + if envelope.is_expired() or command.valid_until_ns <= time_ns(): + return + if command.device_id != self._device_id: + raise PermissionError( + f"command targets {command.device_id!r}, sink is bound to " + f"{self._device_id!r}" + ) + if self._stub is None: + raise RuntimeError("AGV sink has not been set up") + + remaining_s = (command.valid_until_ns - time_ns()) / 1_000_000_000 + if remaining_s <= 0: + return + rpc_timeout = min(self._timeout_s or remaining_s, remaining_s) + + if command.action in self._DIRECT_ACTIONS: + request = self._command_header() + rpc = getattr(self._stub, self._DIRECT_ACTIONS[command.action]) + elif command.action == "set_velocity": + if not self._unsafe_allow_unleased_velocity: + raise PermissionError( + "set_velocity is disabled until cmvr-es enforces a server-side " + "lease; unsafe override must be explicit" + ) + assert self._velocity_limits is not None + vx = _bounded_number(command.arguments, "vx", self._velocity_limits) + vy = _bounded_number(command.arguments, "vy", self._velocity_limits) + wz = _bounded_number(command.arguments, "wz", self._velocity_limits) + request = self._agv_command_pb2.AgvSetVelocityCommand.Request( + header=self._command_header(), + velocity=self._agv_pb2.AgvVelocity(vx=vx, vy=vy, wz=wz), + ) + rpc = self._stub.setVelocity + else: + raise PermissionError(f"unsupported AGV action: {command.action}") + + response = await rpc(request, timeout=rpc_timeout) + feedback = getattr(response, "header", response) + if not feedback.success: + raise CmvrServiceError( + feedback.error_message + or f"cmvr-es rejected AGV action {command.action!r}" + ) + if command.action == "set_velocity": + self._velocity_active = True + elif command.action in {"stop_velocity_control", "emergency_stop"}: + self._velocity_active = False + + async def stop(self) -> None: + if not self._velocity_active or self._stub is None: + return + try: + request = self._command_header() + response = await self._stub.stopVelocityControl( + request, timeout=self._timeout_s + ) + feedback = getattr(response, "header", response) + if not feedback.success: + self._logger.error( + "cmvr-es rejected stopVelocityControl during sink shutdown: %s", + feedback.error_message, + ) + except Exception: + self._logger.exception( + "failed to stop AGV velocity during sink shutdown; " + "server-side lease is required for crash safety" + ) + finally: + self._velocity_active = False + + def _command_header(self) -> Any: + header = self._common_pb2.CommandHeader.Request(device_id=self._device_id) + # The current cmvr-es API has no valid-until/lease field. Sending the + # request time is useful for diagnostics but is not a server-side lease. + header.timestamp.GetCurrentTime() + return header + + +def _bounded_number( + arguments: Mapping[str, Any], + name: str, + limits: Mapping[str, tuple[float, float]], +) -> float: + if name not in arguments: + raise ValueError(f"set_velocity requires argument {name!r}") + value = float(arguments[name]) + if not math.isfinite(value): + raise ValueError(f"set_velocity argument {name!r} must be finite") + minimum, maximum = limits[name] + if not minimum <= value <= maximum: + raise PermissionError( + f"set_velocity argument {name!r}={value!r} is outside " + f"[{minimum}, {maximum}]" + ) + return value + + +def _parse_velocity_limits( + value: Any, +) -> dict[str, tuple[float, float]] | None: + if value is None: + return None + if not isinstance(value, Mapping): + raise ValueError("velocity_limits must be a mapping") + missing = {"vx", "vy", "wz"} - set(value) + if missing: + raise ValueError( + "velocity_limits must define vx, vy and wz; missing: " + + ", ".join(sorted(missing)) + ) + parsed: dict[str, tuple[float, float]] = {} + for name in ("vx", "vy", "wz"): + bounds = value[name] + if ( + not isinstance(bounds, Mapping) + or "min" not in bounds + or "max" not in bounds + ): + raise ValueError(f"velocity_limits.{name} requires min and max") + minimum = float(bounds["min"]) + maximum = float(bounds["max"]) + if not math.isfinite(minimum) or not math.isfinite(maximum): + raise ValueError("velocity limits must be finite") + if minimum > maximum: + raise ValueError(f"velocity_limits.{name} min exceeds max") + parsed[name] = (minimum, maximum) + return parsed + + +def _strict_bool(params: Mapping[str, Any], name: str, *, default: bool) -> bool: + value = params.get(name, default) + if type(value) is not bool: + raise ValueError(f"{name} must be a boolean, not {value!r}") + return value + + +__all__ = ["CmvrAgvCommandSink"] diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/camera.py b/src/cmvr_edge_ai/connectors/cmvr_es/camera.py new file mode 100644 index 0000000..7c5c084 --- /dev/null +++ b/src/cmvr_edge_ai/connectors/cmvr_es/camera.py @@ -0,0 +1,591 @@ +"""Typed adapter for the current cmvr-es RGB camera stream.""" + +from __future__ import annotations + +import asyncio +import logging +import math +from collections.abc import Mapping +from dataclasses import dataclass +from importlib import import_module +from time import monotonic_ns, perf_counter +from typing import Any +from uuid import uuid4 + +from cmvr_edge_ai.contracts import CameraIntrinsics, ImageFrame +from cmvr_edge_ai.core.component import ComponentContext, Emission, Source +from cmvr_edge_ai.core.envelope import Envelope +from cmvr_edge_ai.transports.grpc import GrpcChannelPool + +from .._context import endpoint_value, grpc_channel_settings, resolve_endpoint +from .errors import CmvrApiPackageError, CmvrServiceError + + +_LOGGER = logging.getLogger(__name__) + + +@dataclass(slots=True) +class _StreamStats: + session_id: str + opened_ns: int + window_started_ns: int + frames: int = 0 + received_bytes: int = 0 + key_frames: int = 0 + window_frames: int = 0 + window_bytes: int = 0 + window_key_frames: int = 0 + last_frame_ns: int | None = None + last_remote_sequence: int | None = None + codec: str = "unknown" + width: int = 0 + height: int = 0 + + +class CmvrCameraRgbStreamSource(Source): + """Start a cmvr-es camera and subscribe to ``GetRGBImageStream``. + + cmvr-es currently reads one subscription request and then streams until the + client cancels. The request side remains open until shutdown so this + adapter also remains compatible with a future true bidirectional contract. + Every initial/reconnect session calls ``StartCamera`` first because the + stream RPC starts encoding but does not open the physical camera. + """ + + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + allowed_params = { + "endpoint", + "device_id", + "pixel_format", + "reconnect", + "reconnect_initial_s", + "reconnect_max_s", + "max_reconnect_attempts", + "stream_log_interval_s", + } + unknown = set(params) - allowed_params + if unknown: + raise ValueError( + "unknown camera source parameter(s): " + + ", ".join(sorted(str(value) for value in unknown)) + ) + self._node_id = node_id + self._endpoint_id = str(params.get("endpoint", "cmvr_es")) + device_id = params.get("device_id") + if not isinstance(device_id, str) or not device_id.strip(): + raise ValueError("camera source requires a non-empty device_id") + pixel_format = params.get("pixel_format", "BGR8") + if not isinstance(pixel_format, str) or not pixel_format.strip(): + raise ValueError("pixel_format must be a non-empty string") + self._device_id = device_id.strip() + self._pixel_format = pixel_format.strip() + self._reconnect = _strict_bool(params, "reconnect", default=True) + self._reconnect_initial_s = _positive_float( + params, "reconnect_initial_s", default=0.5 + ) + self._reconnect_max_s = _positive_float(params, "reconnect_max_s", default=10.0) + if self._reconnect_max_s < self._reconnect_initial_s: + raise ValueError("reconnect_max_s must be >= reconnect_initial_s") + max_attempts = params.get("max_reconnect_attempts") + if max_attempts is None: + self._max_reconnect_attempts: int | None = None + elif isinstance(max_attempts, bool) or not isinstance(max_attempts, int): + raise ValueError("max_reconnect_attempts must be an integer or null") + elif max_attempts < 0: + raise ValueError("max_reconnect_attempts must not be negative") + else: + self._max_reconnect_attempts = max_attempts + self._stream_log_interval_s = _positive_float( + params, + "stream_log_interval_s", + default=30.0, + ) + self._pool: GrpcChannelPool | None = None + self._stub: Any = None + self._start_request: Any = None + self._stream_request: Any = None + self._call: Any = None + self._shutdown_event: asyncio.Event | None = None + self._rpc_timeout_s: float | None = None + self._active_session_id: str | None = None + self._total_frames = 0 + self._total_bytes = 0 + self._stopped = False + + async def setup(self, context: ComponentContext) -> None: + endpoint = resolve_endpoint(context, self._endpoint_id) + pool = context.metadata.get("grpc_channel_pool") + if not isinstance(pool, GrpcChannelPool): + raise TypeError("component context is missing GrpcChannelPool") + self._pool = pool + channel = await pool.get(self._endpoint_id, grpc_channel_settings(endpoint)) + self._shutdown_event = context.shutdown_event + self._rpc_timeout_s = float(endpoint_value(endpoint, "timeout_s", 5.0)) + + try: + common_pb2 = import_module("cmvr.api.common_pb2") + camera_command_pb2 = import_module("cmvr.api.camera_command_pb2") + camera_service_pb2_grpc = import_module("cmvr.api.camera_service_pb2_grpc") + except ImportError as exc: + raise CmvrApiPackageError( + "cmvr Python API bindings are missing; run " + "scripts/generate_cmvr_stubs.py or install the cmvr-api wheel" + ) from exc + + start_header = common_pb2.CommandHeader.Request(device_id=self._device_id) + stream_header = common_pb2.CommandHeader.Request(device_id=self._device_id) + self._start_request = camera_command_pb2.StartCameraCommand.Request( + header=start_header + ) + self._stream_request = camera_command_pb2.GetRGBImageStreamCommand.Request( + header=stream_header + ) + self._stub = camera_service_pb2_grpc.CameraServiceStub(channel) + _LOGGER.info( + "camera source configured node=%s endpoint=%s device=%s " + "rpc_timeout_s=%.3f reconnect=%s reconnect_initial_s=%.3f " + "reconnect_max_s=%.3f stream_log_interval_s=%.3f", + self._node_id, + self._endpoint_id, + self._device_id, + self._rpc_timeout_s, + self._reconnect, + self._reconnect_initial_s, + self._reconnect_max_s, + self._stream_log_interval_s, + ) + + async def messages(self): # type: ignore[no-untyped-def] + if ( + self._stub is None + or self._start_request is None + or self._stream_request is None + or self._shutdown_event is None + or self._rpc_timeout_s is None + ): + raise RuntimeError("camera source has not been set up") + shutdown_event = self._shutdown_event + reconnect_attempts = 0 + reconnect_delay_s = self._reconnect_initial_s + local_sequence = 0 + + while not shutdown_event.is_set(): + stream_session_id = uuid4().hex + self._active_session_id = stream_session_id + stats: _StreamStats | None = None + progress_task: asyncio.Task[None] | None = None + + async def subscription_requests(): # type: ignore[no-untyped-def] + yield self._stream_request + await shutdown_event.wait() + + try: + await self._start_camera( + stream_session_id, + reconnect_attempt=reconnect_attempts, + ) + opened_ns = monotonic_ns() + stats = _StreamStats( + session_id=stream_session_id, + opened_ns=opened_ns, + window_started_ns=opened_ns, + ) + _LOGGER.info( + "camera stream opening node=%s endpoint=%s device=%s " + "session=%s reconnect_attempt=%s waiting_for_first_frame=true", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + reconnect_attempts, + ) + self._call = self._stub.GetRGBImageStream(subscription_requests()) + progress_task = asyncio.create_task( + self._log_stream_progress(stats, shutdown_event), + name=f"{self._node_id}:camera-stream-progress", + ) + received_in_session = False + async for response in self._call: + if shutdown_event.is_set(): + return + if not response.header.success: + _LOGGER.warning( + "camera stream feedback error node=%s endpoint=%s " + "device=%s session=%s error=%s", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + response.header.error_message + or "cmvr-es camera stream failed", + ) + raise CmvrServiceError( + response.header.error_message + or "cmvr-es camera stream failed" + ) + frame = response.color_frame + intrinsics = response.intrinsics + frame_data = bytes(frame.data) + frame_received_ns = monotonic_ns() + remote_sequence = int(response.seq_no) + stats.frames += 1 + stats.received_bytes += len(frame_data) + stats.key_frames += int(bool(frame.is_key_frame)) + stats.window_frames += 1 + stats.window_bytes += len(frame_data) + stats.window_key_frames += int(bool(frame.is_key_frame)) + stats.last_frame_ns = frame_received_ns + stats.last_remote_sequence = remote_sequence + stats.codec = frame.codec or "none" + stats.width = int(frame.width) + stats.height = int(frame.height) + self._total_frames += 1 + self._total_bytes += len(frame_data) + if not received_in_session: + _LOGGER.info( + "camera stream first frame node=%s endpoint=%s device=%s " + "session=%s reconnect_attempt=%s time_to_first_frame_ms=%.2f " + "remote_sequence=%s codec=%s width=%s height=%s bytes=%s " + "key_frame=%s", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + reconnect_attempts, + (frame_received_ns - opened_ns) / 1_000_000, + remote_sequence, + stats.codec, + stats.width, + stats.height, + len(frame_data), + bool(frame.is_key_frame), + ) + received_in_session = True + reconnect_attempts = 0 + reconnect_delay_s = self._reconnect_initial_s + captured_at_ns = _protobuf_timestamp_ns(response.header.timestamp) + payload = ImageFrame( + data=frame_data, + width=stats.width, + height=stats.height, + pixel_format=self._pixel_format, + codec=stats.codec, + is_key_frame=bool(frame.is_key_frame), + intrinsics=CameraIntrinsics( + fx=float(intrinsics.fx), + fy=float(intrinsics.fy), + cx=float(intrinsics.cx), + cy=float(intrinsics.cy), + coefficients=tuple( + float(value) for value in intrinsics.coeffs + ), + ), + ) + sequence = local_sequence + local_sequence += 1 + yield Emission( + "frames", + Envelope( + payload=payload, + schema_name="ImageFrame", + schema_version=1, + source_id=self._device_id, + sequence=sequence, + captured_at_ns=captured_at_ns, + session_id=stream_session_id, + attributes={ + "transport": "grpc", + "endpoint": self._endpoint_id, + "remote_sequence": remote_sequence, + "stream_session_id": stream_session_id, + }, + ), + ) + if shutdown_event.is_set(): + return + detail = ( + "cmvr-es camera stream ended" + if received_in_session + else "cmvr-es camera stream ended before the first frame" + ) + raise CmvrServiceError(detail) + except asyncio.CancelledError: + _LOGGER.info( + "camera stream cancelled node=%s endpoint=%s device=%s " + "session=%s session_frames=%s", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + stats.frames if stats is not None else 0, + ) + raise + except Exception as exc: + if shutdown_event.is_set(): + return + if not self._reconnect: + _LOGGER.error( + "camera stream failed node=%s endpoint=%s device=%s " + "session=%s reconnect=false error_type=%s error=%s", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + type(exc).__name__, + exc, + ) + raise + reconnect_attempts += 1 + if ( + self._max_reconnect_attempts is not None + and reconnect_attempts > self._max_reconnect_attempts + ): + _LOGGER.error( + "camera stream reconnect exhausted node=%s endpoint=%s " + "device=%s session=%s attempts=%s error_type=%s error=%s", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + reconnect_attempts, + type(exc).__name__, + exc, + ) + raise CmvrServiceError( + "cmvr-es camera stream exceeded max_reconnect_attempts" + ) from exc + _LOGGER.warning( + "camera stream disconnected node=%s endpoint=%s device=%s " + "session=%s session_frames=%s attempt=%s retry_in_s=%.3f " + "error_type=%s error=%s", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + stats.frames if stats is not None else 0, + reconnect_attempts, + reconnect_delay_s, + type(exc).__name__, + exc, + ) + try: + await asyncio.wait_for( + shutdown_event.wait(), timeout=reconnect_delay_s + ) + return + except TimeoutError: + reconnect_delay_s = min( + self._reconnect_max_s, reconnect_delay_s * 2.0 + ) + finally: + if progress_task is not None: + progress_task.cancel() + await asyncio.gather(progress_task, return_exceptions=True) + if self._call is not None: + self._call.cancel() + self._call = None + if stats is not None: + closed_ns = monotonic_ns() + _LOGGER.info( + "camera stream closed node=%s endpoint=%s device=%s " + "session=%s session_frames=%s session_bytes=%s " + "uptime_s=%.3f first_frame_received=%s", + self._node_id, + self._endpoint_id, + self._device_id, + stream_session_id, + stats.frames, + stats.received_bytes, + max(0.0, (closed_ns - stats.opened_ns) / 1_000_000_000), + stats.frames > 0, + ) + self._active_session_id = None + + async def stop(self) -> None: + if self._stopped: + return + self._stopped = True + _LOGGER.info( + "camera source stopping node=%s endpoint=%s device=%s " + "active_session=%s total_frames=%s total_bytes=%s", + self._node_id, + self._endpoint_id, + self._device_id, + self._active_session_id or "none", + self._total_frames, + self._total_bytes, + ) + if self._call is not None: + self._call.cancel() + self._call = None + _LOGGER.info( + "camera source stopped node=%s endpoint=%s device=%s " + "total_frames=%s total_bytes=%s remote_camera_stopped=false", + self._node_id, + self._endpoint_id, + self._device_id, + self._total_frames, + self._total_bytes, + ) + + async def _start_camera( + self, + session_id: str, + *, + reconnect_attempt: int, + ) -> None: + assert self._stub is not None + assert self._start_request is not None + assert self._rpc_timeout_s is not None + reason = ( + "initial" + if self._total_frames == 0 and reconnect_attempt == 0 + else "reconnect" + ) + _LOGGER.info( + "camera start requested node=%s endpoint=%s device=%s session=%s " + "reason=%s reconnect_attempt=%s timeout_s=%.3f", + self._node_id, + self._endpoint_id, + self._device_id, + session_id, + reason, + reconnect_attempt, + self._rpc_timeout_s, + ) + started = perf_counter() + try: + response = await self._stub.StartCamera( + self._start_request, + timeout=self._rpc_timeout_s, + ) + except asyncio.CancelledError: + raise + except Exception as exc: + _LOGGER.warning( + "camera start failed node=%s endpoint=%s device=%s session=%s " + "reason=%s error_type=%s error=%s", + self._node_id, + self._endpoint_id, + self._device_id, + session_id, + reason, + type(exc).__name__, + exc, + ) + raise + + feedback = getattr(response, "header", response) + if not bool(getattr(feedback, "success", False)): + detail = ( + getattr(feedback, "error_message", "") + or "cmvr-es rejected StartCamera" + ) + _LOGGER.warning( + "camera start failed node=%s endpoint=%s device=%s session=%s " + "reason=%s error_type=CmvrServiceError error=%s", + self._node_id, + self._endpoint_id, + self._device_id, + session_id, + reason, + detail, + ) + raise CmvrServiceError(detail) + + _LOGGER.info( + "camera start succeeded node=%s endpoint=%s device=%s session=%s " + "reason=%s elapsed_ms=%.2f", + self._node_id, + self._endpoint_id, + self._device_id, + session_id, + reason, + (perf_counter() - started) * 1000.0, + ) + + async def _log_stream_progress( + self, + stats: _StreamStats, + shutdown_event: asyncio.Event, + ) -> None: + while not shutdown_event.is_set(): + try: + await asyncio.wait_for( + shutdown_event.wait(), + timeout=self._stream_log_interval_s, + ) + return + except TimeoutError: + now_ns = monotonic_ns() + window_s = max( + (now_ns - stats.window_started_ns) / 1_000_000_000, + 1e-9, + ) + last_frame_age_s = ( + -1.0 + if stats.last_frame_ns is None + else max(0.0, (now_ns - stats.last_frame_ns) / 1_000_000_000) + ) + _LOGGER.info( + "camera stream progress node=%s endpoint=%s device=%s " + "session=%s first_frame_received=%s total_frames=%s " + "window_frames=%s window_fps=%.2f window_bytes=%s " + "bitrate_kbps=%.2f window_key_frames=%s last_remote_sequence=%s " + "codec=%s width=%s height=%s last_frame_age_s=%.3f", + self._node_id, + self._endpoint_id, + self._device_id, + stats.session_id, + stats.frames > 0, + stats.frames, + stats.window_frames, + stats.window_frames / window_s, + stats.window_bytes, + stats.window_bytes * 8.0 / window_s / 1000.0, + stats.window_key_frames, + ( + stats.last_remote_sequence + if stats.last_remote_sequence is not None + else "none" + ), + stats.codec, + stats.width, + stats.height, + last_frame_age_s, + ) + stats.window_frames = 0 + stats.window_bytes = 0 + stats.window_key_frames = 0 + stats.window_started_ns = now_ns + + +def _protobuf_timestamp_ns(timestamp: Any) -> int | None: + seconds = int(getattr(timestamp, "seconds", 0)) + nanos = int(getattr(timestamp, "nanos", 0)) + if seconds == 0 and nanos == 0: + return None + return seconds * 1_000_000_000 + nanos + + +def _strict_bool(params: Mapping[str, Any], name: str, *, default: bool) -> bool: + value = params.get(name, default) + if type(value) is not bool: + raise ValueError(f"{name} must be a boolean") + return value + + +def _positive_float(params: Mapping[str, Any], name: str, *, default: float) -> float: + value = params.get(name, default) + if isinstance(value, bool): + raise ValueError(f"{name} must be a positive number") + try: + parsed = float(value) + except (TypeError, ValueError) as exc: + raise ValueError(f"{name} must be a positive number") from exc + if not math.isfinite(parsed) or parsed <= 0: + raise ValueError(f"{name} must be a positive number") + return parsed + + +__all__ = ["CmvrCameraRgbStreamSource"] diff --git a/src/cmvr_edge_ai/connectors/cmvr_es/errors.py b/src/cmvr_edge_ai/connectors/cmvr_es/errors.py new file mode 100644 index 0000000..0ea0b2a --- /dev/null +++ b/src/cmvr_edge_ai/connectors/cmvr_es/errors.py @@ -0,0 +1,13 @@ +"""Errors raised by cmvr-es semantic connectors.""" + + +class CmvrConnectorError(RuntimeError): + pass + + +class CmvrApiPackageError(CmvrConnectorError): + pass + + +class CmvrServiceError(CmvrConnectorError): + pass diff --git a/src/cmvr_edge_ai/connectors/platform/__init__.py b/src/cmvr_edge_ai/connectors/platform/__init__.py new file mode 100644 index 0000000..a081112 --- /dev/null +++ b/src/cmvr_edge_ai/connectors/platform/__init__.py @@ -0,0 +1,5 @@ +"""Northbound platform connectors.""" + +from .http_json import PlatformHttpJsonSink + +__all__ = ["PlatformHttpJsonSink"] diff --git a/src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..091c4d6 Binary files /dev/null and b/src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..79d4d5a Binary files /dev/null and b/src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-310.pyc b/src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-310.pyc new file mode 100644 index 0000000..41372dd Binary files /dev/null and b/src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-312.pyc b/src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-312.pyc new file mode 100644 index 0000000..4ff071c Binary files /dev/null and b/src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/connectors/platform/http_json.py b/src/cmvr_edge_ai/connectors/platform/http_json.py new file mode 100644 index 0000000..e549d99 --- /dev/null +++ b/src/cmvr_edge_ai/connectors/platform/http_json.py @@ -0,0 +1,260 @@ +"""HTTP JSON telemetry sink for northbound platforms.""" + +from __future__ import annotations + +import base64 +import dataclasses +import logging +import asyncio +import math +from collections.abc import Mapping, Sequence +from enum import Enum +from typing import Any + +from cmvr_edge_ai.contracts import EncodedImage +from cmvr_edge_ai.core.component import ComponentContext, Sink +from cmvr_edge_ai.core.envelope import Envelope +from cmvr_edge_ai.transports.http import HttpClientPool + +from .._context import http_client_settings, resolve_endpoint + + +_LOGGER = logging.getLogger(__name__) +_DEFAULT_RETRY_STATUSES = frozenset({408, 425, 429, 500, 502, 503, 504}) + + +class PlatformHttpJsonSink(Sink): + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + allowed_params = { + "endpoint", + "path", + "failure_mode", + "max_attempts", + "retry_initial_s", + "retry_max_s", + "retry_statuses", + } + unknown = set(params) - allowed_params + if unknown: + raise ValueError( + "unknown HTTP sink parameter(s): " + + ", ".join(sorted(str(value) for value in unknown)) + ) + self._node_id = node_id + self._endpoint_id = str(params["endpoint"]) + path = params.get("path", "/") + if not isinstance(path, str) or not path.startswith("/"): + raise ValueError("HTTP sink path must be an absolute path starting with /") + self._path = path + failure_mode = params.get("failure_mode", "raise") + if not isinstance(failure_mode, str) or failure_mode not in { + "raise", + "log_and_drop", + }: + raise ValueError("failure_mode must be 'raise' or 'log_and_drop'") + self._failure_mode = failure_mode + self._max_attempts = _positive_int(params, "max_attempts", default=3) + self._retry_initial_s = _non_negative_float( + params, "retry_initial_s", default=0.25 + ) + self._retry_max_s = _non_negative_float(params, "retry_max_s", default=5.0) + if self._retry_max_s < self._retry_initial_s: + raise ValueError("retry_max_s must be >= retry_initial_s") + retry_statuses = params.get("retry_statuses", _DEFAULT_RETRY_STATUSES) + if isinstance(retry_statuses, (str, bytes, bytearray, Mapping)): + raise ValueError("retry_statuses must be a sequence of HTTP status codes") + try: + parsed_statuses = frozenset(int(value) for value in retry_statuses) + except (TypeError, ValueError) as exc: + raise ValueError( + "retry_statuses must be a sequence of HTTP status codes" + ) from exc + if any(status < 100 or status > 599 for status in parsed_statuses): + raise ValueError("retry_statuses values must be between 100 and 599") + self._retry_statuses = parsed_statuses + self._client: Any = None + + async def setup(self, context: ComponentContext) -> None: + endpoint = resolve_endpoint(context, self._endpoint_id) + pool = context.metadata.get("http_client_pool") + if not isinstance(pool, HttpClientPool): + raise TypeError("component context is missing HttpClientPool") + self._client = await pool.get(self._endpoint_id, http_client_settings(endpoint)) + + async def consume(self, envelope: Envelope[Any], input_port: str = "input") -> None: + if self._client is None: + raise RuntimeError("HTTP sink has not been set up") + body = { + "schema": envelope.schema, + "source_id": envelope.source_id, + "sequence": envelope.sequence, + "captured_at_ns": envelope.captured_at_ns, + "received_at_ns": envelope.received_at_ns, + "trace_id": envelope.trace_id, + "session_id": envelope.session_id, + "input_port": input_port, + "payload": _jsonable(envelope.payload), + "attributes": _jsonable(envelope.attributes), + } + idempotency_key = _event_id(envelope.payload) or envelope.trace_id + retry_delay_s = self._retry_initial_s + for attempt in range(1, self._max_attempts + 1): + try: + response = await self._client.post( + self._path, + json=body, + headers={"Idempotency-Key": idempotency_key}, + ) + except asyncio.CancelledError: + raise + except Exception as exc: + retryable = _retryable_exception(exc) + if attempt >= self._max_attempts or not retryable: + if self._failure_mode == "log_and_drop" and retryable: + self._log_dropped_report( + envelope, + input_port=input_port, + attempts=attempt, + error=exc, + ) + return + raise + _LOGGER.warning( + "HTTP report retry node=%s path=%s attempt=%s/%s error=%s", + self._node_id, + self._path, + attempt, + self._max_attempts, + exc, + ) + await asyncio.sleep(retry_delay_s) + retry_delay_s = min( + self._retry_max_s, + max(retry_delay_s * 2.0, self._retry_initial_s), + ) + continue + + if ( + response.status_code in self._retry_statuses + and attempt < self._max_attempts + ): + _LOGGER.warning( + "HTTP report retry node=%s path=%s status=%s attempt=%s/%s", + self._node_id, + self._path, + response.status_code, + attempt, + self._max_attempts, + ) + await asyncio.sleep(retry_delay_s) + retry_delay_s = min( + self._retry_max_s, + max(retry_delay_s * 2.0, self._retry_initial_s), + ) + continue + # Status errors outside the explicit retry allowlist are never + # retried, even though httpx represents them as HTTP exceptions. + try: + response.raise_for_status() + except asyncio.CancelledError: + raise + except Exception as exc: + if self._failure_mode == "log_and_drop": + self._log_dropped_report( + envelope, + input_port=input_port, + attempts=attempt, + error=exc, + status_code=response.status_code, + ) + return + raise + return + + def _log_dropped_report( + self, + envelope: Envelope[Any], + *, + input_port: str, + attempts: int, + error: Exception, + status_code: int | None = None, + ) -> None: + _LOGGER.warning( + "HTTP report dropped node=%s endpoint=%s path=%s input_port=%s " + "failure_mode=log_and_drop action=drop attempts=%s status=%s error_type=%s " + "error=%s trace_id=%s", + self._node_id, + self._endpoint_id, + self._path, + input_port, + attempts, + status_code if status_code is not None else "none", + type(error).__name__, + error, + envelope.trace_id, + ) + + +def _jsonable(value: Any) -> Any: + if value is None or isinstance(value, (str, int, float, bool)): + return value + if isinstance(value, EncodedImage): + return { + "media_type": value.media_type, + "width": value.width, + "height": value.height, + "encoding": "base64", + "data": base64.b64encode(value.data).decode("ascii"), + } + if isinstance(value, bytes): + return {"encoding": "base64", "data": base64.b64encode(value).decode("ascii")} + if isinstance(value, Enum): + return value.value + if dataclasses.is_dataclass(value) and not isinstance(value, type): + return { + field.name: _jsonable(getattr(value, field.name)) + for field in dataclasses.fields(value) + if not field.metadata.get("transient", False) + } + if isinstance(value, Mapping): + return {str(key): _jsonable(item) for key, item in value.items()} + if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)): + return [_jsonable(item) for item in value] + raise TypeError(f"value is not JSON serializable: {type(value).__name__}") + + +def _event_id(payload: Any) -> str | None: + value = getattr(payload, "event_id", None) + return value if isinstance(value, str) and value else None + + +def _retryable_exception(exc: Exception) -> bool: + return isinstance(exc, (OSError, TimeoutError)) or type(exc).__module__.startswith( + "httpx" + ) + + +def _positive_int(params: Mapping[str, Any], name: str, *, default: int) -> int: + value = params.get(name, default) + if isinstance(value, bool) or not isinstance(value, int) or value < 1: + raise ValueError(f"{name} must be a positive integer") + return value + + +def _non_negative_float( + params: Mapping[str, Any], name: str, *, default: float +) -> float: + value = params.get(name, default) + if isinstance(value, bool): + raise ValueError(f"{name} must be a non-negative number") + try: + parsed = float(value) + except (TypeError, ValueError) as exc: + raise ValueError(f"{name} must be a non-negative number") from exc + if not math.isfinite(parsed) or parsed < 0: + raise ValueError(f"{name} must be a non-negative number") + return parsed + + +__all__ = ["PlatformHttpJsonSink"] diff --git a/src/cmvr_edge_ai/contracts/__init__.py b/src/cmvr_edge_ai/contracts/__init__.py new file mode 100644 index 0000000..6c0c5da --- /dev/null +++ b/src/cmvr_edge_ai/contracts/__init__.py @@ -0,0 +1,27 @@ +"""Stable internal schemas for protocol-independent AI nodes.""" + +from .control import ApprovedRobotCommand, RobotCommand +from .events import ( + BoundingBox, + ChatTurn, + Detection, + DetectionAlert, + DetectionResult, + TextEvent, +) +from .media import AudioChunk, CameraIntrinsics, EncodedImage, ImageFrame + +__all__ = [ + "AudioChunk", + "ApprovedRobotCommand", + "BoundingBox", + "CameraIntrinsics", + "ChatTurn", + "Detection", + "DetectionAlert", + "DetectionResult", + "EncodedImage", + "ImageFrame", + "RobotCommand", + "TextEvent", +] diff --git a/src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..eec5234 Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..361f19e Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/contracts/__pycache__/control.cpython-310.pyc b/src/cmvr_edge_ai/contracts/__pycache__/control.cpython-310.pyc new file mode 100644 index 0000000..f552f76 Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/control.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/contracts/__pycache__/control.cpython-312.pyc b/src/cmvr_edge_ai/contracts/__pycache__/control.cpython-312.pyc new file mode 100644 index 0000000..7c63e00 Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/control.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/contracts/__pycache__/events.cpython-310.pyc b/src/cmvr_edge_ai/contracts/__pycache__/events.cpython-310.pyc new file mode 100644 index 0000000..cd09979 Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/events.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/contracts/__pycache__/events.cpython-312.pyc b/src/cmvr_edge_ai/contracts/__pycache__/events.cpython-312.pyc new file mode 100644 index 0000000..9fa6dd7 Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/events.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/contracts/__pycache__/media.cpython-310.pyc b/src/cmvr_edge_ai/contracts/__pycache__/media.cpython-310.pyc new file mode 100644 index 0000000..1d90821 Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/media.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/contracts/__pycache__/media.cpython-312.pyc b/src/cmvr_edge_ai/contracts/__pycache__/media.cpython-312.pyc new file mode 100644 index 0000000..4911ffe Binary files /dev/null and b/src/cmvr_edge_ai/contracts/__pycache__/media.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/contracts/control.py b/src/cmvr_edge_ai/contracts/control.py new file mode 100644 index 0000000..f4c9971 --- /dev/null +++ b/src/cmvr_edge_ai/contracts/control.py @@ -0,0 +1,47 @@ +"""Transport-neutral, expiring robot commands.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from types import MappingProxyType +from typing import Any, Mapping +from uuid import uuid4 +from time import time_ns + + +@dataclass(frozen=True, slots=True) +class RobotCommand: + device_id: str + action: str + valid_until_ns: int + sequence: int + arguments: Mapping[str, Any] = field(default_factory=dict) + command_id: str = field(default_factory=lambda: str(uuid4())) + + def __post_init__(self) -> None: + if not self.device_id: + raise ValueError("device_id must not be empty") + if not self.action: + raise ValueError("action must not be empty") + if self.valid_until_ns <= 0: + raise ValueError("valid_until_ns must be positive Unix time") + if self.sequence < 0: + raise ValueError("sequence must not be negative") + if not self.command_id: + raise ValueError("command_id must not be empty") + object.__setattr__(self, "arguments", MappingProxyType(dict(self.arguments))) + + +@dataclass(frozen=True, slots=True) +class ApprovedRobotCommand: + """A command that has passed the trusted safety-gate boundary.""" + + command: RobotCommand + approved_by: str + approved_at_ns: int = field(default_factory=time_ns) + + def __post_init__(self) -> None: + if not self.approved_by: + raise ValueError("approved_by must not be empty") + if self.approved_at_ns <= 0: + raise ValueError("approved_at_ns must be positive Unix time") diff --git a/src/cmvr_edge_ai/contracts/events.py b/src/cmvr_edge_ai/contracts/events.py new file mode 100644 index 0000000..a0717f8 --- /dev/null +++ b/src/cmvr_edge_ai/contracts/events.py @@ -0,0 +1,84 @@ +"""AI result and conversational event contracts.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + +from .media import EncodedImage, ImageFrame + + +@dataclass(frozen=True, slots=True) +class BoundingBox: + x_min: float + y_min: float + x_max: float + y_max: float + + +@dataclass(frozen=True, slots=True) +class Detection: + label: str + confidence: float + box: BoundingBox + track_id: str | None = None + + +@dataclass(frozen=True, slots=True) +class DetectionResult: + detections: tuple[Detection, ...] + model_id: str + inference_ms: float + # Human-readable registration metadata. It is optional for compatibility + # with existing third-party producers of DetectionResult/v1. + model_name: str = "" + # Internal, zero-copy frame reference for downstream evidence generation. + # It must never be serialized by a generic platform connector. + source_frame: ImageFrame | None = field( + default=None, + repr=False, + compare=False, + metadata={"transient": True}, + ) + + +@dataclass(frozen=True, slots=True) +class DetectionAlert: + """A temporal detection rule that is ready for a downstream interface. + + A DetectionResult describes one inferred frame. DetectionAlert is emitted + only after a stateful rule has observed enough distinct frames inside its + configured time window. Keeping the two contracts separate lets raw + detections be recorded or inspected without accidentally calling an alert + endpoint for every frame. + """ + + event_id: str + rule_id: str + model_id: str + model_name: str + labels: tuple[str, ...] + scope: str + scope_id: str + hit_count: int + window_ms: float + first_seen_ns: int + last_seen_ns: int + triggered_at_ns: int + max_confidence: float + detections: tuple[Detection, ...] + image: EncodedImage | None = None + + +@dataclass(frozen=True, slots=True) +class TextEvent: + text: str + role: str = "assistant" + final: bool = True + + +@dataclass(frozen=True, slots=True) +class ChatTurn: + session_id: str + user_text: str + history: tuple[dict[str, Any], ...] = () diff --git a/src/cmvr_edge_ai/contracts/media.py b/src/cmvr_edge_ai/contracts/media.py new file mode 100644 index 0000000..11d6897 --- /dev/null +++ b/src/cmvr_edge_ai/contracts/media.py @@ -0,0 +1,75 @@ +"""Protocol-neutral media contracts used inside pipelines.""" + +from __future__ import annotations + +from dataclasses import dataclass, field + + +@dataclass(frozen=True, slots=True) +class CameraIntrinsics: + fx: float + fy: float + cx: float + cy: float + coefficients: tuple[float, ...] = () + + +@dataclass(frozen=True, slots=True) +class ImageFrame: + """An encoded packet or decoded image buffer. + + ``codec`` is ``"none"`` for decoded/raw frames. Encoded video packets + retain key-frame metadata so a stateful decoder can be used downstream. + """ + + data: bytes + width: int + height: int + pixel_format: str + codec: str = "none" + is_key_frame: bool = False + intrinsics: CameraIntrinsics | None = None + + @property + def is_encoded(self) -> bool: + return self.codec.lower() not in {"", "none", "raw"} + + +@dataclass(frozen=True, slots=True) +class EncodedImage: + """A compressed image ready to cross a platform boundary.""" + + data: bytes = field(repr=False) + media_type: str + width: int + height: int + + def __post_init__(self) -> None: + if not isinstance(self.data, bytes) or not self.data: + raise ValueError("encoded image data must be non-empty bytes") + if not isinstance(self.media_type, str) or not self.media_type.strip(): + raise ValueError("encoded image media_type must be a non-empty string") + if ( + isinstance(self.width, bool) + or not isinstance(self.width, int) + or self.width < 1 + ): + raise ValueError("encoded image width must be a positive integer") + if ( + isinstance(self.height, bool) + or not isinstance(self.height, int) + or self.height < 1 + ): + raise ValueError("encoded image height must be a positive integer") + + +@dataclass(frozen=True, slots=True) +class AudioChunk: + """A contiguous audio chunk independent of its transport protocol.""" + + data: bytes + sample_rate: int + channels: int + sample_format: str + codec: str = "pcm" + discontinuity: bool = False diff --git a/src/cmvr_edge_ai/core/__init__.py b/src/cmvr_edge_ai/core/__init__.py new file mode 100644 index 0000000..271cd75 --- /dev/null +++ b/src/cmvr_edge_ai/core/__init__.py @@ -0,0 +1,62 @@ +"""Public building blocks for configuration-driven edge AI pipelines.""" + +from .component import ( + DEFAULT_INPUT_PORT, + DEFAULT_OUTPUT_PORT, + Component, + ComponentContext, + Emission, + HealthReport, + Operator, + OperatorResult, + OutputItem, + Sink, + Source, +) +from .edge_queue import ( + EdgeQueue, + EdgeQueueClosed, + EdgeQueueError, + EdgeQueueFull, + OverflowPolicy, + PutResult, + QueueStats, +) +from .envelope import Envelope +from .runtime import ( + Edge, + GraphValidationError, + NodeExecutionError, + PipelineRuntime, + PipelineRuntimeError, + RuntimeState, +) + + +__all__ = [ + "Component", + "ComponentContext", + "DEFAULT_INPUT_PORT", + "DEFAULT_OUTPUT_PORT", + "Edge", + "EdgeQueue", + "EdgeQueueClosed", + "EdgeQueueError", + "EdgeQueueFull", + "Emission", + "Envelope", + "GraphValidationError", + "HealthReport", + "NodeExecutionError", + "Operator", + "OperatorResult", + "OutputItem", + "OverflowPolicy", + "PipelineRuntime", + "PipelineRuntimeError", + "PutResult", + "QueueStats", + "RuntimeState", + "Sink", + "Source", +] diff --git a/src/cmvr_edge_ai/core/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/core/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..1d42b6b Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..5c9c28b Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/component.cpython-310.pyc b/src/cmvr_edge_ai/core/__pycache__/component.cpython-310.pyc new file mode 100644 index 0000000..3403073 Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/component.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/component.cpython-312.pyc b/src/cmvr_edge_ai/core/__pycache__/component.cpython-312.pyc new file mode 100644 index 0000000..f564e53 Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/component.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-310.pyc b/src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-310.pyc new file mode 100644 index 0000000..89b6801 Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-312.pyc b/src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-312.pyc new file mode 100644 index 0000000..ca2acbd Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/envelope.cpython-310.pyc b/src/cmvr_edge_ai/core/__pycache__/envelope.cpython-310.pyc new file mode 100644 index 0000000..cc8d1c7 Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/envelope.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/envelope.cpython-312.pyc b/src/cmvr_edge_ai/core/__pycache__/envelope.cpython-312.pyc new file mode 100644 index 0000000..eb8fc12 Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/envelope.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/runtime.cpython-310.pyc b/src/cmvr_edge_ai/core/__pycache__/runtime.cpython-310.pyc new file mode 100644 index 0000000..b5f319c Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/runtime.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/core/__pycache__/runtime.cpython-312.pyc b/src/cmvr_edge_ai/core/__pycache__/runtime.cpython-312.pyc new file mode 100644 index 0000000..849dca5 Binary files /dev/null and b/src/cmvr_edge_ai/core/__pycache__/runtime.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/core/component.py b/src/cmvr_edge_ai/core/component.py new file mode 100644 index 0000000..ba90752 --- /dev/null +++ b/src/cmvr_edge_ai/core/component.py @@ -0,0 +1,196 @@ +"""Minimal asynchronous component contracts for pipeline nodes.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import AsyncIterable, AsyncIterator, Iterable, Mapping +from dataclasses import dataclass, field +from types import MappingProxyType +from typing import Any, TypeAlias + +import asyncio + +from .envelope import Envelope + + +DEFAULT_INPUT_PORT = "input" +DEFAULT_OUTPUT_PORT = "output" + + +@dataclass(frozen=True, slots=True) +class Emission: + """One envelope emitted on a named output port.""" + + port: str + envelope: Envelope[Any] + + def __post_init__(self) -> None: + if not self.port: + raise ValueError("emission port must not be empty") + if not isinstance(self.envelope, Envelope): + raise TypeError("emission envelope must be an Envelope") + + +OutputItem: TypeAlias = Envelope[Any] | Emission +OperatorResult: TypeAlias = ( + OutputItem | Iterable[OutputItem] | AsyncIterable[OutputItem] | None +) + + +@dataclass(frozen=True, slots=True) +class ComponentContext: + """Runtime information made available during component setup.""" + + pipeline_id: str + node_id: str + shutdown_event: asyncio.Event + metadata: Mapping[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if not self.pipeline_id: + raise ValueError("pipeline_id must not be empty") + if not self.node_id: + raise ValueError("node_id must not be empty") + object.__setattr__(self, "metadata", MappingProxyType(dict(self.metadata))) + + +@dataclass(frozen=True, slots=True) +class HealthReport: + """Small transport-neutral health response for supervisors.""" + + healthy: bool = True + detail: str = "" + + +class Component(ABC): + """Base lifecycle shared by sources, operators and sinks. + + Hooks are intentionally no-ops by default. Implementations should acquire + expensive resources in ``setup`` or ``start`` and release them in ``stop``. + The runtime calls each hook at most once per start/stop cycle. + """ + + async def setup(self, context: ComponentContext) -> None: + """Validate configuration and allocate resources.""" + + async def start(self) -> None: + """Start background work after every node has completed setup.""" + + async def stop(self) -> None: + """Release resources; implementations should make this idempotent.""" + + async def health(self) -> HealthReport: + """Return the component's current health.""" + + return HealthReport() + + +class Source(Component, ABC): + """A component that produces envelopes from an external or synthetic input.""" + + @abstractmethod + def messages(self) -> AsyncIterator[OutputItem]: + """Return an async iterator of envelopes or named emissions. + + Yielding an :class:`Envelope` uses :data:`DEFAULT_OUTPUT_PORT`. + Implementations normally define this method as an async generator. + """ + + raise NotImplementedError + + +class Operator(Component, ABC): + """A component that transforms an envelope and may emit zero or more results.""" + + @abstractmethod + async def process( + self, + envelope: Envelope[Any], + input_port: str = DEFAULT_INPUT_PORT, + ) -> OperatorResult: + """Process one envelope received on ``input_port``. + + A plain envelope is emitted on :data:`DEFAULT_OUTPUT_PORT`; use + :class:`Emission` for a different output port. Iterables and async + iterables are supported for zero-to-many transforms. + """ + + raise NotImplementedError + + +class Sink(Component, ABC): + """A terminal component that sends messages to an external destination.""" + + @abstractmethod + async def consume( + self, + envelope: Envelope[Any], + input_port: str = DEFAULT_INPUT_PORT, + ) -> None: + """Consume one envelope received on ``input_port``.""" + + raise NotImplementedError + + +async def iter_emissions( + result: OperatorResult, + *, + default_port: str = DEFAULT_OUTPUT_PORT, +) -> AsyncIterator[Emission]: + """Normalize a component result into named emissions. + + The helper performs strict element validation so a component accidentally + returning raw bytes or a model object fails near the offending node instead + of much later in a connector. + """ + + if not default_port: + raise ValueError("default_port must not be empty") + + if result is None: + return + if isinstance(result, Envelope): + yield Emission(default_port, result) + return + if isinstance(result, Emission): + yield result + return + + if isinstance(result, AsyncIterable): + async for item in result: + yield _as_emission(item, default_port) + return + + if isinstance(result, Iterable): + for item in result: + yield _as_emission(item, default_port) + return + + raise TypeError( + "component output must be an Envelope, Emission, iterable, " + "async iterable, or None" + ) + + +def _as_emission(item: OutputItem, default_port: str) -> Emission: + if isinstance(item, Emission): + return item + if isinstance(item, Envelope): + return Emission(default_port, item) + raise TypeError("component output iterable contains an invalid item") + + +__all__ = [ + "Component", + "ComponentContext", + "DEFAULT_INPUT_PORT", + "DEFAULT_OUTPUT_PORT", + "Emission", + "HealthReport", + "Operator", + "OperatorResult", + "OutputItem", + "Sink", + "Source", + "iter_emissions", +] diff --git a/src/cmvr_edge_ai/core/edge_queue.py b/src/cmvr_edge_ai/core/edge_queue.py new file mode 100644 index 0000000..f285995 --- /dev/null +++ b/src/cmvr_edge_ai/core/edge_queue.py @@ -0,0 +1,232 @@ +"""Bounded per-edge queues with explicit overload behaviour.""" + +from __future__ import annotations + +from collections import deque +from collections.abc import AsyncIterator +from dataclasses import dataclass +from enum import Enum +from typing import Deque, Generic, TypeVar + +import asyncio + + +ItemT = TypeVar("ItemT") + + +class OverflowPolicy(str, Enum): + """Action taken when a producer reaches an edge's capacity.""" + + BLOCK = "block" + DROP_OLDEST = "drop_oldest" + DROP_NEWEST = "drop_newest" + REJECT = "reject" + + +class PutResult(str, Enum): + """Non-exceptional outcome of :meth:`EdgeQueue.put`.""" + + ENQUEUED = "enqueued" + DROPPED_OLDEST = "dropped_oldest" + DROPPED_NEWEST = "dropped_newest" + + +class EdgeQueueError(RuntimeError): + """Base class for edge queue failures.""" + + +class EdgeQueueClosed(EdgeQueueError): + """Raised when reading a drained closed queue or writing a closed queue.""" + + +class EdgeQueueFull(EdgeQueueError): + """Raised when a full queue uses the ``reject`` overflow policy.""" + + +@dataclass(frozen=True, slots=True) +class QueueStats: + """Point-in-time counters for one edge queue.""" + + capacity: int + current_size: int + high_watermark: int + enqueued: int + dequeued: int + dropped_oldest: int + dropped_newest: int + rejected: int + blocked_puts: int + discarded_on_close: int + closed: bool + + +class EdgeQueue(Generic[ItemT]): + """A small asyncio queue whose overload policy is never implicit. + + Unlike :class:`asyncio.Queue`, closing wakes both blocked producers and + consumers. A normal close is drainable: existing items are returned before + :class:`EdgeQueueClosed` is raised. ``close(discard=True)`` is available for + forced shutdown. + """ + + def __init__( + self, + capacity: int, + overflow: OverflowPolicy | str = OverflowPolicy.BLOCK, + *, + name: str = "", + ) -> None: + if capacity < 1: + raise ValueError("edge queue capacity must be at least 1") + try: + parsed_overflow = OverflowPolicy(overflow) + except ValueError as exc: + choices = ", ".join(policy.value for policy in OverflowPolicy) + raise ValueError( + f"unknown overflow policy {overflow!r}; use {choices}" + ) from exc + + self._capacity = capacity + self._overflow = parsed_overflow + self._name = name + self._items: Deque[ItemT] = deque() + self._condition = asyncio.Condition() + self._closed = False + + self._high_watermark = 0 + self._enqueued = 0 + self._dequeued = 0 + self._dropped_oldest = 0 + self._dropped_newest = 0 + self._rejected = 0 + self._blocked_puts = 0 + self._discarded_on_close = 0 + + @property + def name(self) -> str: + return self._name + + @property + def capacity(self) -> int: + return self._capacity + + @property + def overflow(self) -> OverflowPolicy: + return self._overflow + + @property + def closed(self) -> bool: + return self._closed + + def qsize(self) -> int: + return len(self._items) + + def empty(self) -> bool: + return not self._items + + def full(self) -> bool: + return len(self._items) >= self._capacity + + def stats(self) -> QueueStats: + """Return a consistent-enough event-loop snapshot of queue counters.""" + + return QueueStats( + capacity=self._capacity, + current_size=len(self._items), + high_watermark=self._high_watermark, + enqueued=self._enqueued, + dequeued=self._dequeued, + dropped_oldest=self._dropped_oldest, + dropped_newest=self._dropped_newest, + rejected=self._rejected, + blocked_puts=self._blocked_puts, + discarded_on_close=self._discarded_on_close, + closed=self._closed, + ) + + async def put(self, item: ItemT) -> PutResult: + """Put ``item`` according to this queue's overflow policy.""" + + async with self._condition: + self._raise_if_closed() + + result = PutResult.ENQUEUED + if self._overflow is OverflowPolicy.BLOCK: + if self.full(): + self._blocked_puts += 1 + while self.full() and not self._closed: + await self._condition.wait() + self._raise_if_closed() + elif self.full() and self._overflow is OverflowPolicy.DROP_OLDEST: + self._items.popleft() + self._dropped_oldest += 1 + result = PutResult.DROPPED_OLDEST + elif self.full() and self._overflow is OverflowPolicy.DROP_NEWEST: + self._dropped_newest += 1 + return PutResult.DROPPED_NEWEST + elif self.full() and self._overflow is OverflowPolicy.REJECT: + self._rejected += 1 + queue_name = f" {self._name!r}" if self._name else "" + raise EdgeQueueFull(f"edge queue{queue_name} is full") + + self._items.append(item) + self._enqueued += 1 + self._high_watermark = max(self._high_watermark, len(self._items)) + self._condition.notify_all() + return result + + async def get(self) -> ItemT: + """Get the next item, waiting until data arrives or the queue closes.""" + + async with self._condition: + while not self._items and not self._closed: + await self._condition.wait() + if not self._items: + raise EdgeQueueClosed(self._closed_message()) + + item = self._items.popleft() + self._dequeued += 1 + self._condition.notify_all() + return item + + async def close(self, *, discard: bool = False) -> None: + """Close the queue and wake all waiters. + + The method is idempotent. By default queued items remain available to + consumers. With ``discard=True`` they are removed immediately and + accounted for in ``discarded_on_close``. + """ + + async with self._condition: + if discard and self._items: + self._discarded_on_close += len(self._items) + self._items.clear() + self._closed = True + self._condition.notify_all() + + async def __aiter__(self) -> AsyncIterator[ItemT]: + while True: + try: + yield await self.get() + except EdgeQueueClosed: + return + + def _raise_if_closed(self) -> None: + if self._closed: + raise EdgeQueueClosed(self._closed_message()) + + def _closed_message(self) -> str: + if self._name: + return f"edge queue {self._name!r} is closed" + return "edge queue is closed" + + +__all__ = [ + "EdgeQueue", + "EdgeQueueClosed", + "EdgeQueueError", + "EdgeQueueFull", + "OverflowPolicy", + "PutResult", + "QueueStats", +] diff --git a/src/cmvr_edge_ai/core/envelope.py b/src/cmvr_edge_ai/core/envelope.py new file mode 100644 index 0000000..fb6741c --- /dev/null +++ b/src/cmvr_edge_ai/core/envelope.py @@ -0,0 +1,139 @@ +"""Transport-neutral messages used inside an edge-ai pipeline. + +The envelope is deliberately independent from protobuf, HTTP and model-specific +types. Connectors translate their wire representation at the edge of the +pipeline and operators only exchange :class:`Envelope` instances. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from time import time_ns +from types import MappingProxyType +from typing import Any, Generic, Mapping, TypeVar +from uuid import uuid4 + + +PayloadT = TypeVar("PayloadT") +NewPayloadT = TypeVar("NewPayloadT") + + +def _new_trace_id() -> str: + return uuid4().hex + + +@dataclass(frozen=True, slots=True) +class Envelope(Generic[PayloadT]): + """An immutable metadata wrapper around one pipeline payload. + + ``Envelope`` immutability is shallow: the wrapper and its attributes cannot + be changed, but callers remain responsible for treating a mutable payload + (for example a NumPy array) as read-only after publishing it. This permits + zero-copy fan-out without imposing a dependency on a particular array or + shared-memory implementation. + + All timestamps use Unix time in nanoseconds so timestamps received from + remote processes can be compared. ``deadline_ns`` is optional; runtimes do + not drop expired messages automatically because expiry policy is a business + decision best implemented by an operator or sink. + """ + + payload: PayloadT + schema_name: str = "opaque" + schema_version: int = 1 + source_id: str = "" + sequence: int = 0 + captured_at_ns: int | None = None + received_at_ns: int = field(default_factory=time_ns) + deadline_ns: int | None = None + trace_id: str = field(default_factory=_new_trace_id) + session_id: str | None = None + attributes: Mapping[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if not self.schema_name: + raise ValueError("schema_name must not be empty") + if self.schema_version < 1: + raise ValueError("schema_version must be at least 1") + if self.sequence < 0: + raise ValueError("sequence must not be negative") + if not self.trace_id: + raise ValueError("trace_id must not be empty") + if self.received_at_ns < 0: + raise ValueError("received_at_ns must not be negative") + if self.captured_at_ns is not None and self.captured_at_ns < 0: + raise ValueError("captured_at_ns must not be negative") + if self.deadline_ns is not None and self.deadline_ns < 0: + raise ValueError("deadline_ns must not be negative") + + copied_attributes = dict(self.attributes) + if any(not isinstance(key, str) for key in copied_attributes): + raise TypeError("attribute keys must be strings") + object.__setattr__( + self, + "attributes", + MappingProxyType(copied_attributes), + ) + + @property + def schema(self) -> str: + """Return the compact schema identifier, for example ``ImageFrame/v1``.""" + + return f"{self.schema_name}/v{self.schema_version}" + + def is_expired(self, now_ns: int | None = None) -> bool: + """Return whether the optional deadline has passed.""" + + if self.deadline_ns is None: + return False + return (time_ns() if now_ns is None else now_ns) >= self.deadline_ns + + def with_payload( + self, + payload: NewPayloadT, + *, + schema_name: str | None = None, + schema_version: int | None = None, + source_id: str | None = None, + ) -> Envelope[NewPayloadT]: + """Create a derived envelope while retaining correlation metadata.""" + + return Envelope( + payload=payload, + schema_name=self.schema_name if schema_name is None else schema_name, + schema_version=( + self.schema_version if schema_version is None else schema_version + ), + source_id=self.source_id if source_id is None else source_id, + sequence=self.sequence, + captured_at_ns=self.captured_at_ns, + # Keep the ingress timestamp so end-to-end latency remains + # observable across an arbitrary number of operators. + received_at_ns=self.received_at_ns, + deadline_ns=self.deadline_ns, + trace_id=self.trace_id, + session_id=self.session_id, + attributes=self.attributes, + ) + + def with_attributes(self, **attributes: Any) -> Envelope[PayloadT]: + """Create a copy with additional or replaced read-only attributes.""" + + merged = dict(self.attributes) + merged.update(attributes) + return Envelope( + payload=self.payload, + schema_name=self.schema_name, + schema_version=self.schema_version, + source_id=self.source_id, + sequence=self.sequence, + captured_at_ns=self.captured_at_ns, + received_at_ns=self.received_at_ns, + deadline_ns=self.deadline_ns, + trace_id=self.trace_id, + session_id=self.session_id, + attributes=merged, + ) + + +__all__ = ["Envelope", "PayloadT"] diff --git a/src/cmvr_edge_ai/core/runtime.py b/src/cmvr_edge_ai/core/runtime.py new file mode 100644 index 0000000..d93e388 --- /dev/null +++ b/src/cmvr_edge_ai/core/runtime.py @@ -0,0 +1,667 @@ +"""A lightweight, in-process asynchronous pipeline runtime.""" + +from __future__ import annotations + +from collections import defaultdict, deque +from collections.abc import AsyncIterable, AsyncIterator, Iterable, Mapping +from dataclasses import dataclass +from enum import Enum +from types import MappingProxyType +from typing import Any + +import asyncio +import inspect + +from .component import ( + DEFAULT_INPUT_PORT, + DEFAULT_OUTPUT_PORT, + Component, + ComponentContext, + Emission, + HealthReport, + Operator, + Sink, + Source, + iter_emissions, +) +from .edge_queue import ( + EdgeQueue, + EdgeQueueClosed, + OverflowPolicy, + QueueStats, +) +from .envelope import Envelope + + +class RuntimeState(str, Enum): + CREATED = "created" + STARTING = "starting" + RUNNING = "running" + STOPPING = "stopping" + STOPPED = "stopped" + + +class PipelineRuntimeError(RuntimeError): + """Base exception raised by the pipeline runtime.""" + + +class GraphValidationError(PipelineRuntimeError, ValueError): + """Raised when nodes and edges do not form a valid executable DAG.""" + + +class NodeExecutionError(PipelineRuntimeError): + """Wrap an exception with the id of the component that raised it.""" + + def __init__(self, node_id: str, cause: BaseException) -> None: + self.node_id = node_id + self.cause = cause + super().__init__(f"node {node_id!r} failed: {cause}") + + +@dataclass(frozen=True, slots=True) +class Edge: + """Declarative connection between one output port and one input port.""" + + source_node: str + target_node: str + source_port: str = DEFAULT_OUTPUT_PORT + target_port: str = DEFAULT_INPUT_PORT + capacity: int = 1 + overflow: OverflowPolicy | str = OverflowPolicy.BLOCK + name: str | None = None + + def __post_init__(self) -> None: + for field_name in ( + "source_node", + "target_node", + "source_port", + "target_port", + ): + value = getattr(self, field_name) + if not value or not value.strip(): + raise ValueError(f"{field_name} must not be empty") + if self.capacity < 1: + raise ValueError("edge capacity must be at least 1") + if self.name is not None and not self.name.strip(): + raise ValueError("edge name must not be empty") + try: + object.__setattr__(self, "overflow", OverflowPolicy(self.overflow)) + except ValueError as exc: + choices = ", ".join(policy.value for policy in OverflowPolicy) + raise ValueError( + f"unknown overflow policy {self.overflow!r}; use {choices}" + ) from exc + + @property + def resolved_name(self) -> str: + return self.name or ( + f"{self.source_node}.{self.source_port}" + f"->{self.target_node}.{self.target_port}" + ) + + +@dataclass(slots=True) +class _LiveEdge: + spec: Edge + queue: EdgeQueue[Envelope[Any]] + + +class PipelineRuntime: + """Execute a configuration-compiled DAG in one asyncio event loop. + + The runtime intentionally does not own threads, processes, transports or + retry policy. Those belong to components or a future worker supervisor. + Every graph edge owns a separate bounded queue, and only emissions matching + that edge's ``source_port`` are routed to it. + + Runtime instances are one-shot. Create a new instance to restart a stopped + pipeline; this avoids accidentally reusing closed queues or partially torn + down model resources. + """ + + def __init__( + self, + nodes: Mapping[str, Component], + edges: Iterable[Edge], + *, + pipeline_id: str = "pipeline", + shutdown_timeout_s: float = 10.0, + metadata: Mapping[str, Any] | None = None, + ) -> None: + if not pipeline_id or not pipeline_id.strip(): + raise ValueError("pipeline_id must not be empty") + if shutdown_timeout_s < 0: + raise ValueError("shutdown_timeout_s must not be negative") + + self._pipeline_id = pipeline_id + self._nodes = dict(nodes) + self._edge_specs = tuple(edges) + self._shutdown_timeout_s = shutdown_timeout_s + self._metadata = MappingProxyType(dict(metadata or {})) + self._state = RuntimeState.CREATED + self._shutdown_event = asyncio.Event() + self._stop_lock = asyncio.Lock() + self._tasks: dict[str, asyncio.Task[None]] = {} + self._setup_nodes: list[str] = [] + self._stopped_nodes: set[str] = set() + + self._validate_graph() + self._topological_order = self._compute_topological_order() + self._live_edges = tuple( + _LiveEdge( + spec=edge, + queue=EdgeQueue( + edge.capacity, + edge.overflow, + name=edge.resolved_name, + ), + ) + for edge in self._edge_specs + ) + self._outgoing: dict[tuple[str, str], tuple[_LiveEdge, ...]] = ( + self._index_outgoing() + ) + self._incoming: dict[str, tuple[_LiveEdge, ...]] = self._index_incoming() + + @property + def pipeline_id(self) -> str: + return self._pipeline_id + + @property + def state(self) -> RuntimeState: + return self._state + + @property + def nodes(self) -> Mapping[str, Component]: + return MappingProxyType(self._nodes) + + @property + def edges(self) -> tuple[Edge, ...]: + return self._edge_specs + + @property + def shutdown_event(self) -> asyncio.Event: + return self._shutdown_event + + @property + def edge_queues(self) -> Mapping[str, EdgeQueue[Envelope[Any]]]: + return MappingProxyType( + {edge.spec.resolved_name: edge.queue for edge in self._live_edges} + ) + + def edge_stats(self) -> dict[str, QueueStats]: + """Return snapshots keyed by resolved edge name.""" + + return { + edge.spec.resolved_name: edge.queue.stats() for edge in self._live_edges + } + + async def health(self) -> dict[str, HealthReport]: + """Collect health from all nodes without failing the whole query.""" + + async def check(node_id: str, component: Component) -> tuple[str, HealthReport]: + try: + report = await asyncio.wait_for(component.health(), timeout=1.0) + task = self._tasks.get(node_id) + if task is not None and task.done(): + if task.cancelled(): + report = HealthReport(False, "runtime task was cancelled") + else: + failure = task.exception() + if failure is not None: + report = HealthReport( + False, f"runtime task failed: {failure}" + ) + elif self._state is RuntimeState.RUNNING: + report = HealthReport( + False, + "runtime task completed while pipeline is running", + ) + except Exception as exc: # health is diagnostic, not pipeline control + report = HealthReport(False, f"health check failed: {exc}") + return node_id, report + + reports = await asyncio.gather( + *(check(node_id, component) for node_id, component in self._nodes.items()) + ) + return dict(reports) + + async def start(self) -> None: + """Run lifecycle hooks and start one task per component.""" + + async with self._stop_lock: + if self._state is not RuntimeState.CREATED: + raise PipelineRuntimeError( + f"cannot start pipeline in state {self._state.value!r}" + ) + self._state = RuntimeState.STARTING + + try: + # Prepare downstream consumers before upstream producers. + for node_id in reversed(self._topological_order): + component = self._nodes[node_id] + context = ComponentContext( + pipeline_id=self._pipeline_id, + node_id=node_id, + shutdown_event=self._shutdown_event, + metadata=dict(self._metadata), + ) + self._setup_nodes.append(node_id) + await component.setup(context) + + for node_id in reversed(self._topological_order): + component = self._nodes[node_id] + await component.start() + except BaseException: + self._shutdown_event.set() + deadline = asyncio.get_running_loop().time() + self._shutdown_timeout_s + await self._close_all_edges(discard=True) + await self._stop_components(deadline=deadline) + self._state = RuntimeState.STOPPED + raise + + self._tasks = { + node_id: asyncio.create_task( + self._run_node(node_id, self._nodes[node_id]), + name=f"{self._pipeline_id}:{node_id}", + ) + for node_id in reversed(self._topological_order) + } + self._state = RuntimeState.RUNNING + + async def wait(self) -> None: + """Wait for natural completion, propagating the first node failure.""" + + if self._state not in (RuntimeState.RUNNING, RuntimeState.STOPPING): + if self._state is RuntimeState.STOPPED: + return + raise PipelineRuntimeError( + f"cannot wait for pipeline in state {self._state.value!r}" + ) + try: + await asyncio.gather(*self._tasks.values()) + except asyncio.CancelledError: + raise + except BaseException: + await self.stop(graceful=False) + raise + + async def run(self) -> None: + """Start, wait, and always execute lifecycle teardown.""" + + await self.start() + try: + await self.wait() + finally: + await self.stop() + + async def stop( + self, + *, + graceful: bool = True, + timeout_s: float | None = None, + ) -> None: + """Stop sources, optionally drain downstream queues, then tear down. + + Graceful shutdown cancels source tasks first. Their output queues close + in ``finally`` blocks, allowing operators and sinks to drain all already + accepted messages. If draining exceeds the timeout, remaining tasks + are cancelled and queued messages are discarded. + """ + + if timeout_s is not None and timeout_s < 0: + raise ValueError("timeout_s must not be negative") + + async with self._stop_lock: + if self._state is RuntimeState.STOPPED: + return + if self._state is RuntimeState.CREATED: + self._state = RuntimeState.STOPPED + self._shutdown_event.set() + await self._close_all_edges(discard=True) + return + + self._state = RuntimeState.STOPPING + self._shutdown_event.set() + effective_timeout = ( + self._shutdown_timeout_s if timeout_s is None else timeout_s + ) + deadline = asyncio.get_running_loop().time() + effective_timeout + + if graceful: + source_ids = tuple( + node_id + for node_id, component in self._nodes.items() + if isinstance(component, Source) + ) + self._cancel_tasks(source_ids) + # Source hooks close external subscriptions promptly. Operators + # and sinks remain alive until accepted messages have drained. + await self._stop_components(source_ids, deadline=deadline) + drained = await self._wait_for_tasks(self._remaining(deadline)) + if not drained: + self._cancel_tasks() + await self._close_all_edges(discard=True) + await self._stop_components(deadline=deadline) + await self._join_tasks(self._remaining(deadline)) + else: + self._cancel_tasks() + await self._close_all_edges(discard=True) + await self._stop_components(deadline=deadline) + await self._join_tasks(self._remaining(deadline)) + + await self._stop_components(deadline=deadline) + self._tasks.clear() + self._state = RuntimeState.STOPPED + + async def _run_node(self, node_id: str, component: Component) -> None: + try: + if isinstance(component, Source): + await self._run_source(node_id, component) + elif isinstance(component, Operator): + await self._run_operator(node_id, component) + elif isinstance(component, Sink): + await self._run_sink(node_id, component) + else: # guarded by graph validation + raise TypeError("unsupported component kind") + except asyncio.CancelledError: + raise + except BaseException as exc: + raise NodeExecutionError(node_id, exc) from exc + finally: + await self._close_outgoing(node_id) + + async def _run_source(self, node_id: str, source: Source) -> None: + stream = source.messages() + if inspect.isawaitable(stream): + # Be tolerant of an ``async def`` factory that returns an iterator. + stream = await stream + if not isinstance(stream, AsyncIterable): + raise TypeError("Source.messages() must return an async iterable") + + async for item in stream: + async for emission in iter_emissions(item): + await self._route(node_id, emission) + + async def _run_operator(self, node_id: str, operator: Operator) -> None: + async for live_edge, envelope in self._receive(node_id): + result = await operator.process( + envelope, + input_port=live_edge.spec.target_port, + ) + async for emission in iter_emissions(result): + await self._route(node_id, emission) + + async def _run_sink(self, node_id: str, sink: Sink) -> None: + async for live_edge, envelope in self._receive(node_id): + await sink.consume( + envelope, + input_port=live_edge.spec.target_port, + ) + + async def _route(self, source_node: str, emission: Emission) -> None: + edges = self._outgoing.get((source_node, emission.port), ()) + if not edges: + return + + results = await asyncio.gather( + *(edge.queue.put(emission.envelope) for edge in edges), + return_exceptions=True, + ) + for result in results: + if isinstance(result, EdgeQueueClosed) and self._shutdown_event.is_set(): + continue + if isinstance(result, BaseException): + raise result + + async def _receive( + self, + node_id: str, + ) -> AsyncIterator[tuple[_LiveEdge, Envelope[Any]]]: + incoming = self._incoming.get(node_id, ()) + pending: dict[asyncio.Task[Envelope[Any]], _LiveEdge] = { + asyncio.create_task( + edge.queue.get(), + name=f"{self._pipeline_id}:{node_id}:read:{edge.spec.resolved_name}", + ): edge + for edge in incoming + } + + try: + while pending: + done, _ = await asyncio.wait( + pending, + return_when=asyncio.FIRST_COMPLETED, + ) + for task in done: + edge = pending.pop(task) + try: + envelope = task.result() + except EdgeQueueClosed: + continue + + yield edge, envelope + + if not (edge.queue.closed and edge.queue.empty()): + next_read = asyncio.create_task( + edge.queue.get(), + name=( + f"{self._pipeline_id}:{node_id}:read:" + f"{edge.spec.resolved_name}" + ), + ) + pending[next_read] = edge + finally: + for task in pending: + task.cancel() + if pending: + await asyncio.gather(*pending, return_exceptions=True) + + async def _close_outgoing(self, node_id: str) -> None: + queues = [ + live_edge.queue + for (source, _), edges in self._outgoing.items() + if source == node_id + for live_edge in edges + ] + if queues: + await asyncio.gather(*(queue.close() for queue in queues)) + + async def _close_all_edges(self, *, discard: bool) -> None: + if self._live_edges: + await asyncio.gather( + *(edge.queue.close(discard=discard) for edge in self._live_edges) + ) + + async def _wait_for_tasks(self, timeout_s: float) -> bool: + if not self._tasks: + return True + _, pending = await asyncio.wait( + self._tasks.values(), + timeout=timeout_s, + return_when=asyncio.ALL_COMPLETED, + ) + return not pending + + def _cancel_tasks(self, node_ids: Iterable[str] | None = None) -> None: + selected = self._tasks.keys() if node_ids is None else node_ids + for node_id in selected: + task = self._tasks.get(node_id) + if task is None: + continue + if not task.done(): + task.cancel() + + async def _join_tasks(self, timeout_s: float) -> bool: + tasks = tuple(self._tasks.values()) + if not tasks: + return True + _, pending = await asyncio.wait( + tasks, + timeout=timeout_s, + return_when=asyncio.ALL_COMPLETED, + ) + for task in pending: + task.cancel() + return not pending + + async def _stop_components( + self, + node_ids: Iterable[str] | None = None, + *, + deadline: float | None = None, + ) -> None: + # setup may allocate resources even when a later start hook fails. + selected = None if node_ids is None else set(node_ids) + setup_order = list(dict.fromkeys(self._setup_nodes)) + for node_id in reversed(setup_order): + if selected is not None and node_id not in selected: + continue + if node_id in self._stopped_nodes: + continue + try: + if deadline is None: + await self._nodes[node_id].stop() + else: + remaining = self._remaining(deadline) + if remaining <= 0: + continue + await asyncio.wait_for( + self._nodes[node_id].stop(), timeout=remaining + ) + except Exception: + # Teardown is best effort so one broken connector cannot leak all + # remaining resources. Supervisors should surface node health/logs. + pass + finally: + self._stopped_nodes.add(node_id) + if selected is None: + self._setup_nodes.clear() + + @staticmethod + def _remaining(deadline: float) -> float: + return max(0.0, deadline - asyncio.get_running_loop().time()) + + def _index_outgoing(self) -> dict[tuple[str, str], tuple[_LiveEdge, ...]]: + mutable: dict[tuple[str, str], list[_LiveEdge]] = defaultdict(list) + for edge in self._live_edges: + mutable[(edge.spec.source_node, edge.spec.source_port)].append(edge) + return {key: tuple(value) for key, value in mutable.items()} + + def _index_incoming(self) -> dict[str, tuple[_LiveEdge, ...]]: + mutable: dict[str, list[_LiveEdge]] = defaultdict(list) + for edge in self._live_edges: + mutable[edge.spec.target_node].append(edge) + return {key: tuple(value) for key, value in mutable.items()} + + def _validate_graph(self) -> None: + if not self._nodes: + raise GraphValidationError("pipeline must contain at least one node") + + for node_id, component in self._nodes.items(): + if not node_id or not node_id.strip(): + raise GraphValidationError("node id must not be empty") + kinds = sum( + isinstance(component, kind) for kind in (Source, Operator, Sink) + ) + if kinds != 1: + raise GraphValidationError( + f"node {node_id!r} must implement exactly one of " + "Source, Operator, or Sink" + ) + + edge_names: set[str] = set() + edge_keys: set[tuple[str, str, str, str]] = set() + incoming_count: dict[str, int] = defaultdict(int) + outgoing_count: dict[str, int] = defaultdict(int) + adjacency: dict[str, set[str]] = defaultdict(set) + indegree = {node_id: 0 for node_id in self._nodes} + + for edge in self._edge_specs: + if edge.source_node not in self._nodes: + raise GraphValidationError( + f"edge {edge.resolved_name!r} references unknown source node " + f"{edge.source_node!r}" + ) + if edge.target_node not in self._nodes: + raise GraphValidationError( + f"edge {edge.resolved_name!r} references unknown target node " + f"{edge.target_node!r}" + ) + if edge.resolved_name in edge_names: + raise GraphValidationError( + f"duplicate edge name {edge.resolved_name!r}" + ) + edge_names.add(edge.resolved_name) + + edge_key = ( + edge.source_node, + edge.source_port, + edge.target_node, + edge.target_port, + ) + if edge_key in edge_keys: + raise GraphValidationError(f"duplicate edge {edge.resolved_name!r}") + edge_keys.add(edge_key) + incoming_count[edge.target_node] += 1 + outgoing_count[edge.source_node] += 1 + + if edge.target_node not in adjacency[edge.source_node]: + adjacency[edge.source_node].add(edge.target_node) + indegree[edge.target_node] += 1 + + for node_id, component in self._nodes.items(): + if isinstance(component, Source) and incoming_count[node_id]: + raise GraphValidationError( + f"source node {node_id!r} cannot have incoming edges" + ) + if isinstance(component, (Operator, Sink)) and not incoming_count[node_id]: + raise GraphValidationError( + f"{type(component).__name__.lower()} node {node_id!r} " + "must have at least one incoming edge" + ) + if isinstance(component, Sink) and outgoing_count[node_id]: + raise GraphValidationError( + f"sink node {node_id!r} cannot have outgoing edges" + ) + + ready = deque(node_id for node_id, degree in indegree.items() if degree == 0) + visited = 0 + while ready: + node_id = ready.popleft() + visited += 1 + for target in adjacency[node_id]: + indegree[target] -= 1 + if indegree[target] == 0: + ready.append(target) + if visited != len(self._nodes): + raise GraphValidationError("pipeline graph must be acyclic") + + def _compute_topological_order(self) -> tuple[str, ...]: + adjacency: dict[str, set[str]] = defaultdict(set) + indegree = {node_id: 0 for node_id in self._nodes} + for edge in self._edge_specs: + if edge.target_node not in adjacency[edge.source_node]: + adjacency[edge.source_node].add(edge.target_node) + indegree[edge.target_node] += 1 + + ready = deque(node_id for node_id, degree in indegree.items() if degree == 0) + ordered: list[str] = [] + while ready: + node_id = ready.popleft() + ordered.append(node_id) + for target in adjacency[node_id]: + indegree[target] -= 1 + if indegree[target] == 0: + ready.append(target) + return tuple(ordered) + + +__all__ = [ + "Edge", + "GraphValidationError", + "NodeExecutionError", + "PipelineRuntime", + "PipelineRuntimeError", + "RuntimeState", +] diff --git a/src/cmvr_edge_ai/detection/__init__.py b/src/cmvr_edge_ai/detection/__init__.py new file mode 100644 index 0000000..1771d65 --- /dev/null +++ b/src/cmvr_edge_ai/detection/__init__.py @@ -0,0 +1,38 @@ +"""Model-independent object detection, decoding and alert rules.""" + +from .base import DetectionModel +from .models import ( + CONSTRUCTION_PPE_LABELS, + PPE_6CLASS_LABELS, + register_builtin_detection_models, +) +from .operator import DetectionOperator, DetectorParams +from .plugins import register_detection_plugins +from .registry import ( + DetectionModelFactory, + DetectionModelRegistry, + DetectionModelRegistryError, + DetectionModelSpec, + DuplicateDetectionModelError, + UnknownDetectionModelError, +) +from .rules import DetectionTriggerOperator +from .video import VideoDecoderOperator + +__all__ = [ + "CONSTRUCTION_PPE_LABELS", + "PPE_6CLASS_LABELS", + "DetectionModel", + "DetectionModelFactory", + "DetectionModelRegistry", + "DetectionModelRegistryError", + "DetectionModelSpec", + "DetectionOperator", + "DetectionTriggerOperator", + "DetectorParams", + "DuplicateDetectionModelError", + "UnknownDetectionModelError", + "VideoDecoderOperator", + "register_builtin_detection_models", + "register_detection_plugins", +] diff --git a/src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..723c5af Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..19c075a Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-310.pyc new file mode 100644 index 0000000..90a1b35 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-312.pyc new file mode 100644 index 0000000..ff3b813 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/base.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/base.cpython-310.pyc new file mode 100644 index 0000000..46a1cf0 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/base.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/base.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000..99647f2 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/base.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/operator.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/operator.cpython-310.pyc new file mode 100644 index 0000000..dbe8258 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/operator.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/operator.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/operator.cpython-312.pyc new file mode 100644 index 0000000..e0608fa Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/operator.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-310.pyc new file mode 100644 index 0000000..1d6af07 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-312.pyc new file mode 100644 index 0000000..4f571ba Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/registry.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/registry.cpython-310.pyc new file mode 100644 index 0000000..75d8566 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/registry.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/registry.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/registry.cpython-312.pyc new file mode 100644 index 0000000..42bb807 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/registry.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/rules.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/rules.cpython-310.pyc new file mode 100644 index 0000000..86d3a13 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/rules.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/rules.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/rules.cpython-312.pyc new file mode 100644 index 0000000..aec7f37 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/rules.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/video.cpython-310.pyc b/src/cmvr_edge_ai/detection/__pycache__/video.cpython-310.pyc new file mode 100644 index 0000000..6939ee2 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/video.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/__pycache__/video.cpython-312.pyc b/src/cmvr_edge_ai/detection/__pycache__/video.cpython-312.pyc new file mode 100644 index 0000000..d529fd1 Binary files /dev/null and b/src/cmvr_edge_ai/detection/__pycache__/video.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/annotation.py b/src/cmvr_edge_ai/detection/annotation.py new file mode 100644 index 0000000..6be6b3d --- /dev/null +++ b/src/cmvr_edge_ai/detection/annotation.py @@ -0,0 +1,176 @@ +"""On-demand bounding-box rendering for detection alerts.""" + +from __future__ import annotations + +from collections.abc import Sequence +from importlib import import_module +from io import BytesIO +import math +from typing import Any + +from cmvr_edge_ai.contracts import Detection, EncodedImage, ImageFrame + + +class ImageAnnotationDependencyError(RuntimeError): + """Raised when the optional image annotation runtime is unavailable.""" + + +class ImageAnnotationError(ValueError): + """Raised when an input frame cannot be rendered safely.""" + + +def ensure_annotation_support() -> None: + """Fail early with an actionable message when Pillow is not installed.""" + + _pillow_modules() + + +def render_annotated_jpeg( + frame: ImageFrame, + detections: Sequence[Detection], + *, + jpeg_quality: int, +) -> EncodedImage: + """Draw valid detection boxes and encode one decoded frame as JPEG.""" + + if isinstance(jpeg_quality, bool) or not isinstance(jpeg_quality, int): + raise ImageAnnotationError("jpeg_quality must be an integer") + if not 1 <= jpeg_quality <= 95: + raise ImageAnnotationError("jpeg_quality must be between 1 and 95") + _validate_frame(frame) + image_module, image_draw_module = _pillow_modules() + + raw_mode = "BGR" if frame.pixel_format.upper() == "BGR8" else "RGB" + image = image_module.frombytes( + "RGB", + (frame.width, frame.height), + frame.data, + "raw", + raw_mode, + ) + draw = image_draw_module.Draw(image) + for detection in detections: + if not isinstance(detection, Detection): + raise ImageAnnotationError( + "detections must contain Detection instances" + ) + box = _clipped_box(detection, frame.width, frame.height) + if box is None: + # One corrupt backend box must not prevent the other evidence boxes + # or the structured safety alert from being produced. + continue + color = _label_color(detection.label) + draw.rectangle(box, outline=color, width=3) + label = f"{detection.label} {detection.confidence:.2f}" + text_box = draw.textbbox((box[0], box[1]), label) + text_width = max(1, text_box[2] - text_box[0]) + text_height = max(1, text_box[3] - text_box[1]) + text_y = max(0.0, box[1] - text_height - 4) + background = ( + box[0], + text_y, + min(float(frame.width - 1), box[0] + text_width + 4), + min(float(frame.height - 1), text_y + text_height + 4), + ) + draw.rectangle(background, fill=color) + draw.text((box[0] + 2, text_y + 2), label, fill=(255, 255, 255)) + + output = BytesIO() + image.save(output, format="JPEG", quality=jpeg_quality) + return EncodedImage( + data=output.getvalue(), + media_type="image/jpeg", + width=frame.width, + height=frame.height, + ) + + +def _pillow_modules() -> tuple[Any, Any]: + try: + return import_module("PIL.Image"), import_module("PIL.ImageDraw") + except ImportError as exc: + raise ImageAnnotationDependencyError( + "alert images require Pillow; install the 'image' extra " + "(uv sync --extra image)" + ) from exc + + +def _validate_frame(frame: ImageFrame) -> None: + if not isinstance(frame, ImageFrame): + raise ImageAnnotationError( + f"expected ImageFrame, got {type(frame).__name__}" + ) + if not isinstance(frame.codec, str): + raise ImageAnnotationError("frame codec must be a string") + if frame.codec.lower() not in {"", "none", "raw"}: + raise ImageAnnotationError("alert images require a decoded ImageFrame") + if not isinstance(frame.pixel_format, str) or frame.pixel_format.upper() not in { + "BGR8", + "RGB8", + }: + raise ImageAnnotationError( + "alert images support only packed BGR8 or RGB8 frames" + ) + if ( + isinstance(frame.width, bool) + or not isinstance(frame.width, int) + or frame.width < 1 + or isinstance(frame.height, bool) + or not isinstance(frame.height, int) + or frame.height < 1 + ): + raise ImageAnnotationError("frame dimensions must be positive integers") + expected_size = frame.width * frame.height * 3 + if not isinstance(frame.data, bytes): + raise ImageAnnotationError("packed frame buffer must be bytes") + if len(frame.data) != expected_size: + raise ImageAnnotationError( + f"packed frame buffer has {len(frame.data)} bytes; " + f"expected {expected_size}" + ) + + +def _clipped_box( + detection: Detection, + width: int, + height: int, +) -> tuple[float, float, float, float] | None: + values = ( + detection.box.x_min, + detection.box.y_min, + detection.box.x_max, + detection.box.y_max, + ) + if any( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(float(value)) + for value in values + ): + return None + x_min, y_min, x_max, y_max = (float(value) for value in values) + if x_max <= x_min or y_max <= y_min: + return None + clipped = ( + min(max(x_min, 0.0), float(width - 1)), + min(max(y_min, 0.0), float(height - 1)), + min(max(x_max, 0.0), float(width - 1)), + min(max(y_max, 0.0), float(height - 1)), + ) + if clipped[2] <= clipped[0] or clipped[3] <= clipped[1]: + return None + return clipped + + +def _label_color(label: str) -> tuple[int, int, int]: + # Stable across processes unlike Python's randomized hash(). + seed = sum((index + 1) * byte for index, byte in enumerate(label.encode("utf-8"))) + return (64 + seed % 192, 64 + (seed * 5) % 192, 64 + (seed * 11) % 192) + + +__all__ = [ + "ImageAnnotationDependencyError", + "ImageAnnotationError", + "ensure_annotation_support", + "render_annotated_jpeg", +] diff --git a/src/cmvr_edge_ai/detection/base.py b/src/cmvr_edge_ai/detection/base.py new file mode 100644 index 0000000..0c3224a --- /dev/null +++ b/src/cmvr_edge_ai/detection/base.py @@ -0,0 +1,50 @@ +"""Backend-neutral abstraction for object-detection models.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Sequence + +from cmvr_edge_ai.contracts import Detection, ImageFrame + + +class DetectionModel(ABC): + """A loaded detection model owned by one pipeline component. + + The interface is deliberately synchronous because most inference SDKs expose + blocking APIs. Pipeline operators must offload these methods to the shared + thread executor (or to an explicitly owned process worker) instead of calling + them directly on the asyncio event loop. + """ + + @abstractmethod + def load(self) -> None: + """Load weights and allocate model runtime resources.""" + + raise NotImplementedError + + @abstractmethod + def predict( + self, + frame: ImageFrame, + labels: tuple[str, ...], + confidence: float, + ) -> Sequence[Detection]: + """Detect selected ``labels`` in one decoded image frame. + + Implementations may use the selected labels to reduce backend + post-processing, but callers still validate/filter returned detections at + the common operator boundary. ``confidence`` is in the inclusive + ``[0, 1]`` range. + """ + + raise NotImplementedError + + @abstractmethod + def close(self) -> None: + """Release model resources; implementations should be idempotent.""" + + raise NotImplementedError + + +__all__ = ["DetectionModel"] diff --git a/src/cmvr_edge_ai/detection/models/__init__.py b/src/cmvr_edge_ai/detection/models/__init__.py new file mode 100644 index 0000000..8118766 --- /dev/null +++ b/src/cmvr_edge_ai/detection/models/__init__.py @@ -0,0 +1,17 @@ +"""Built-in detection model adapters and artifact registrations.""" + +from .ppe_yolo import ( + CONSTRUCTION_PPE_LABELS, + PPE_6CLASS_LABELS, + register_builtin_detection_models, +) +from .yolo import UltralyticsYoloModel, YoloDependencyError, YoloModelOptions + +__all__ = [ + "CONSTRUCTION_PPE_LABELS", + "PPE_6CLASS_LABELS", + "UltralyticsYoloModel", + "YoloDependencyError", + "YoloModelOptions", + "register_builtin_detection_models", +] diff --git a/src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..28e3681 Binary files /dev/null and b/src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..af90f71 Binary files /dev/null and b/src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-310.pyc b/src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-310.pyc new file mode 100644 index 0000000..69ef277 Binary files /dev/null and b/src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-312.pyc b/src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-312.pyc new file mode 100644 index 0000000..c5b61de Binary files /dev/null and b/src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-310.pyc b/src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-310.pyc new file mode 100644 index 0000000..25421db Binary files /dev/null and b/src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-312.pyc b/src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-312.pyc new file mode 100644 index 0000000..e97719c Binary files /dev/null and b/src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/detection/models/ppe_yolo.py b/src/cmvr_edge_ai/detection/models/ppe_yolo.py new file mode 100644 index 0000000..9dc6c02 --- /dev/null +++ b/src/cmvr_edge_ai/detection/models/ppe_yolo.py @@ -0,0 +1,96 @@ +"""Registration metadata for the built-in PPE YOLOv8 checkpoints.""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from ..base import DetectionModel +from ..registry import DetectionModelRegistry, DetectionModelSpec +from .yolo import UltralyticsYoloModel + + +CONSTRUCTION_PPE_LABELS = ( + "Boots", + "Ear-Protection", + "Glass", + "Glove", + "Hard_hat", + "Mask", + "No-Boots", + "No-Ear-Protection", + "No-Glass", + "No-Glove", + "No-Helmet", + "No-Mask", + "No-Vest", + "Worker", + "Vest", + "Circular_Saw", + "Fire_Extinguisher", + "Fire_prevention_Net", + "Welding_Equipment", +) + +PPE_6CLASS_LABELS = ( + "Gloves", + "Vest", + "goggles", + "helmet", + "mask", + "safety_shoe", +) + + +def _create_construction_ppe_model( + params: Mapping[str, Any], +) -> DetectionModel: + return UltralyticsYoloModel( + params, + expected_labels=CONSTRUCTION_PPE_LABELS, + ) + + +def _create_ppe_6classes_model( + params: Mapping[str, Any], +) -> DetectionModel: + return UltralyticsYoloModel( + params, + expected_labels=PPE_6CLASS_LABELS, + ) + + +def register_builtin_detection_models(registry: DetectionModelRegistry) -> None: + registry.register( + DetectionModelSpec( + model_id="construction-ppe-yolov8@1", + name="Construction PPE YOLOv8s", + supported_labels=CONSTRUCTION_PPE_LABELS, + factory=_create_construction_ppe_model, + backend="ultralytics-yolo", + description=( + "Construction-site worker and personal protective equipment " + "detector with positive and No-* violation labels" + ), + ) + ) + registry.register( + DetectionModelSpec( + model_id="ppe-6classes-yolov8n@1", + name="PPE Detection YOLOv8n (6 Classes)", + supported_labels=PPE_6CLASS_LABELS, + factory=_create_ppe_6classes_model, + backend="ultralytics-yolo", + description=( + "Lightweight six-class personal protective equipment detector " + "for gloves, vests, goggles, helmets, masks and safety shoes" + ), + ) + ) + + +__all__ = [ + "CONSTRUCTION_PPE_LABELS", + "PPE_6CLASS_LABELS", + "register_builtin_detection_models", +] diff --git a/src/cmvr_edge_ai/detection/models/yolo.py b/src/cmvr_edge_ai/detection/models/yolo.py new file mode 100644 index 0000000..1aff00d --- /dev/null +++ b/src/cmvr_edge_ai/detection/models/yolo.py @@ -0,0 +1,209 @@ +"""Ultralytics YOLO adapter for registered detection model artifacts.""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from importlib import import_module +from pathlib import Path +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field, field_validator + +from cmvr_edge_ai.contracts import BoundingBox, Detection, ImageFrame + +from ..base import DetectionModel + + +class YoloDependencyError(RuntimeError): + """Raised when the optional YOLO runtime has not been installed.""" + + +class YoloModelOptions(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + weights: str = Field(min_length=1) + device: int | str = "cpu" + imgsz: int = Field(default=640, ge=32, le=8192) + iou: float = Field(default=0.7, ge=0.0, le=1.0) + half: bool = False + max_det: int = Field(default=300, ge=1, le=100_000) + agnostic_nms: bool = False + + @field_validator("weights") + @classmethod + def normalize_weights(cls, value: str) -> str: + value = value.strip() + if not value: + raise ValueError("weights must not be empty") + return value + + @field_validator("device") + @classmethod + def validate_device(cls, value: int | str) -> int | str: + if isinstance(value, int): + if value < 0: + raise ValueError("device must be non-negative") + return value + value = value.strip() + if not value: + raise ValueError("device must not be empty") + return value + + +class UltralyticsYoloModel(DetectionModel): + """Normalize an Ultralytics detector behind the framework model API.""" + + def __init__( + self, + params: Mapping[str, Any], + *, + expected_labels: tuple[str, ...], + ) -> None: + try: + self._options = YoloModelOptions.model_validate(dict(params)) + except Exception as exc: + raise ValueError(f"invalid YOLO model options: {exc}") from exc + self._expected_labels = expected_labels + self._label_to_id = { + label: index for index, label in enumerate(self._expected_labels) + } + self._model: Any = None + self._numpy: Any = None + + @property + def weights_path(self) -> Path: + return Path(self._options.weights).expanduser() + + def load(self) -> None: + weights = self.weights_path + if not weights.is_file(): + raise FileNotFoundError(f"YOLO weights do not exist: {weights}") + try: + np = import_module("numpy") + YOLO = getattr(import_module("ultralytics"), "YOLO") + except ImportError as exc: + raise YoloDependencyError( + "YOLO support is not installed; install " + "cmvr-edge-ai[yolo]. The PPE checkpoint was created with " + "Ultralytics 8.4.31, so validate the deployed 8.x runtime." + ) from exc + + # Ultralytics .pt files use the PyTorch checkpoint loader. Deploy only + # weights obtained from a trusted build/artifact source. + model = YOLO(str(weights), task="detect") + actual_labels = _ordered_model_names(model.names) + if actual_labels != self._expected_labels: + raise ValueError( + "YOLO checkpoint labels do not match the registered model: " + f"expected {self._expected_labels!r}, got {actual_labels!r}" + ) + self._numpy = np + self._model = model + + def predict( + self, + frame: ImageFrame, + labels: tuple[str, ...], + confidence: float, + ) -> Sequence[Detection]: + if self._model is None or self._numpy is None: + raise RuntimeError("YOLO model has not been loaded") + if frame.is_encoded: + raise ValueError("YOLO requires a decoded ImageFrame") + pixel_format = frame.pixel_format.strip().upper() + if pixel_format not in {"BGR8", "RGB8"}: + raise ValueError( + f"YOLO adapter supports BGR8 or RGB8, got {frame.pixel_format!r}" + ) + expected_size = frame.width * frame.height * 3 + if frame.width <= 0 or frame.height <= 0 or len(frame.data) != expected_size: + raise ValueError( + "decoded frame buffer size does not match width*height*3: " + f"got {len(frame.data)}, expected {expected_size}" + ) + + image = self._numpy.frombuffer(frame.data, dtype=self._numpy.uint8).reshape( + (frame.height, frame.width, 3) + ) + if pixel_format == "RGB8": + # Ultralytics ndarray inputs follow the OpenCV BGR convention. + image = image[:, :, ::-1] + + class_ids = [self._label_to_id[label] for label in labels] + results = self._model.predict( + source=image, + classes=class_ids, + conf=confidence, + iou=self._options.iou, + imgsz=self._options.imgsz, + device=self._options.device, + half=self._options.half, + max_det=self._options.max_det, + agnostic_nms=self._options.agnostic_nms, + verbose=False, + ) + if not results: + return () + boxes = results[0].boxes + if boxes is None: + return () + + xyxy_rows = _to_python_list(boxes.xyxy) + confidences = _to_python_list(boxes.conf) + class_indexes = _to_python_list(boxes.cls) + detections: list[Detection] = [] + selected = frozenset(labels) + for coordinates, score, class_index in zip( + xyxy_rows, confidences, class_indexes + ): + index = int(class_index) + if not 0 <= index < len(self._expected_labels): + raise ValueError(f"YOLO returned an unknown class id: {index}") + label = self._expected_labels[index] + if label not in selected: + continue + if len(coordinates) != 4: + raise ValueError("YOLO xyxy result must contain four coordinates") + detections.append( + Detection( + label=label, + confidence=float(score), + box=BoundingBox(*(float(value) for value in coordinates)), + ) + ) + return tuple(detections) + + def close(self) -> None: + self._model = None + self._numpy = None + + +def _ordered_model_names(value: Any) -> tuple[str, ...]: + if isinstance(value, Mapping): + try: + indexes = sorted(int(key) for key in value) + if indexes != list(range(len(indexes))): + raise ValueError("class IDs must be contiguous and start at zero") + return tuple(str(value[index]) for index in indexes) + except (KeyError, TypeError, ValueError) as exc: + raise ValueError(f"invalid YOLO model.names mapping: {value!r}") from exc + if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)): + return tuple(str(label) for label in value) + raise ValueError(f"invalid YOLO model.names value: {value!r}") + + +def _to_python_list(value: Any) -> list[Any]: + if hasattr(value, "detach"): + value = value.detach() + if hasattr(value, "cpu"): + value = value.cpu() + if hasattr(value, "tolist"): + value = value.tolist() + return list(value) + + +__all__ = [ + "UltralyticsYoloModel", + "YoloDependencyError", + "YoloModelOptions", +] diff --git a/src/cmvr_edge_ai/detection/operator.py b/src/cmvr_edge_ai/detection/operator.py new file mode 100644 index 0000000..63b1649 --- /dev/null +++ b/src/cmvr_edge_ai/detection/operator.py @@ -0,0 +1,374 @@ +"""Model-independent detection pipeline operator.""" + +from __future__ import annotations + +import logging +import math +from collections import Counter +from collections.abc import Mapping, Sequence +from concurrent.futures import Executor +from time import monotonic_ns, perf_counter +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator + +from cmvr_edge_ai.contracts import Detection, DetectionResult, ImageFrame +from cmvr_edge_ai.core.component import ( + ComponentContext, + Emission, + HealthReport, + Operator, +) +from cmvr_edge_ai.core.envelope import Envelope +from cmvr_edge_ai.workers import run_blocking + +from .base import DetectionModel +from .registry import DetectionModelRegistry, DetectionModelSpec + + +_LOGGER = logging.getLogger(__name__) + + +class DetectorParams(BaseModel): + """Strict deployment parameters shared by every detection backend.""" + + model_config = ConfigDict(extra="forbid", frozen=True, protected_namespaces=()) + + model: str = Field(min_length=1) + detect_labels: tuple[str, ...] | None = None + confidence: float = Field(default=0.25, ge=0.0, le=1.0) + label_confidence: dict[str, float] = Field(default_factory=dict) + model_options: dict[str, Any] = Field(default_factory=dict) + max_fps: float | None = Field(default=None, gt=0.0, le=1000.0) + attach_frame: bool = Field(default=False, strict=True) + inference_log_interval_s: float | None = Field( + default=None, + gt=0.0, + le=3600.0, + ) + + @field_validator("model") + @classmethod + def normalize_model(cls, value: str) -> str: + value = value.strip() + if not value: + raise ValueError("model must not be empty") + return value + + @field_validator("inference_log_interval_s", mode="before") + @classmethod + def reject_boolean_log_interval(cls, value: Any) -> Any: + if isinstance(value, bool): + raise ValueError("inference_log_interval_s must be a number of seconds") + return value + + @field_validator("detect_labels") + @classmethod + def validate_detect_labels( + cls, value: tuple[str, ...] | None + ) -> tuple[str, ...] | None: + if value is None: + return None + if not value: + raise ValueError("detect_labels must not be empty; omit it to detect all") + normalized = tuple(label.strip() for label in value) + if any(not label for label in normalized): + raise ValueError("detect_labels must contain non-empty strings") + if len(set(normalized)) != len(normalized): + raise ValueError("detect_labels must not contain duplicates") + return normalized + + @field_validator("label_confidence") + @classmethod + def validate_label_confidence(cls, value: dict[str, float]) -> dict[str, float]: + normalized: dict[str, float] = {} + for label, confidence in value.items(): + clean_label = label.strip() + if not clean_label: + raise ValueError("label_confidence keys must not be empty") + parsed = float(confidence) + if not 0.0 <= parsed <= 1.0: + raise ValueError( + f"label_confidence.{clean_label} must be between 0 and 1" + ) + normalized[clean_label] = parsed + return normalized + + @model_validator(mode="after") + def validate_label_threshold_scope(self) -> DetectorParams: + if self.detect_labels is not None: + unknown = set(self.label_confidence) - set(self.detect_labels) + if unknown: + raise ValueError( + "label_confidence contains labels not selected by detect_labels: " + + ", ".join(sorted(unknown)) + ) + return self + + +class DetectionOperator(Operator): + """Load one registered model and normalize its per-frame detections.""" + + def __init__( + self, + node_id: str, + params: Mapping[str, Any], + *, + model_registry: DetectionModelRegistry, + ) -> None: + self._node_id = node_id + try: + self._params = DetectorParams.model_validate(dict(params)) + except Exception as exc: + raise ValueError(f"invalid detection node parameters: {exc}") from exc + + self._spec = model_registry.resolve(self._params.model) + selected = ( + self._spec.supported_labels + if self._params.detect_labels is None + else self._params.detect_labels + ) + unsupported = set(selected) - set(self._spec.supported_labels) + if unsupported: + raise ValueError( + f"model {self._spec.model_id!r} does not support label(s): " + + ", ".join(sorted(unsupported)) + ) + invalid_thresholds = set(self._params.label_confidence) - set( + self._spec.supported_labels + ) + if invalid_thresholds: + raise ValueError( + f"model {self._spec.model_id!r} does not support threshold label(s): " + + ", ".join(sorted(invalid_thresholds)) + ) + + self._selected_labels = tuple(selected) + self._selected_label_set = frozenset(selected) + self._minimum_backend_confidence = min( + ( + self._params.label_confidence.get(label, self._params.confidence) + for label in self._selected_labels + ), + default=self._params.confidence, + ) + self._model: DetectionModel = self._spec.factory(self._params.model_options) + if not isinstance(self._model, DetectionModel): + raise TypeError( + f"model factory for {self._spec.model_id!r} returned " + f"{type(self._model).__name__}, expected DetectionModel" + ) + + self._executor: Executor | None = None + self._loaded = False + self._closed = False + self._last_started_ns: int | None = None + self._processed = 0 + self._skipped_rate_limit = 0 + self._detections = 0 + self._last_inference_log_ns: int | None = None + self._log_window_frames = 0 + self._log_window_detections = 0 + self._log_window_inference_ms = 0.0 + self._log_window_labels: Counter[str] = Counter() + + @property + def model_spec(self) -> DetectionModelSpec: + return self._spec + + @property + def selected_labels(self) -> tuple[str, ...]: + return self._selected_labels + + @property + def attach_frame(self) -> bool: + return self._params.attach_frame + + async def setup(self, context: ComponentContext) -> None: + if self._closed: + raise RuntimeError("detection operator is already closed") + executor = context.metadata.get("thread_executor") + if executor is not None and not isinstance(executor, Executor): + raise TypeError("component context thread_executor is not an Executor") + self._executor = executor + load_started = perf_counter() + await run_blocking(self._model.load, executor=self._executor) + load_ms = (perf_counter() - load_started) * 1000.0 + self._loaded = True + _LOGGER.info( + "detection model loaded node=%s model=%s name=%s backend=%s " + "labels=%s load_ms=%.2f", + self._node_id, + self._spec.model_id, + self._spec.name, + self._spec.backend, + ",".join(self._selected_labels), + load_ms, + ) + + async def process( + self, envelope: Envelope[Any], input_port: str = "frames" + ) -> Emission | None: + del input_port + if not self._loaded: + raise RuntimeError("detection model has not been loaded") + frame = envelope.payload + if not isinstance(frame, ImageFrame): + raise TypeError( + f"{self._node_id} expected ImageFrame, got {type(frame).__name__}" + ) + if frame.is_encoded: + raise ValueError( + f"{self._node_id} requires a decoded ImageFrame; got codec " + f"{frame.codec!r}" + ) + + now = monotonic_ns() + if self._params.max_fps is not None and self._last_started_ns is not None: + minimum_interval_ns = int(1_000_000_000 / self._params.max_fps) + if now - self._last_started_ns < minimum_interval_ns: + self._skipped_rate_limit += 1 + return None + self._last_started_ns = now + + started = perf_counter() + predicted = await run_blocking( + self._model.predict, + frame, + labels=self._selected_labels, + confidence=self._minimum_backend_confidence, + executor=self._executor, + ) + inference_ms = (perf_counter() - started) * 1000.0 + detections = self._filter_detections(predicted) + self._processed += 1 + self._detections += len(detections) + self._record_inference_log( + envelope, + detections, + inference_ms=inference_ms, + now_ns=now, + ) + + result = DetectionResult( + detections=detections, + model_id=self._spec.model_id, + inference_ms=inference_ms, + model_name=self._spec.name, + source_frame=frame if self._params.attach_frame else None, + ) + output = envelope.with_payload( + result, + schema_name="DetectionResult", + schema_version=1, + ).with_attributes( + detection_model_id=self._spec.model_id, + detection_model_name=self._spec.name, + ) + return Emission("detections", output) + + async def stop(self) -> None: + if self._closed: + return + self._closed = True + try: + # close() is required to be idempotent and is also called after a + # partially failed load so native allocations are not leaked. + await run_blocking(self._model.close, executor=self._executor) + finally: + self._loaded = False + + async def health(self) -> HealthReport: + return HealthReport( + healthy=self._loaded, + detail=( + f"model={self._spec.model_id} loaded={self._loaded} " + f"frames={self._processed} detections={self._detections} " + f"rate_skipped={self._skipped_rate_limit}" + ), + ) + + def _record_inference_log( + self, + envelope: Envelope[Any], + detections: tuple[Detection, ...], + *, + inference_ms: float, + now_ns: int, + ) -> None: + interval_s = self._params.inference_log_interval_s + if interval_s is None: + return + + self._log_window_frames += 1 + self._log_window_detections += len(detections) + self._log_window_inference_ms += inference_ms + self._log_window_labels.update(item.label for item in detections) + + interval_ns = int(interval_s * 1_000_000_000) + if ( + self._last_inference_log_ns is not None + and now_ns - self._last_inference_log_ns < interval_ns + ): + return + + hit_labels = ",".join( + f"{label}:{count}" + for label, count in sorted(self._log_window_labels.items()) + ) or "none" + average_inference_ms = ( + self._log_window_inference_ms / self._log_window_frames + ) + _LOGGER.info( + "detection inference node=%s model=%s total_frames=%s " + "window_frames=%s window_detections=%s hit_labels=%s " + "avg_inference_ms=%.2f rate_skipped=%s source=%s sequence=%s", + self._node_id, + self._spec.model_id, + self._processed, + self._log_window_frames, + self._log_window_detections, + hit_labels, + average_inference_ms, + self._skipped_rate_limit, + envelope.source_id, + envelope.sequence, + ) + self._last_inference_log_ns = now_ns + self._log_window_frames = 0 + self._log_window_detections = 0 + self._log_window_inference_ms = 0.0 + self._log_window_labels.clear() + + def _filter_detections( + self, predicted: Sequence[Detection] + ) -> tuple[Detection, ...]: + normalized: list[Detection] = [] + for detection in predicted: + if not isinstance(detection, Detection): + raise TypeError( + f"model {self._spec.model_id!r} returned " + f"{type(detection).__name__}, expected Detection" + ) + if detection.label not in self._spec.supported_labels: + raise ValueError( + f"model {self._spec.model_id!r} returned unregistered label " + f"{detection.label!r}" + ) + if detection.label not in self._selected_label_set: + continue + if not math.isfinite(detection.confidence) or not ( + 0.0 <= detection.confidence <= 1.0 + ): + raise ValueError( + f"model returned invalid confidence {detection.confidence!r}" + ) + threshold = self._params.label_confidence.get( + detection.label, self._params.confidence + ) + if detection.confidence >= threshold: + normalized.append(detection) + return tuple(normalized) + + +__all__ = ["DetectionOperator", "DetectorParams"] diff --git a/src/cmvr_edge_ai/detection/plugins.py b/src/cmvr_edge_ai/detection/plugins.py new file mode 100644 index 0000000..0a3662c --- /dev/null +++ b/src/cmvr_edge_ai/detection/plugins.py @@ -0,0 +1,62 @@ +"""Pipeline plugin registrations for model-independent detection nodes.""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from cmvr_edge_ai.plugins import PluginKind, PluginRegistry, PluginSpec + +from .operator import DetectionOperator +from .registry import DetectionModelRegistry +from .rules import DetectionTriggerOperator +from .video import VideoDecoderOperator + + +def register_detection_plugins( + registry: PluginRegistry, + model_registry: DetectionModelRegistry, +) -> None: + def detector_factory(node_id: str, params: Mapping[str, Any]) -> DetectionOperator: + return DetectionOperator( + node_id, + params, + model_registry=model_registry, + ) + + registry.register( + PluginSpec( + plugin_id="media.video_decoder.pyav@1", + kind=PluginKind.OPERATOR, + factory=VideoDecoderOperator, + inputs={"frames": "ImageFrame/v1"}, + outputs={"frames": "ImageFrame/v1"}, + description="Stateful H264/H265 decoder backed by optional PyAV", + tags=frozenset({"media", "video", "decoder"}), + ) + ) + registry.register( + PluginSpec( + plugin_id="detection.model@1", + kind=PluginKind.OPERATOR, + factory=detector_factory, + inputs={"frames": "ImageFrame/v1"}, + outputs={"detections": "DetectionResult/v1"}, + description="Run a registered object-detection model", + tags=frozenset({"ai", "detection", "model"}), + ) + ) + registry.register( + PluginSpec( + plugin_id="detection.repeat_gate@1", + kind=PluginKind.OPERATOR, + factory=DetectionTriggerOperator, + inputs={"detections": "DetectionResult/v1"}, + outputs={"alerts": "DetectionAlert/v1"}, + description="Emit alerts after configurable repeated frame hits", + tags=frozenset({"detection", "rule", "temporal"}), + ) + ) + + +__all__ = ["register_detection_plugins"] diff --git a/src/cmvr_edge_ai/detection/registry.py b/src/cmvr_edge_ai/detection/registry.py new file mode 100644 index 0000000..f2bdec3 --- /dev/null +++ b/src/cmvr_edge_ai/detection/registry.py @@ -0,0 +1,154 @@ +"""Explicit registry for deployable object-detection model artifacts.""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from importlib import metadata +from typing import Any, cast + +from .base import DetectionModel + + +class DetectionModelRegistryError(RuntimeError): + """Base class for detection model registration and lookup failures.""" + + +class DuplicateDetectionModelError(DetectionModelRegistryError): + """Raised when a model identifier is registered more than once.""" + + +class UnknownDetectionModelError(DetectionModelRegistryError): + """Raised when a requested model identifier has not been registered.""" + + +DetectionModelFactory = Callable[[Mapping[str, Any]], DetectionModel] + + +@dataclass(frozen=True, slots=True) +class DetectionModelSpec: + """Static metadata and factory for one versioned model artifact.""" + + model_id: str + name: str + supported_labels: tuple[str, ...] + factory: DetectionModelFactory + backend: str + description: str = "" + + def __post_init__(self) -> None: + if not isinstance(self.model_id, str) or "@" not in self.model_id: + raise ValueError( + "model id must be versioned (for example detector-name@1): " + f"{self.model_id}" + ) + identifier, version = self.model_id.rsplit("@", maxsplit=1) + if not identifier or not version.isdigit() or int(version) < 1: + raise ValueError( + "model id must end in a positive numeric version: " f"{self.model_id}" + ) + + if not isinstance(self.name, str) or not self.name.strip(): + raise ValueError("model name must be a non-empty string") + if not isinstance(self.backend, str) or not self.backend.strip(): + raise ValueError("model backend must be a non-empty string") + if not isinstance(self.description, str): + raise TypeError("model description must be a string") + if not callable(self.factory): + raise TypeError("detection model factory must be callable") + + labels_value = self.supported_labels + if not isinstance(labels_value, Sequence) or isinstance( + labels_value, (str, bytes, bytearray) + ): + raise TypeError("supported_labels must be an ordered collection") + labels = tuple(labels_value) + if not labels: + raise ValueError("supported_labels must not be empty") + + invalid = [ + repr(label) + for label in labels + if not isinstance(label, str) or not label.strip() + ] + if invalid: + raise ValueError( + "supported_labels must contain non-empty strings: " + ", ".join(invalid) + ) + seen: set[str] = set() + duplicates: list[str] = [] + for label in labels: + if label in seen and label not in duplicates: + duplicates.append(label) + seen.add(label) + if duplicates: + raise ValueError( + "supported_labels must be unique; duplicate(s): " + + ", ".join(duplicates) + ) + + object.__setattr__(self, "name", self.name.strip()) + object.__setattr__(self, "backend", self.backend.strip()) + object.__setattr__(self, "supported_labels", labels) + + +class DetectionModelRegistry: + """In-memory model registry with optional Python entry-point discovery.""" + + ENTRY_POINT_GROUP = "cmvr_edge_ai.detection_models" + + def __init__(self) -> None: + self._models: dict[str, DetectionModelSpec] = {} + + def register(self, spec: DetectionModelSpec) -> DetectionModelSpec: + if not isinstance(spec, DetectionModelSpec): + raise TypeError("detection model registry accepts DetectionModelSpec") + if spec.model_id in self._models: + raise DuplicateDetectionModelError( + f"detection model already registered: {spec.model_id}" + ) + self._models[spec.model_id] = spec + return spec + + def resolve(self, model_id: str) -> DetectionModelSpec: + try: + return self._models[model_id] + except KeyError as exc: + available = ", ".join(sorted(self._models)) or "" + raise UnknownDetectionModelError( + f"unknown detection model {model_id!r}; available models: {available}" + ) from exc + + def specs(self) -> tuple[DetectionModelSpec, ...]: + return tuple(self._models[key] for key in sorted(self._models)) + + def load_entry_points(self) -> None: + """Load model specs or registration callbacks from installed packages.""" + + discovered = cast(Any, metadata.entry_points()) + selected = ( + discovered.select(group=self.ENTRY_POINT_GROUP) + if hasattr(discovered, "select") + else discovered.get(self.ENTRY_POINT_GROUP, ()) + ) + for entry_point in selected: + loaded = entry_point.load() + if isinstance(loaded, DetectionModelSpec): + self.register(loaded) + elif callable(loaded): + loaded(self) + else: + raise DetectionModelRegistryError( + f"entry point {entry_point.name!r} must expose " + "DetectionModelSpec or a registry callback" + ) + + +__all__ = [ + "DetectionModelFactory", + "DetectionModelRegistry", + "DetectionModelRegistryError", + "DetectionModelSpec", + "DuplicateDetectionModelError", + "UnknownDetectionModelError", +] diff --git a/src/cmvr_edge_ai/detection/rules.py b/src/cmvr_edge_ai/detection/rules.py new file mode 100644 index 0000000..5bf9685 --- /dev/null +++ b/src/cmvr_edge_ai/detection/rules.py @@ -0,0 +1,661 @@ +"""Stateful temporal rules for normalized object-detection results.""" + +from __future__ import annotations + +from collections import deque +from collections.abc import Mapping, Sequence +from concurrent.futures import Executor +from dataclasses import dataclass, field, replace +import logging +import math +from typing import Any, Literal, cast +from uuid import uuid4 + +from cmvr_edge_ai.contracts import ( + BoundingBox, + Detection, + DetectionAlert, + DetectionResult, + EncodedImage, + ImageFrame, +) +from cmvr_edge_ai.core import ComponentContext, Emission, Envelope, HealthReport, Operator +from cmvr_edge_ai.workers import run_blocking + +from .annotation import ensure_annotation_support, render_annotated_jpeg + + +TimeSource = Literal["auto", "captured", "received"] +RuleScope = Literal["source", "track"] +FrameKey = tuple[str, int] +StateKey = tuple[str, str, str] + + +_LOGGER = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True) +class DetectionTriggerRule: + """Validated deployment rule used by :class:`DetectionTriggerOperator`.""" + + rule_id: str + labels: tuple[str, ...] + min_hits: int + window_ms: float + cooldown_ms: float + min_confidence: float + scope: RuleScope + + @property + def window_ns(self) -> int: + return int(self.window_ms * 1_000_000) + + @property + def cooldown_ns(self) -> int: + return int(self.cooldown_ms * 1_000_000) + + +@dataclass(frozen=True, slots=True) +class AlertImageOptions: + enabled: bool = False + jpeg_quality: int = 85 + + +@dataclass(frozen=True, slots=True) +class _Hit: + timestamp_ns: int + labels: tuple[str, ...] + max_confidence: float + + +@dataclass(slots=True) +class _TriggerState: + hits: deque[_Hit] = field(default_factory=deque) + # Kept independently from ``hits`` so replaying the threshold frame cannot + # immediately retrigger a zero-cooldown rule after its hit window resets. + seen_frames: dict[FrameKey, int] = field(default_factory=dict) + cooldown_until_ns: int | None = None + last_observed_ns: int | None = None + + +class DetectionTriggerOperator(Operator): + """Emit alerts after distinct frames satisfy temporal detection rules. + + A hit is one matching *frame* for one rule and scope, regardless of how + many matching boxes that frame contains. Source-scoped rules aggregate a + camera stream. Track-scoped rules maintain independent windows per + ``track_id`` and ignore detections for which no track ID is available. + + Once a rule triggers, its hit window is cleared. Frames observed during + cooldown are deliberately not accumulated; a fresh ``min_hits`` must be + observed after cooldown expires. Recent frame identities remain cached + for one window to prevent duplicate/replayed envelopes from being counted. + """ + + _PARAM_KEYS = frozenset({"rules", "time_source", "alert_image"}) + _RULE_KEYS = frozenset( + { + "id", + "labels", + "min_hits", + "window_ms", + "cooldown_ms", + "min_confidence", + "scope", + } + ) + + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + if not isinstance(params, Mapping): + raise TypeError("detection trigger params must be a mapping") + unknown = set(params) - self._PARAM_KEYS + if unknown: + raise ValueError( + "unknown detection trigger parameter(s): " + + ", ".join(sorted(str(item) for item in unknown)) + ) + + self._node_id = node_id + self._time_source = _parse_time_source(params.get("time_source", "auto")) + self._alert_image = _parse_alert_image(params.get("alert_image")) + self._rules = _parse_rules(params.get("rules")) + self._rules_by_id = {rule.rule_id: rule for rule in self._rules} + self._states: dict[StateKey, _TriggerState] = {} + self._executor: Executor | None = None + + self._processed_frames = 0 + self._alerts_emitted = 0 + self._cooldown_suppressed = 0 + self._duplicate_frames = 0 + self._stale_frames = 0 + self._untracked_detections = 0 + self._invalid_detections = 0 + self._alert_images_encoded = 0 + self._alert_images_attached = 0 + self._alert_image_missing_frames = 0 + self._alert_image_failures = 0 + + @property + def rules(self) -> tuple[DetectionTriggerRule, ...]: + """Return the immutable, validated rules (primarily for diagnostics).""" + + return self._rules + + @property + def alert_image_enabled(self) -> bool: + return self._alert_image.enabled + + async def setup(self, context: ComponentContext) -> None: + executor = context.metadata.get("thread_executor") + if executor is not None and not isinstance(executor, Executor): + raise TypeError("component context thread_executor is not an Executor") + self._executor = executor + if self._alert_image.enabled: + # Configuration errors should be discovered before camera traffic + # starts, while individual corrupt frames remain fail-soft below. + ensure_annotation_support() + + async def process( + self, + envelope: Envelope[Any], + input_port: str = "input", + ) -> tuple[Emission, ...] | None: + del input_port + result = envelope.payload + if not isinstance(result, DetectionResult): + raise TypeError( + f"{self._node_id} expected DetectionResult, " + f"got {type(result).__name__}" + ) + + timestamp_ns = self._timestamp_for(envelope) + self._processed_frames += 1 + frame_key = (envelope.source_id, envelope.sequence) + emissions: list[Emission] = [] + detections, invalid_count = _wire_safe_detections(result.detections) + if invalid_count: + self._invalid_detections += invalid_count + _LOGGER.warning( + "ignored invalid detections node=%s source=%s sequence=%s count=%s", + self._node_id, + envelope.source_id, + envelope.sequence, + invalid_count, + ) + + for rule in self._rules: + matching = _matching_detections(detections, rule) + if not matching: + continue + + if rule.scope == "source": + emission = self._observe( + rule=rule, + scope_id=envelope.source_id, + source_id=envelope.source_id, + frame_key=frame_key, + timestamp_ns=timestamp_ns, + detections=matching, + result=result, + envelope=envelope, + ) + if emission is not None: + emissions.append(emission) + continue + + by_track: dict[str, list[Detection]] = {} + for detection in matching: + if detection.track_id is None or not str(detection.track_id): + self._untracked_detections += 1 + continue + by_track.setdefault(str(detection.track_id), []).append(detection) + for track_id, track_detections in by_track.items(): + emission = self._observe( + rule=rule, + scope_id=track_id, + source_id=envelope.source_id, + frame_key=frame_key, + timestamp_ns=timestamp_ns, + detections=tuple(track_detections), + result=result, + envelope=envelope, + ) + if emission is not None: + emissions.append(emission) + + self._prune_inactive_states(timestamp_ns) + if emissions and self._alert_image.enabled: + emissions = await self._attach_alert_image( + emissions, + result.source_frame, + envelope, + ) + return tuple(emissions) if emissions else None + + async def stop(self) -> None: + self._executor = None + + async def health(self) -> HealthReport: + return HealthReport( + not ( + self._invalid_detections > 0 + or ( + self._alert_image.enabled + and ( + self._alert_image_missing_frames > 0 + or self._alert_image_failures > 0 + ) + ) + ), + " ".join( + ( + f"processed_frames={self._processed_frames}", + f"alerts_emitted={self._alerts_emitted}", + f"cooldown_suppressed={self._cooldown_suppressed}", + f"duplicate_frames={self._duplicate_frames}", + f"stale_frames={self._stale_frames}", + f"untracked_detections={self._untracked_detections}", + f"invalid_detections={self._invalid_detections}", + f"alert_images_encoded={self._alert_images_encoded}", + f"alert_images_attached={self._alert_images_attached}", + f"alert_image_missing_frames={self._alert_image_missing_frames}", + f"alert_image_failures={self._alert_image_failures}", + f"active_states={len(self._states)}", + ) + ), + ) + + async def _attach_alert_image( + self, + emissions: list[Emission], + frame: ImageFrame | None, + source_envelope: Envelope[Any], + ) -> list[Emission]: + if frame is None: + self._alert_image_missing_frames += 1 + _LOGGER.warning( + "detection alert image omitted node=%s source=%s sequence=%s: " + "DetectionResult has no source_frame", + self._node_id, + source_envelope.source_id, + source_envelope.sequence, + ) + return emissions + + # Multiple rules/tracks can trigger from the same threshold frame. A + # single union image bounds edge CPU and memory while retaining every + # box relevant to at least one emitted alert. + unique_detections: list[Detection] = [] + for emission in emissions: + alert = emission.envelope.payload + if not isinstance(alert, DetectionAlert): + continue + for detection in alert.detections: + if detection not in unique_detections: + unique_detections.append(detection) + + try: + image = await run_blocking( + render_annotated_jpeg, + frame, + tuple(unique_detections), + jpeg_quality=self._alert_image.jpeg_quality, + executor=self._executor, + ) + if not isinstance(image, EncodedImage): + raise TypeError( + "alert image renderer returned " + f"{type(image).__name__}, expected EncodedImage" + ) + except Exception: + self._alert_image_failures += 1 + _LOGGER.warning( + "detection alert image rendering failed node=%s source=%s " + "sequence=%s; sending structured alert without image", + self._node_id, + source_envelope.source_id, + source_envelope.sequence, + exc_info=True, + ) + return emissions + + updated: list[Emission] = [] + for emission in emissions: + alert = emission.envelope.payload + if not isinstance(alert, DetectionAlert): + updated.append(emission) + continue + updated.append( + Emission( + emission.port, + emission.envelope.with_payload(replace(alert, image=image)), + ) + ) + self._alert_images_encoded += 1 + self._alert_images_attached += len(updated) + return updated + + def _timestamp_for(self, envelope: Envelope[Any]) -> int: + if self._time_source == "captured": + if envelope.captured_at_ns is None: + raise ValueError( + f"{self._node_id} requires captured_at_ns when " + "time_source='captured'" + ) + return envelope.captured_at_ns + if self._time_source == "received": + return envelope.received_at_ns + return ( + envelope.captured_at_ns + if envelope.captured_at_ns is not None + else envelope.received_at_ns + ) + + def _observe( + self, + *, + rule: DetectionTriggerRule, + scope_id: str, + source_id: str, + frame_key: FrameKey, + timestamp_ns: int, + detections: tuple[Detection, ...], + result: DetectionResult, + envelope: Envelope[Any], + ) -> Emission | None: + state_key = (rule.rule_id, source_id, scope_id) + state = self._states.setdefault(state_key, _TriggerState()) + + if state.last_observed_ns is not None and timestamp_ns < state.last_observed_ns: + self._stale_frames += 1 + return None + state.last_observed_ns = timestamp_ns + self._evict_old_entries(state, rule, timestamp_ns) + + if frame_key in state.seen_frames: + self._duplicate_frames += 1 + return None + state.seen_frames[frame_key] = timestamp_ns + + if ( + state.cooldown_until_ns is not None + and timestamp_ns < state.cooldown_until_ns + ): + self._cooldown_suppressed += 1 + return None + if state.cooldown_until_ns is not None: + state.cooldown_until_ns = None + state.hits.clear() + + present_labels = {detection.label for detection in detections} + hit_labels = tuple(label for label in rule.labels if label in present_labels) + hit = _Hit( + timestamp_ns=timestamp_ns, + labels=hit_labels, + max_confidence=max(detection.confidence for detection in detections), + ) + state.hits.append(hit) + if len(state.hits) < rule.min_hits: + return None + + hits = tuple(state.hits) + labels_seen = {label for item in hits for label in item.labels} + alert = DetectionAlert( + event_id=uuid4().hex, + rule_id=rule.rule_id, + model_id=result.model_id, + model_name=result.model_name or result.model_id, + labels=tuple(label for label in rule.labels if label in labels_seen), + scope=rule.scope, + scope_id=scope_id, + hit_count=len(hits), + window_ms=rule.window_ms, + first_seen_ns=hits[0].timestamp_ns, + last_seen_ns=hits[-1].timestamp_ns, + triggered_at_ns=timestamp_ns, + max_confidence=max(item.max_confidence for item in hits), + # Only boxes from the threshold frame are attached. Retaining all + # boxes from every hit would multiply memory and HTTP payload size. + detections=detections, + ) + state.hits.clear() + state.cooldown_until_ns = timestamp_ns + rule.cooldown_ns + self._alerts_emitted += 1 + return Emission( + "alerts", + envelope.with_payload( + alert, + schema_name="DetectionAlert", + schema_version=1, + ), + ) + + @staticmethod + def _evict_old_entries( + state: _TriggerState, + rule: DetectionTriggerRule, + timestamp_ns: int, + ) -> None: + cutoff_ns = timestamp_ns - rule.window_ns + # The window is inclusive at its lower boundary: an observation exactly + # ``window_ms`` old remains eligible. + while state.hits and state.hits[0].timestamp_ns < cutoff_ns: + state.hits.popleft() + expired_frames = [ + key for key, seen_ns in state.seen_frames.items() if seen_ns < cutoff_ns + ] + for key in expired_frames: + del state.seen_frames[key] + + def _prune_inactive_states(self, timestamp_ns: int) -> None: + expired: list[StateKey] = [] + for key, state in self._states.items(): + rule = self._rules_by_id[key[0]] + if state.last_observed_ns is None or timestamp_ns < state.last_observed_ns: + continue + self._evict_old_entries(state, rule, timestamp_ns) + if ( + state.cooldown_until_ns is not None + and timestamp_ns >= state.cooldown_until_ns + ): + state.cooldown_until_ns = None + if ( + not state.hits + and not state.seen_frames + and state.cooldown_until_ns is None + and timestamp_ns - state.last_observed_ns > rule.window_ns + ): + expired.append(key) + for key in expired: + del self._states[key] + + +def _parse_time_source(value: Any) -> TimeSource: + if not isinstance(value, str) or value not in {"auto", "captured", "received"}: + raise ValueError("time_source must be one of: auto, captured, received") + return cast(TimeSource, value) + + +def _parse_alert_image(value: Any) -> AlertImageOptions: + if value is None: + return AlertImageOptions() + if not isinstance(value, Mapping): + raise ValueError("alert_image must be a mapping") + unknown = set(value) - {"enabled", "jpeg_quality"} + if unknown: + raise ValueError( + "alert_image has unknown field(s): " + + ", ".join(sorted(str(item) for item in unknown)) + ) + enabled = value.get("enabled", False) + if not isinstance(enabled, bool): + raise ValueError("alert_image.enabled must be a boolean") + jpeg_quality = _strict_int( + value.get("jpeg_quality", 85), "alert_image.jpeg_quality" + ) + if not 1 <= jpeg_quality <= 95: + raise ValueError("alert_image.jpeg_quality must be between 1 and 95") + return AlertImageOptions(enabled=enabled, jpeg_quality=jpeg_quality) + + +def _parse_rules(value: Any) -> tuple[DetectionTriggerRule, ...]: + if not isinstance(value, Sequence) or isinstance(value, (str, bytes)): + raise ValueError("rules must be a non-empty list") + if not value: + raise ValueError("rules must be a non-empty list") + + parsed: list[DetectionTriggerRule] = [] + seen_ids: set[str] = set() + for index, raw_rule in enumerate(value): + path = f"rules[{index}]" + if not isinstance(raw_rule, Mapping): + raise ValueError(f"{path} must be a mapping") + unknown = set(raw_rule) - DetectionTriggerOperator._RULE_KEYS + if unknown: + raise ValueError( + f"{path} has unknown field(s): " + + ", ".join(sorted(str(item) for item in unknown)) + ) + + rule_id = _non_empty_string(raw_rule.get("id"), f"{path}.id") + if rule_id in seen_ids: + raise ValueError(f"duplicate detection rule id: {rule_id}") + seen_ids.add(rule_id) + + labels_value = raw_rule.get("labels") + if not isinstance(labels_value, Sequence) or isinstance( + labels_value, (str, bytes) + ): + raise ValueError(f"{path}.labels must be a non-empty list") + labels = tuple( + _non_empty_string(label, f"{path}.labels[{label_index}]") + for label_index, label in enumerate(labels_value) + ) + if not labels: + raise ValueError(f"{path}.labels must be a non-empty list") + if len(set(labels)) != len(labels): + raise ValueError(f"{path}.labels must not contain duplicates") + + min_hits = _strict_int(raw_rule.get("min_hits"), f"{path}.min_hits") + if min_hits < 1: + raise ValueError(f"{path}.min_hits must be at least 1") + window_ms = _finite_number(raw_rule.get("window_ms"), f"{path}.window_ms") + if window_ms <= 0 or int(window_ms * 1_000_000) < 1: + raise ValueError(f"{path}.window_ms must be positive") + cooldown_ms = _finite_number( + raw_rule.get("cooldown_ms", 0.0), f"{path}.cooldown_ms" + ) + if cooldown_ms < 0: + raise ValueError(f"{path}.cooldown_ms must not be negative") + min_confidence = _finite_number( + raw_rule.get("min_confidence", 0.0), f"{path}.min_confidence" + ) + if not 0.0 <= min_confidence <= 1.0: + raise ValueError(f"{path}.min_confidence must be between 0 and 1") + scope = raw_rule.get("scope", "source") + if not isinstance(scope, str) or scope not in {"source", "track"}: + raise ValueError(f"{path}.scope must be 'source' or 'track'") + + parsed.append( + DetectionTriggerRule( + rule_id=rule_id, + labels=labels, + min_hits=min_hits, + window_ms=window_ms, + cooldown_ms=cooldown_ms, + min_confidence=min_confidence, + scope=cast(RuleScope, scope), + ) + ) + return tuple(parsed) + + +def _matching_detections( + detections: tuple[Detection, ...], rule: DetectionTriggerRule +) -> tuple[Detection, ...]: + labels = set(rule.labels) + matching: list[Detection] = [] + for detection in detections: + if not isinstance(detection, Detection): + raise TypeError( + "DetectionResult.detections must contain Detection instances" + ) + if not math.isfinite(detection.confidence): + raise ValueError("detection confidence must be finite") + if detection.label in labels and detection.confidence >= rule.min_confidence: + matching.append(detection) + return tuple(matching) + + +def _wire_safe_detections( + detections: tuple[Detection, ...], +) -> tuple[tuple[Detection, ...], int]: + """Drop malformed items that could make an entire JSON alert unsendable.""" + + valid: list[Detection] = [] + invalid_count = 0 + for detection in detections: + if not isinstance(detection, Detection): + raise TypeError( + "DetectionResult.detections must contain Detection instances" + ) + if not _wire_safe_detection(detection): + invalid_count += 1 + continue + valid.append(detection) + return tuple(valid), invalid_count + + +def _wire_safe_detection(detection: Detection) -> bool: + if not isinstance(detection.label, str) or not detection.label: + return False + if ( + isinstance(detection.confidence, bool) + or not isinstance(detection.confidence, (int, float)) + or not math.isfinite(float(detection.confidence)) + or not 0.0 <= float(detection.confidence) <= 1.0 + ): + return False + if detection.track_id is not None and not isinstance(detection.track_id, str): + return False + if not isinstance(detection.box, BoundingBox): + return False + coordinates = ( + detection.box.x_min, + detection.box.y_min, + detection.box.x_max, + detection.box.y_max, + ) + if any( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(float(value)) + for value in coordinates + ): + return False + x_min, y_min, x_max, y_max = (float(value) for value in coordinates) + return x_max > x_min and y_max > y_min + + +def _non_empty_string(value: Any, path: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise ValueError(f"{path} must be a non-empty string") + return value.strip() + + +def _strict_int(value: Any, path: str) -> int: + if isinstance(value, bool) or not isinstance(value, int): + raise ValueError(f"{path} must be an integer") + return value + + +def _finite_number(value: Any, path: str) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError(f"{path} must be a number") + parsed = float(value) + if not math.isfinite(parsed): + raise ValueError(f"{path} must be finite") + return parsed + + +__all__ = ["AlertImageOptions", "DetectionTriggerOperator", "DetectionTriggerRule"] diff --git a/src/cmvr_edge_ai/detection/video.py b/src/cmvr_edge_ai/detection/video.py new file mode 100644 index 0000000..29976c8 --- /dev/null +++ b/src/cmvr_edge_ai/detection/video.py @@ -0,0 +1,319 @@ +"""Stateful decoding of encoded camera packets into raw image frames. + +The framework keeps the concrete FFmpeg dependency optional. Importing this +module therefore never imports PyAV; the default backend does so lazily when +the first encoded key frame is received. Tests and alternative deployments +can inject another backend through ``backend_factory``. +""" + +from __future__ import annotations + +import threading +from collections.abc import Callable, Iterable, Mapping +from concurrent.futures import Executor +from dataclasses import dataclass +from importlib import import_module +from typing import Any, Protocol + +from cmvr_edge_ai.contracts import ImageFrame +from cmvr_edge_ai.core.component import ( + ComponentContext, + Emission, + HealthReport, + Operator, +) +from cmvr_edge_ai.core.envelope import Envelope +from cmvr_edge_ai.workers.thread import run_blocking + + +class VideoDecodeError(RuntimeError): + """A recoverable malformed/discontinuous encoded-video packet.""" + + +@dataclass(frozen=True, slots=True) +class DecodedVideoFrame: + """One packed image produced by a decoder backend.""" + + data: bytes + width: int + height: int + pixel_format: str = "BGR8" + + def __post_init__(self) -> None: + if not isinstance(self.data, bytes): + raise TypeError("decoded video frame data must be bytes") + if self.width <= 0 or self.height <= 0: + raise ValueError("decoded video frame dimensions must be positive") + if not isinstance(self.pixel_format, str) or not self.pixel_format.strip(): + raise ValueError("decoded video frame pixel_format must not be empty") + + +class VideoDecoderBackend(Protocol): + """Synchronous stateful decoder owned by one operator instance.""" + + def decode(self, data: bytes) -> Iterable[DecodedVideoFrame]: + """Consume one encoded packet and return zero or more complete images.""" + + def close(self) -> None: + """Release native decoder resources; implementations must be idempotent.""" + + +VideoDecoderBackendFactory = Callable[[str], VideoDecoderBackend] + + +class _PyAvDecoderBackend: + """Default Annex-B H.264/H.265 decoder backed by optional PyAV.""" + + def __init__(self, codec: str) -> None: + try: + av = import_module("av") + except ImportError as exc: + raise RuntimeError( + "PyAV is required to decode encoded video; install the optional " + "video dependencies (for example: pip install -e '.[video]')" + ) from exc + + self._av = av + self._codec: Any = av.CodecContext.create(codec, "r") + + def decode(self, data: bytes) -> tuple[DecodedVideoFrame, ...]: + if self._codec is None: + raise RuntimeError("video decoder backend is closed") + try: + frames = self._codec.decode(self._av.Packet(data)) + except Exception as exc: + ffmpeg_error = getattr( + getattr(self._av, "error", None), "FFmpegError", None + ) + if isinstance(ffmpeg_error, type) and isinstance(exc, ffmpeg_error): + raise VideoDecodeError(str(exc)) from exc + raise + + decoded: list[DecodedVideoFrame] = [] + for frame in frames: + image = frame.to_ndarray(format="bgr24") + decoded.append( + DecodedVideoFrame( + data=image.tobytes(order="C"), + width=int(frame.width), + height=int(frame.height), + pixel_format="BGR8", + ) + ) + return tuple(decoded) + + def close(self) -> None: + # PyAV CodecContext does not expose a stable explicit close API across + # supported versions. Dropping the final reference releases it. + self._codec = None + + +class VideoDecoderOperator(Operator): + """Decode one encoded stream while recovering at key-frame boundaries. + + A decoder context is valid only for one source/session/codec/dimension + identity and a continuous sequence. Any identity change or sequence gap + closes that context and suppresses packets until the next key frame. Raw + frames are emitted unchanged and never import the optional decoder library. + """ + + def __init__( + self, + node_id: str, + params: Mapping[str, Any], + *, + backend_factory: VideoDecoderBackendFactory | None = None, + ) -> None: + if not isinstance(node_id, str) or not node_id.strip(): + raise ValueError("video decoder node_id must not be empty") + if params: + unknown = ", ".join(sorted(str(key) for key in params)) + raise ValueError(f"video decoder does not support parameters: {unknown}") + + self._node_id = node_id + self._backend_factory = backend_factory or _PyAvDecoderBackend + self._backend: VideoDecoderBackend | None = None + self._backend_codec: str | None = None + self._backend_lock = threading.Lock() + self._executor: Executor | None = None + self._stream_identity: tuple[str, str | None, str, int, int] | None = None + self._last_sequence: int | None = None + self._waiting_for_keyframe = True + self._decoded_frames = 0 + self._dropped_until_keyframe = 0 + self._resets = 0 + self._decode_errors = 0 + + async def setup(self, context: ComponentContext) -> None: + executor = context.metadata.get("thread_executor") + if executor is not None and not isinstance(executor, Executor): + raise TypeError("component context thread_executor must be an Executor") + self._executor = executor + + async def process( + self, + envelope: Envelope[Any], + input_port: str = "input", + ) -> Emission | tuple[Emission, ...] | None: + del input_port + payload = envelope.payload + if not isinstance(payload, ImageFrame): + raise TypeError( + f"{self._node_id} expected ImageFrame, got " f"{type(payload).__name__}" + ) + + codec = _normalize_codec(payload.codec) + if codec is None: + if self._stream_identity is not None or self._backend is not None: + await self._reset_stream() + return Emission("frames", envelope) + + if payload.width <= 0 or payload.height <= 0: + raise ValueError("encoded image dimensions must be positive") + if not payload.data: + raise ValueError("encoded image data must not be empty") + + identity = ( + envelope.source_id, + envelope.session_id, + codec, + payload.width, + payload.height, + ) + identity_changed = self._stream_identity != identity + sequence_gap = ( + not identity_changed + and self._last_sequence is not None + and envelope.sequence != self._last_sequence + 1 + ) + if identity_changed or sequence_gap: + await self._reset_stream(identity=identity) + + self._stream_identity = identity + self._last_sequence = envelope.sequence + if self._waiting_for_keyframe and not payload.is_key_frame: + self._dropped_until_keyframe += 1 + return None + + try: + frames = await run_blocking( + self._decode_sync, + codec, + payload.data, + executor=self._executor, + ) + except VideoDecodeError: + self._decode_errors += 1 + await self._reset_stream(identity=identity) + self._last_sequence = envelope.sequence + return None + + # A successfully accepted key frame establishes decoder continuity even + # when FFmpeg buffers it and returns no image until a later packet. + self._waiting_for_keyframe = False + emissions: list[Emission] = [] + for frame in frames: + decoded = ImageFrame( + data=frame.data, + width=frame.width, + height=frame.height, + pixel_format=frame.pixel_format, + codec="none", + is_key_frame=False, + intrinsics=payload.intrinsics, + ) + emissions.append( + Emission( + "frames", + envelope.with_payload( + decoded, + schema_name="ImageFrame", + schema_version=1, + ), + ) + ) + self._decoded_frames += len(emissions) + return tuple(emissions) if emissions else None + + async def stop(self) -> None: + await self._reset_stream(count_reset=False) + self._executor = None + + async def health(self) -> HealthReport: + return HealthReport( + True, + "decoded_frames={} dropped_until_keyframe={} resets={} " + "decode_errors={} waiting_for_keyframe={}".format( + self._decoded_frames, + self._dropped_until_keyframe, + self._resets, + self._decode_errors, + self._waiting_for_keyframe, + ), + ) + + async def _reset_stream( + self, + *, + identity: tuple[str, str | None, str, int, int] | None = None, + count_reset: bool = True, + ) -> None: + had_state = self._stream_identity is not None or self._backend is not None + if had_state: + # Always synchronize with the backend lock once a stream exists. + # If an awaited executor call was cancelled while native decoding + # continued, checking only ``self._backend`` here could race before + # the worker publishes the newly-created backend and leak it. + await run_blocking(self._close_backend_sync, executor=self._executor) + self._stream_identity = identity + self._last_sequence = None + self._waiting_for_keyframe = True + if count_reset and had_state: + self._resets += 1 + + def _decode_sync( + self, + codec: str, + data: bytes, + ) -> tuple[DecodedVideoFrame, ...]: + with self._backend_lock: + if self._backend is None: + self._backend = self._backend_factory(codec) + self._backend_codec = codec + elif self._backend_codec != codec: + # The async state machine normally closes it first. Retain a + # defensive guard in case this helper is reused independently. + self._backend.close() + self._backend = self._backend_factory(codec) + self._backend_codec = codec + return tuple(self._backend.decode(data)) + + def _close_backend_sync(self) -> None: + with self._backend_lock: + backend = self._backend + self._backend = None + self._backend_codec = None + if backend is not None: + backend.close() + + +def _normalize_codec(value: str) -> str | None: + if not isinstance(value, str): + raise TypeError("image codec must be a string") + codec = value.strip().lower().lstrip(".") + if codec in {"", "none", "raw"}: + return None + if codec in {"h264", "avc", "avc1"}: + return "h264" + if codec in {"h265", "hevc", "hev1", "hvc1"}: + return "hevc" + raise ValueError(f"unsupported encoded video codec: {value!r}") + + +__all__ = [ + "DecodedVideoFrame", + "VideoDecodeError", + "VideoDecoderBackend", + "VideoDecoderBackendFactory", + "VideoDecoderOperator", +] diff --git a/src/cmvr_edge_ai/observability/__init__.py b/src/cmvr_edge_ai/observability/__init__.py new file mode 100644 index 0000000..10eaf99 --- /dev/null +++ b/src/cmvr_edge_ai/observability/__init__.py @@ -0,0 +1,5 @@ +"""Health, logging and metrics helpers.""" + +from .logging import JsonFormatter, configure_logging + +__all__ = ["JsonFormatter", "configure_logging"] diff --git a/src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..74c8884 Binary files /dev/null and b/src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..9d3d2ef Binary files /dev/null and b/src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/observability/__pycache__/logging.cpython-310.pyc b/src/cmvr_edge_ai/observability/__pycache__/logging.cpython-310.pyc new file mode 100644 index 0000000..91ad56d Binary files /dev/null and b/src/cmvr_edge_ai/observability/__pycache__/logging.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/observability/__pycache__/logging.cpython-312.pyc b/src/cmvr_edge_ai/observability/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000..9d80bbd Binary files /dev/null and b/src/cmvr_edge_ai/observability/__pycache__/logging.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/observability/logging.py b/src/cmvr_edge_ai/observability/logging.py new file mode 100644 index 0000000..de06e0d --- /dev/null +++ b/src/cmvr_edge_ai/observability/logging.py @@ -0,0 +1,36 @@ +"""Low-overhead text or JSON logging setup.""" + +from __future__ import annotations + +import json +import logging +from datetime import datetime, timezone +from typing import Any + + +class JsonFormatter(logging.Formatter): + def format(self, record: logging.LogRecord) -> str: + payload: dict[str, Any] = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "level": record.levelname, + "logger": record.name, + "message": record.getMessage(), + } + if record.exc_info: + payload["exception"] = self.formatException(record.exc_info) + return json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + + +def configure_logging(level: str = "INFO", log_format: str = "text") -> None: + handler = logging.StreamHandler() + if log_format == "json": + handler.setFormatter(JsonFormatter()) + elif log_format == "text": + handler.setFormatter( + logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s") + ) + else: + raise ValueError("log_format must be 'text' or 'json'") + logging.basicConfig( + level=getattr(logging, level.upper()), handlers=[handler], force=True + ) diff --git a/src/cmvr_edge_ai/plugins/__init__.py b/src/cmvr_edge_ai/plugins/__init__.py new file mode 100644 index 0000000..8542898 --- /dev/null +++ b/src/cmvr_edge_ai/plugins/__init__.py @@ -0,0 +1,23 @@ +"""Plugin discovery and registration.""" + +from .registry import ( + DuplicatePluginError, + PluginError, + PluginKind, + PluginRegistry, + PluginSpec, + UnknownPluginError, + schemas_compatible, +) +from .builtin import register_builtin_plugins + +__all__ = [ + "DuplicatePluginError", + "PluginError", + "PluginKind", + "PluginRegistry", + "PluginSpec", + "UnknownPluginError", + "schemas_compatible", + "register_builtin_plugins", +] diff --git a/src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..7666180 Binary files /dev/null and b/src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..14d417c Binary files /dev/null and b/src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-310.pyc b/src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-310.pyc new file mode 100644 index 0000000..ae13b9a Binary files /dev/null and b/src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-312.pyc b/src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-312.pyc new file mode 100644 index 0000000..65a69c1 Binary files /dev/null and b/src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-310.pyc b/src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-310.pyc new file mode 100644 index 0000000..78c2a8f Binary files /dev/null and b/src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-312.pyc b/src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-312.pyc new file mode 100644 index 0000000..392a6b2 Binary files /dev/null and b/src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/plugins/builtin.py b/src/cmvr_edge_ai/plugins/builtin.py new file mode 100644 index 0000000..497c1c0 --- /dev/null +++ b/src/cmvr_edge_ai/plugins/builtin.py @@ -0,0 +1,296 @@ +"""Small built-in plugins used for smoke tests and control boundaries.""" + +from __future__ import annotations + +import asyncio +import logging +import math +from collections.abc import Mapping +from time import monotonic_ns, time_ns +from typing import Any + +from cmvr_edge_ai.contracts import ApprovedRobotCommand, RobotCommand +from cmvr_edge_ai.core.component import Emission, HealthReport, Operator, Sink, Source +from cmvr_edge_ai.core.envelope import Envelope + +from .registry import PluginKind, PluginRegistry, PluginSpec + + +class SequenceSource(Source): + """Finite synthetic source useful for deployment and runtime smoke tests.""" + + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + self._node_id = node_id + items = params.get("items", ()) + if isinstance(items, (str, bytes, bytearray, Mapping)): + raise ValueError("items must be a sequence, not a scalar or mapping") + try: + self._items = tuple(items) + except TypeError as exc: + raise ValueError("items must be an iterable sequence") from exc + self._interval_s = float(params.get("interval_s", 0.0)) + self._schema_name = str(params.get("schema_name", "opaque")) + self._schema_version = int(params.get("schema_version", 1)) + if self._interval_s < 0: + raise ValueError("interval_s must not be negative") + + async def messages(self): # type: ignore[no-untyped-def] + for sequence, payload in enumerate(self._items): + if self._interval_s: + await asyncio.sleep(self._interval_s) + yield Emission( + "output", + Envelope( + payload=payload, + schema_name=self._schema_name, + schema_version=self._schema_version, + source_id=self._node_id, + sequence=sequence, + ), + ) + + +class PassthroughOperator(Operator): + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + del node_id + del params + + async def process( + self, envelope: Envelope[Any], input_port: str = "input" + ) -> Emission: + del input_port + return Emission("output", envelope) + + +class LogSink(Sink): + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + logger_name = str(params.get("logger", "cmvr_edge_ai.sink")) + self._logger = logging.getLogger(logger_name) + self._node_id = node_id + self._level = getattr(logging, str(params.get("level", "INFO")).upper()) + + async def consume(self, envelope: Envelope[Any], input_port: str = "input") -> None: + self._logger.log( + self._level, + "pipeline output node=%s port=%s schema=%s trace_id=%s payload=%r", + self._node_id, + input_port, + envelope.schema, + envelope.trace_id, + envelope.payload, + ) + + +class CommandSafetyGate(Operator): + """Fail-closed validation for transport-neutral robot commands.""" + + def __init__(self, node_id: str, params: Mapping[str, Any]) -> None: + self._node_id = node_id + self._allowed_actions = frozenset( + str(action) for action in params.get("allowed_actions", ()) + ) + self._allowed_devices = frozenset( + str(device) for device in params.get("allowed_devices", ()) + ) + self._min_interval_ns = int(float(params.get("min_interval_ms", 0.0)) * 1e6) + self._max_ttl_ns = int(float(params.get("max_ttl_ms", 5000.0)) * 1e6) + self._argument_limits = _parse_argument_limits( + params.get("argument_limits", {}) + ) + required_limits = params.get("require_limits_for", ()) + self._require_limits_for = frozenset( + str(action) for action in required_limits + ) | {"set_velocity"} + self._enforce_sequence = _strict_bool( + params, "enforce_monotonic_sequence", default=True + ) + self._last_command_ns: dict[tuple[str, str], int] = {} + self._last_sequence: dict[tuple[str, str], int] = {} + self._rejected_count = 0 + self._logger = logging.getLogger(__name__) + if not self._allowed_actions: + raise ValueError("safety gate requires a non-empty allowed_actions list") + if self._min_interval_ns < 0: + raise ValueError("min_interval_ms must not be negative") + if self._max_ttl_ns <= 0: + raise ValueError("max_ttl_ms must be positive") + missing_limits = (self._allowed_actions & self._require_limits_for) - set( + self._argument_limits + ) + if missing_limits: + raise ValueError( + "safety gate requires argument_limits for action(s): " + + ", ".join(sorted(missing_limits)) + ) + if "set_velocity" in self._allowed_actions: + velocity_fields = set(self._argument_limits["set_velocity"]) + missing_fields = {"vx", "vy", "wz"} - velocity_fields + if missing_fields: + raise ValueError( + "argument_limits.set_velocity must define vx, vy and wz; " + f"missing: {', '.join(sorted(missing_fields))}" + ) + + async def process( + self, envelope: Envelope[Any], input_port: str = "input" + ) -> Emission | None: + del input_port + command = envelope.payload + if not isinstance(command, RobotCommand): + raise TypeError( + f"{self._node_id} expected RobotCommand, got {type(command).__name__}" + ) + now_wall_ns = time_ns() + if envelope.is_expired(now_wall_ns) or command.valid_until_ns <= now_wall_ns: + return self._reject(command, "expired") + if command.valid_until_ns - now_wall_ns > self._max_ttl_ns: + return self._reject(command, "TTL exceeds safety-gate maximum") + if command.action not in self._allowed_actions: + return self._reject(command, "action is not allowlisted") + if self._allowed_devices and command.device_id not in self._allowed_devices: + return self._reject(command, "device is not allowlisted") + + for argument, (minimum, maximum) in self._argument_limits.get( + command.action, {} + ).items(): + if argument not in command.arguments: + return self._reject( + command, f"required argument {argument!r} is missing" + ) + try: + value = float(command.arguments[argument]) + except (TypeError, ValueError): + return self._reject(command, f"argument {argument!r} is not numeric") + if not math.isfinite(value) or not minimum <= value <= maximum: + return self._reject( + command, + f"argument {argument!r}={value!r} is outside " + f"[{minimum}, {maximum}]", + ) + + key = (command.device_id, command.action) + previous_sequence = self._last_sequence.get(key) + if ( + self._enforce_sequence + and previous_sequence is not None + and command.sequence <= previous_sequence + ): + return self._reject(command, "sequence is stale or duplicated") + self._last_sequence[key] = command.sequence + now = monotonic_ns() + previous = self._last_command_ns.get(key) + if previous is not None and now - previous < self._min_interval_ns: + return self._reject(command, "rate limit exceeded") + self._last_command_ns[key] = now + approved = ApprovedRobotCommand(command=command, approved_by=self._node_id) + return Emission( + "commands", + envelope.with_payload( + approved, + schema_name="ApprovedRobotCommand", + schema_version=1, + source_id=self._node_id, + ), + ) + + async def health(self) -> HealthReport: + return HealthReport(True, f"rejected_commands={self._rejected_count}") + + def _reject(self, command: RobotCommand, reason: str) -> Emission | None: + self._rejected_count += 1 + self._logger.warning( + "robot command rejected gate=%s command_id=%s device=%s action=%s " + "reason=%s", + self._node_id, + command.command_id, + command.device_id, + command.action, + reason, + ) + return None + + +def _parse_argument_limits(value: Any) -> dict[str, dict[str, tuple[float, float]]]: + if not isinstance(value, Mapping): + raise ValueError("argument_limits must be a mapping") + parsed: dict[str, dict[str, tuple[float, float]]] = {} + for action, arguments in value.items(): + if not isinstance(arguments, Mapping): + raise ValueError(f"argument_limits.{action} must be a mapping") + parsed_arguments: dict[str, tuple[float, float]] = {} + for name, bounds in arguments.items(): + if ( + not isinstance(bounds, Mapping) + or "min" not in bounds + or "max" not in bounds + ): + raise ValueError( + f"argument_limits.{action}.{name} requires min and max" + ) + minimum = float(bounds["min"]) + maximum = float(bounds["max"]) + if not math.isfinite(minimum) or not math.isfinite(maximum): + raise ValueError("argument limits must be finite") + if minimum > maximum: + raise ValueError(f"argument_limits.{action}.{name} min exceeds max") + parsed_arguments[str(name)] = (minimum, maximum) + parsed[str(action)] = parsed_arguments + return parsed + + +def _strict_bool(params: Mapping[str, Any], name: str, *, default: bool) -> bool: + value = params.get(name, default) + if type(value) is not bool: + raise ValueError(f"{name} must be a boolean, not {value!r}") + return value + + +def register_builtin_plugins(registry: PluginRegistry) -> None: + registry.register( + PluginSpec( + plugin_id="core.sequence_source@1", + kind=PluginKind.SOURCE, + factory=SequenceSource, + outputs={"output": "*"}, + description="Finite synthetic source for smoke tests", + ) + ) + registry.register( + PluginSpec( + plugin_id="core.passthrough@1", + kind=PluginKind.OPERATOR, + factory=PassthroughOperator, + inputs={"input": "*"}, + outputs={"output": "*"}, + description="Protocol-neutral pass-through operator", + ) + ) + registry.register( + PluginSpec( + plugin_id="core.log_sink@1", + kind=PluginKind.SINK, + factory=LogSink, + inputs={"input": "*"}, + description="Structured log sink", + ) + ) + registry.register( + PluginSpec( + plugin_id="safety.robot_command_gate@1", + kind=PluginKind.OPERATOR, + factory=CommandSafetyGate, + inputs={"input": "RobotCommand/v1"}, + outputs={"commands": "ApprovedRobotCommand/v1"}, + description="Fail-closed allowlist and rate gate for robot commands", + tags=frozenset({"safety_gate"}), + ) + ) + + +__all__ = [ + "CommandSafetyGate", + "LogSink", + "PassthroughOperator", + "SequenceSource", + "register_builtin_plugins", +] diff --git a/src/cmvr_edge_ai/plugins/registry.py b/src/cmvr_edge_ai/plugins/registry.py new file mode 100644 index 0000000..5a28a63 --- /dev/null +++ b/src/cmvr_edge_ai/plugins/registry.py @@ -0,0 +1,166 @@ +"""Explicit plugin registry used by the configuration compiler. + +Plugins are registered under stable, versioned identifiers. Runtime +configuration never imports an arbitrary ``module:object`` path. +""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping +from dataclasses import dataclass, field +from enum import Enum +from importlib import metadata +from types import MappingProxyType +from typing import Any, cast + + +class PluginError(RuntimeError): + """Base class for plugin registration and lookup failures.""" + + +class DuplicatePluginError(PluginError): + """Raised when a plugin identifier is registered twice.""" + + +class UnknownPluginError(PluginError): + """Raised when configuration references an unknown plugin.""" + + +class PluginKind(str, Enum): + SOURCE = "source" + OPERATOR = "operator" + SINK = "sink" + + +PluginFactory = Callable[[str, Mapping[str, Any]], Any] + + +@dataclass(frozen=True, slots=True) +class PluginSpec: + """Static metadata and factory for a pipeline component.""" + + plugin_id: str + kind: PluginKind + factory: PluginFactory + inputs: Mapping[str, str] = field(default_factory=dict) + outputs: Mapping[str, str] = field(default_factory=dict) + description: str = "" + tags: frozenset[str] = field(default_factory=frozenset) + + def __post_init__(self) -> None: + if not isinstance(self.plugin_id, str) or "@" not in self.plugin_id: + raise ValueError( + f"plugin id must be versioned (for example name@1): {self.plugin_id}" + ) + name, version = self.plugin_id.rsplit("@", maxsplit=1) + if not name or not version.isdigit() or int(version) < 1: + raise ValueError( + f"plugin id must end in a positive numeric version: {self.plugin_id}" + ) + try: + parsed_kind = PluginKind(self.kind) + except ValueError as exc: + raise ValueError(f"unknown plugin kind: {self.kind!r}") from exc + if not callable(self.factory): + raise TypeError("plugin factory must be callable") + + inputs = _validated_ports("input", self.inputs) + outputs = _validated_ports("output", self.outputs) + tags = frozenset(self.tags) + if any(not isinstance(tag, str) or not tag for tag in tags): + raise ValueError("plugin tags must be non-empty strings") + + object.__setattr__(self, "kind", parsed_kind) + object.__setattr__(self, "inputs", MappingProxyType(inputs)) + object.__setattr__(self, "outputs", MappingProxyType(outputs)) + object.__setattr__(self, "tags", tags) + if parsed_kind is PluginKind.SOURCE and inputs: + raise ValueError("source plugins cannot declare input ports") + if parsed_kind is PluginKind.SINK and outputs: + raise ValueError("sink plugins cannot declare output ports") + + +class PluginRegistry: + """In-memory registry with optional Python entry-point discovery.""" + + ENTRY_POINT_GROUP = "cmvr_edge_ai.plugins" + + def __init__(self) -> None: + self._plugins: dict[str, PluginSpec] = {} + + def register(self, spec: PluginSpec) -> PluginSpec: + if spec.plugin_id in self._plugins: + raise DuplicatePluginError(f"plugin already registered: {spec.plugin_id}") + self._plugins[spec.plugin_id] = spec + return spec + + def resolve(self, plugin_id: str) -> PluginSpec: + try: + return self._plugins[plugin_id] + except KeyError as exc: + available = ", ".join(sorted(self._plugins)) or "" + raise UnknownPluginError( + f"unknown plugin {plugin_id!r}; available plugins: {available}" + ) from exc + + def create( + self, plugin_id: str, node_id: str, params: Mapping[str, Any] | None = None + ) -> Any: + return self.resolve(plugin_id).factory(node_id, params or {}) + + def specs(self) -> tuple[PluginSpec, ...]: + return tuple(self._plugins[key] for key in sorted(self._plugins)) + + def load_entry_points(self) -> None: + """Load plugin registration callbacks from installed distributions. + + An entry point may expose either a ``PluginSpec`` or a callable taking + this registry. Discovery remains explicit and restricted to the + dedicated entry-point group. + """ + + discovered = cast(Any, metadata.entry_points()) + selected = ( + discovered.select(group=self.ENTRY_POINT_GROUP) + if hasattr(discovered, "select") + else discovered.get(self.ENTRY_POINT_GROUP, ()) + ) + for entry_point in selected: + loaded = entry_point.load() + if isinstance(loaded, PluginSpec): + self.register(loaded) + elif callable(loaded): + loaded(self) + else: + raise PluginError( + f"entry point {entry_point.name!r} must expose PluginSpec " + "or a registry callback" + ) + + +def schemas_compatible(source_schema: str, target_schema: str) -> bool: + """Return whether two declared port schemas can be connected.""" + + return ( + source_schema == target_schema or source_schema == "*" or target_schema == "*" + ) + + +def _validated_ports(direction: str, ports: Mapping[str, str]) -> dict[str, str]: + if not isinstance(ports, Mapping): + raise TypeError(f"plugin {direction} ports must be a mapping") + copied = dict(ports) + invalid = sorted( + repr(name) + for name, schema in copied.items() + if not isinstance(name, str) + or not name + or not isinstance(schema, str) + or not schema + ) + if invalid: + raise ValueError( + f"plugin {direction} ports require non-empty string names and schemas: " + + ", ".join(invalid) + ) + return copied diff --git a/src/cmvr_edge_ai/transports/__init__.py b/src/cmvr_edge_ai/transports/__init__.py new file mode 100644 index 0000000..c5b8c0b --- /dev/null +++ b/src/cmvr_edge_ai/transports/__init__.py @@ -0,0 +1,5 @@ +"""Protocol-native transport helpers used by boundary connectors.""" + +from .base import ManagedTransport + +__all__ = ["ManagedTransport"] diff --git a/src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..3d809ff Binary files /dev/null and b/src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..6760d78 Binary files /dev/null and b/src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/__pycache__/base.cpython-310.pyc b/src/cmvr_edge_ai/transports/__pycache__/base.cpython-310.pyc new file mode 100644 index 0000000..72fc6be Binary files /dev/null and b/src/cmvr_edge_ai/transports/__pycache__/base.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/__pycache__/base.cpython-312.pyc b/src/cmvr_edge_ai/transports/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000..0d9621b Binary files /dev/null and b/src/cmvr_edge_ai/transports/__pycache__/base.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/base.py b/src/cmvr_edge_ai/transports/base.py new file mode 100644 index 0000000..5395d18 --- /dev/null +++ b/src/cmvr_edge_ai/transports/base.py @@ -0,0 +1,15 @@ +"""Shared transport lifecycle only. + +The framework intentionally does not force gRPC streams, HTTP requests and +QUIC datagrams behind one fake ``send`` method. Connectors expose the common +Source/Operator/Sink contracts while transport details stay protocol-native. +""" + +from __future__ import annotations + +from typing import Protocol + + +class ManagedTransport(Protocol): + async def close(self) -> None: + """Release connections and protocol resources.""" diff --git a/src/cmvr_edge_ai/transports/grpc/__init__.py b/src/cmvr_edge_ai/transports/grpc/__init__.py new file mode 100644 index 0000000..63053af --- /dev/null +++ b/src/cmvr_edge_ai/transports/grpc/__init__.py @@ -0,0 +1,16 @@ +"""gRPC transport utilities.""" + +from .channel import ( + GrpcChannelPool, + GrpcChannelSettings, + GrpcDependencyError, +) +from .bidi import BidiRequestStream, BidiRequestStreamClosed + +__all__ = [ + "BidiRequestStream", + "BidiRequestStreamClosed", + "GrpcChannelPool", + "GrpcChannelSettings", + "GrpcDependencyError", +] diff --git a/src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..57006d3 Binary files /dev/null and b/src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..772516e Binary files /dev/null and b/src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-310.pyc b/src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-310.pyc new file mode 100644 index 0000000..3352d2a Binary files /dev/null and b/src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-312.pyc b/src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-312.pyc new file mode 100644 index 0000000..581c3ab Binary files /dev/null and b/src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-310.pyc b/src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-310.pyc new file mode 100644 index 0000000..8b4e133 Binary files /dev/null and b/src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-312.pyc b/src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-312.pyc new file mode 100644 index 0000000..f1746e9 Binary files /dev/null and b/src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/grpc/bidi.py b/src/cmvr_edge_ai/transports/grpc/bidi.py new file mode 100644 index 0000000..c707701 --- /dev/null +++ b/src/cmvr_edge_ai/transports/grpc/bidi.py @@ -0,0 +1,68 @@ +"""Bounded request iterator for true bidirectional gRPC connectors.""" + +from __future__ import annotations + +from typing import AsyncIterator, Generic, TypeVar + +from cmvr_edge_ai.core.edge_queue import ( + EdgeQueue, + EdgeQueueClosed, + OverflowPolicy, + QueueStats, +) + + +RequestT = TypeVar("RequestT") + + +class BidiRequestStreamClosed(RuntimeError): + """Raised when a producer writes after or during stream shutdown.""" + + +class BidiRequestStream(Generic[RequestT]): + """An async request iterator with explicit backpressure and close.""" + + def __init__(self, capacity: int = 8) -> None: + if capacity < 1: + raise ValueError("capacity must be at least 1") + self._queue: EdgeQueue[RequestT] = EdgeQueue( + capacity, + OverflowPolicy.BLOCK, + name="grpc-bidi-request-stream", + ) + self._consumer_started = False + + @property + def closed(self) -> bool: + return self._queue.closed + + def stats(self) -> QueueStats: + return self._queue.stats() + + async def send(self, request: RequestT) -> None: + try: + await self._queue.put(request) + except EdgeQueueClosed as exc: + raise BidiRequestStreamClosed( + "bidirectional request stream is closed" + ) from exc + + async def close(self) -> None: + # A normal close drains already accepted requests, while atomically + # waking every blocked sender so none can report a false success. + await self._queue.close() + + async def __aiter__(self) -> AsyncIterator[RequestT]: + if self._consumer_started: + raise RuntimeError("BidiRequestStream supports exactly one consumer") + self._consumer_started = True + try: + async for item in self._queue: + yield item + finally: + # If gRPC cancels or abandons the iterator, no queued request can be + # delivered. Discard it and wake blocked producers immediately. + await self._queue.close(discard=True) + + +__all__ = ["BidiRequestStream", "BidiRequestStreamClosed"] diff --git a/src/cmvr_edge_ai/transports/grpc/channel.py b/src/cmvr_edge_ai/transports/grpc/channel.py new file mode 100644 index 0000000..6f390c8 --- /dev/null +++ b/src/cmvr_edge_ai/transports/grpc/channel.py @@ -0,0 +1,79 @@ +"""Async gRPC channel reuse with a lazy optional dependency.""" + +from __future__ import annotations + +import asyncio +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + + +class GrpcDependencyError(RuntimeError): + """Raised when a gRPC connector is used without the grpc extra.""" + + +@dataclass(frozen=True, slots=True) +class GrpcChannelSettings: + target: str + tls: bool = False + root_certificates: Path | None = None + options: tuple[tuple[str, int | str], ...] = field(default_factory=tuple) + + +class GrpcChannelPool: + """Create one ``grpc.aio.Channel`` per named endpoint. + + The pool must be created and used from a single asyncio event-loop thread, + matching the threading requirements of ``grpc.aio`` objects. + """ + + def __init__(self) -> None: + self._channels: dict[str, Any] = {} + self._settings: dict[str, GrpcChannelSettings] = {} + self._lock = asyncio.Lock() + + async def get(self, endpoint_id: str, settings: GrpcChannelSettings) -> Any: + async with self._lock: + existing = self._channels.get(endpoint_id) + if existing is not None: + if self._settings[endpoint_id] != settings: + raise ValueError( + f"gRPC endpoint {endpoint_id!r} requested with conflicting settings" + ) + return existing + + try: + import grpc + except ImportError as exc: + raise GrpcDependencyError( + "gRPC support is not installed; install cmvr-edge-ai[grpc]" + ) from exc + + if settings.tls: + roots = ( + settings.root_certificates.read_bytes() + if settings.root_certificates is not None + else None + ) + credentials = grpc.ssl_channel_credentials(root_certificates=roots) + channel = grpc.aio.secure_channel( + settings.target, credentials, options=settings.options + ) + else: + channel = grpc.aio.insecure_channel( + settings.target, options=settings.options + ) + + self._channels[endpoint_id] = channel + self._settings[endpoint_id] = settings + return channel + + async def close(self, grace_s: float = 1.0) -> None: + async with self._lock: + channels = tuple(self._channels.values()) + self._channels.clear() + self._settings.clear() + await asyncio.gather( + *(channel.close(grace=grace_s) for channel in channels), + return_exceptions=True, + ) diff --git a/src/cmvr_edge_ai/transports/http/__init__.py b/src/cmvr_edge_ai/transports/http/__init__.py new file mode 100644 index 0000000..15b569a --- /dev/null +++ b/src/cmvr_edge_ai/transports/http/__init__.py @@ -0,0 +1,5 @@ +"""HTTP transport utilities.""" + +from .client import HttpClientPool, HttpClientSettings, HttpDependencyError + +__all__ = ["HttpClientPool", "HttpClientSettings", "HttpDependencyError"] diff --git a/src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..b50e5d9 Binary files /dev/null and b/src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..85c9c5c Binary files /dev/null and b/src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-310.pyc b/src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-310.pyc new file mode 100644 index 0000000..2cac9f8 Binary files /dev/null and b/src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-312.pyc b/src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000..20a568b Binary files /dev/null and b/src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/http/client.py b/src/cmvr_edge_ai/transports/http/client.py new file mode 100644 index 0000000..a151ef4 --- /dev/null +++ b/src/cmvr_edge_ai/transports/http/client.py @@ -0,0 +1,62 @@ +"""Shared asynchronous HTTP clients, imported only when configured.""" + +from __future__ import annotations + +import asyncio +from dataclasses import dataclass, field +from typing import Any + + +class HttpDependencyError(RuntimeError): + """Raised when an HTTP connector is used without the HTTP extra.""" + + +@dataclass(frozen=True, slots=True) +class HttpClientSettings: + base_url: str + timeout_s: float = 5.0 + headers: tuple[tuple[str, str], ...] = field(default_factory=tuple) + verify_tls: bool = True + + +class HttpClientPool: + def __init__(self) -> None: + self._clients: dict[str, Any] = {} + self._settings: dict[str, HttpClientSettings] = {} + self._lock = asyncio.Lock() + + async def get(self, endpoint_id: str, settings: HttpClientSettings) -> Any: + async with self._lock: + existing = self._clients.get(endpoint_id) + if existing is not None: + if self._settings[endpoint_id] != settings: + raise ValueError( + f"HTTP endpoint {endpoint_id!r} requested with conflicting settings" + ) + return existing + + try: + import httpx + except ImportError as exc: + raise HttpDependencyError( + "HTTP support is not installed; install cmvr-edge-ai[http]" + ) from exc + + client = httpx.AsyncClient( + base_url=settings.base_url, + timeout=settings.timeout_s, + headers=dict(settings.headers), + verify=settings.verify_tls, + ) + self._clients[endpoint_id] = client + self._settings[endpoint_id] = settings + return client + + async def close(self) -> None: + async with self._lock: + clients = tuple(self._clients.values()) + self._clients.clear() + self._settings.clear() + await asyncio.gather( + *(client.aclose() for client in clients), return_exceptions=True + ) diff --git a/src/cmvr_edge_ai/transports/quic/__init__.py b/src/cmvr_edge_ai/transports/quic/__init__.py new file mode 100644 index 0000000..462bad7 --- /dev/null +++ b/src/cmvr_edge_ai/transports/quic/__init__.py @@ -0,0 +1 @@ +"""Reserved package for future QUIC stream and datagram connectors.""" diff --git a/src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..d0f850e Binary files /dev/null and b/src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..6510f4f Binary files /dev/null and b/src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/transports/udp/__init__.py b/src/cmvr_edge_ai/transports/udp/__init__.py new file mode 100644 index 0000000..7b5677b --- /dev/null +++ b/src/cmvr_edge_ai/transports/udp/__init__.py @@ -0,0 +1,5 @@ +"""Reserved package for typed UDP connectors. + +UDP datagrams will be exposed through Source/Sink plugins rather than a +protocol-erasing transport API. +""" diff --git a/src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..fcecc59 Binary files /dev/null and b/src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..e572369 Binary files /dev/null and b/src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/workers/__init__.py b/src/cmvr_edge_ai/workers/__init__.py new file mode 100644 index 0000000..3508bdf --- /dev/null +++ b/src/cmvr_edge_ai/workers/__init__.py @@ -0,0 +1,11 @@ +"""Explicit, resource-bounded execution helpers for plugin authors.""" + +from .process import PersistentProcessWorker, ProcessWorkerError, RemoteWorkerError +from .thread import run_blocking + +__all__ = [ + "PersistentProcessWorker", + "ProcessWorkerError", + "RemoteWorkerError", + "run_blocking", +] diff --git a/src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-310.pyc b/src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..7aac0c3 Binary files /dev/null and b/src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-312.pyc b/src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..1a79865 Binary files /dev/null and b/src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/workers/__pycache__/process.cpython-310.pyc b/src/cmvr_edge_ai/workers/__pycache__/process.cpython-310.pyc new file mode 100644 index 0000000..bd2f084 Binary files /dev/null and b/src/cmvr_edge_ai/workers/__pycache__/process.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/workers/__pycache__/process.cpython-312.pyc b/src/cmvr_edge_ai/workers/__pycache__/process.cpython-312.pyc new file mode 100644 index 0000000..d116b9c Binary files /dev/null and b/src/cmvr_edge_ai/workers/__pycache__/process.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/workers/__pycache__/thread.cpython-310.pyc b/src/cmvr_edge_ai/workers/__pycache__/thread.cpython-310.pyc new file mode 100644 index 0000000..21c5483 Binary files /dev/null and b/src/cmvr_edge_ai/workers/__pycache__/thread.cpython-310.pyc differ diff --git a/src/cmvr_edge_ai/workers/__pycache__/thread.cpython-312.pyc b/src/cmvr_edge_ai/workers/__pycache__/thread.cpython-312.pyc new file mode 100644 index 0000000..4f4e1c0 Binary files /dev/null and b/src/cmvr_edge_ai/workers/__pycache__/thread.cpython-312.pyc differ diff --git a/src/cmvr_edge_ai/workers/process.py b/src/cmvr_edge_ai/workers/process.py new file mode 100644 index 0000000..c30b1ed --- /dev/null +++ b/src/cmvr_edge_ai/workers/process.py @@ -0,0 +1,211 @@ +"""Small, bounded, persistent subprocess worker for CPU/model plugins. + +The worker is intentionally not wired automatically from YAML. A model plugin +owns one worker for its whole lifecycle, loads its model once in the child, and +submits small payloads or shared-memory references. Large decoded frames +should not be copied through these queues. +""" + +from __future__ import annotations + +import asyncio +import multiprocessing as mp +import queue +import traceback +from collections.abc import Callable +from typing import Any +from uuid import uuid4 +from time import monotonic + + +WorkerHandler = Callable[[Any], Any] +WorkerFactory = Callable[[], WorkerHandler] +_STOP = "__cmvr_edge_ai_worker_stop__" + + +class ProcessWorkerError(RuntimeError): + pass + + +class RemoteWorkerError(ProcessWorkerError): + def __init__(self, remote_type: str, message: str, remote_traceback: str) -> None: + self.remote_type = remote_type + self.remote_traceback = remote_traceback + super().__init__(f"remote {remote_type}: {message}\n{remote_traceback}") + + +class PersistentProcessWorker: + """One long-lived subprocess with bounded request and result queues.""" + + def __init__( + self, + factory: WorkerFactory, + *, + name: str = "model-worker", + start_method: str = "spawn", + queue_capacity: int = 1, + ) -> None: + if queue_capacity < 1: + raise ValueError("queue_capacity must be at least 1") + self._factory = factory + self._name = name + self._context: Any = mp.get_context(start_method) + self._requests = self._context.Queue(maxsize=queue_capacity) + self._results = self._context.Queue(maxsize=queue_capacity) + self._process: mp.Process | None = None + self._submit_lock = asyncio.Lock() + self._closed = False + self._poisoned_reason: str | None = None + + @property + def is_alive(self) -> bool: + return self._process is not None and self._process.is_alive() + + @property + def poisoned(self) -> bool: + """Whether an in-flight timeout/cancellation made correlation unsafe.""" + + return self._poisoned_reason is not None + + async def start(self) -> None: + if self._closed: + raise ProcessWorkerError("worker is one-shot and has already been stopped") + if self._process is not None: + raise ProcessWorkerError("worker has already been started") + process = self._context.Process( + target=_worker_main, + args=(self._factory, self._requests, self._results), + name=self._name, + ) + process.start() + self._process = process + + async def submit(self, payload: Any, *, timeout_s: float | None = None) -> Any: + if not self.is_alive: + raise ProcessWorkerError("worker is not running") + if self._poisoned_reason is not None: + raise ProcessWorkerError( + "worker is poisoned by an indeterminate prior request; stop and " + f"recreate it ({self._poisoned_reason})" + ) + if timeout_s is not None and timeout_s <= 0: + raise ValueError("timeout_s must be positive") + request_id = uuid4().hex + async with self._submit_lock: + if self._poisoned_reason is not None: + raise ProcessWorkerError( + "worker is poisoned by an indeterminate prior request; stop " + f"and recreate it ({self._poisoned_reason})" + ) + deadline = monotonic() + timeout_s if timeout_s is not None else None + request_may_have_been_sent = False + try: + # Cancellation can race the backing queue thread, so once the + # put begins we conservatively treat its delivery as uncertain. + request_may_have_been_sent = True + await self._put_request((request_id, payload), deadline) + response = await self._get_response(deadline) + except BaseException as exc: + if request_may_have_been_sent: + self._poisoned_reason = type(exc).__name__ + raise + + response_id, success, value = response + if response_id != request_id: + self._poisoned_reason = "response correlation mismatch" + raise ProcessWorkerError( + f"worker response correlation mismatch: {response_id} != {request_id}" + ) + if success: + return value + remote_type, message, remote_traceback = value + raise RemoteWorkerError(remote_type, message, remote_traceback) + + async def _put_request(self, request: Any, deadline: float | None) -> None: + while True: + if not self.is_alive: + raise ProcessWorkerError("worker exited before accepting request") + timeout = _poll_timeout(deadline) + try: + await asyncio.to_thread(self._requests.put, request, True, timeout) + return + except queue.Full: + if deadline is not None and monotonic() >= deadline: + raise TimeoutError("worker request timed out") + + async def _get_response(self, deadline: float | None) -> Any: + while True: + if not self.is_alive: + raise ProcessWorkerError("worker exited before returning a response") + timeout = _poll_timeout(deadline) + try: + return await asyncio.to_thread(self._results.get, True, timeout) + except queue.Empty: + if deadline is not None and monotonic() >= deadline: + raise TimeoutError("worker response timed out") + + async def stop(self, *, timeout_s: float = 5.0) -> None: + if timeout_s <= 0: + raise ValueError("timeout_s must be positive") + process = self._process + if process is None: + return + if process.is_alive(): + try: + await asyncio.to_thread( + self._requests.put, _STOP, True, min(timeout_s, 1.0) + ) + except queue.Full: + pass + await asyncio.to_thread(process.join, timeout_s) + if process.is_alive(): + process.terminate() + await asyncio.to_thread(process.join, 1.0) + self._process = None + self._requests.close() + self._results.close() + self._closed = True + + +def _worker_main(factory: WorkerFactory, requests: Any, results: Any) -> None: + try: + handler = factory() + except BaseException as exc: + # There is no request id yet, so the parent observes process death. + traceback.print_exception(exc) + return + + while True: + item = requests.get() + if item == _STOP: + return + request_id, payload = item + try: + result = handler(payload) + results.put((request_id, True, result)) + except BaseException as exc: + results.put( + ( + request_id, + False, + (type(exc).__name__, str(exc), traceback.format_exc()), + ) + ) + + +def _poll_timeout(deadline: float | None) -> float: + if deadline is None: + return 0.2 + remaining = deadline - monotonic() + if remaining <= 0: + return 0.0 + return min(remaining, 0.2) + + +__all__ = [ + "PersistentProcessWorker", + "ProcessWorkerError", + "RemoteWorkerError", + "WorkerFactory", + "WorkerHandler", +] diff --git a/src/cmvr_edge_ai/workers/thread.py b/src/cmvr_edge_ai/workers/thread.py new file mode 100644 index 0000000..6ae775c --- /dev/null +++ b/src/cmvr_edge_ai/workers/thread.py @@ -0,0 +1,32 @@ +"""Bounded thread offload helper using the application's shared executor.""" + +from __future__ import annotations + +import asyncio +from collections.abc import Callable +from concurrent.futures import Executor +from functools import partial +from typing import Any, TypeVar + + +ResultT = TypeVar("ResultT") + + +async def run_blocking( + function: Callable[..., ResultT], + *args: Any, + executor: Executor | None = None, + **kwargs: Any, +) -> ResultT: + """Run blocking native/SDK code without starving the event loop. + + Plugins should pass the bounded ``thread_executor`` from ComponentContext + metadata. Leaving it unset deliberately uses the host loop's executor + without replacing or taking ownership of that executor. + """ + + call = partial(function, *args, **kwargs) + return await asyncio.get_running_loop().run_in_executor(executor, call) + + +__all__ = ["run_blocking"] diff --git a/talk_server/README.md b/talk_server/README.md new file mode 100644 index 0000000..2cb2f6b --- /dev/null +++ b/talk_server/README.md @@ -0,0 +1,15 @@ +# Talk pipeline + +`pipeline.yaml` is runnable with a simulated audio chunk so the framework can +be tested before the cmvr-es microphone stream proto is available. + +When the bidirectional audio RPC lands, replace the synthetic source with the +typed `cmvr.grpc.microphone_audio_stream@1` connector and expand the chain to: + +```text +AudioChunk -> VAD -> ASR -> dialogue -> TTS -> speaker stream +``` + +The internal `AudioChunk/v1` contract and port/QoS model are already independent +of the final protobuf message names. + diff --git a/talk_server/pipeline.yaml b/talk_server/pipeline.yaml new file mode 100644 index 0000000..0f65f2f --- /dev/null +++ b/talk_server/pipeline.yaml @@ -0,0 +1,42 @@ +api_version: cmvr.edge.ai/v1 + +runtime: + thread_workers: 3 + shutdown_timeout_s: 8 + +pipelines: + talk_smoke: + enabled: true + nodes: + audio_stream_placeholder: + # Replace with cmvr.grpc.microphone_audio_stream@1 when its proto lands. + uses: core.sequence_source@1 + with: + items: + - simulated-audio-chunk + schema_name: AudioChunk + schema_version: 1 + + dialogue_placeholder: + # The real chain will be VAD -> ASR -> dialogue -> TTS. + uses: core.passthrough@1 + + output: + uses: core.log_sink@1 + with: + logger: cmvr_edge_ai.talk + + edges: + - from: audio_stream_placeholder.output + to: dialogue_placeholder.input + qos: + profile: audio_contiguous + capacity: 16 + overflow: block + + - from: dialogue_placeholder.output + to: output.input + qos: + profile: request + capacity: 8 + overflow: block diff --git a/tests/__pycache__/conftest.cpython-310.pyc b/tests/__pycache__/conftest.cpython-310.pyc new file mode 100644 index 0000000..68c5b34 Binary files /dev/null and b/tests/__pycache__/conftest.cpython-310.pyc differ diff --git a/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc b/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..e5f30ae Binary files /dev/null and b/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/__pycache__/conftest.cpython-312.pyc b/tests/__pycache__/conftest.cpython-312.pyc new file mode 100644 index 0000000..65b4cc7 Binary files /dev/null and b/tests/__pycache__/conftest.cpython-312.pyc differ diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..4ded8f2 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,13 @@ +"""Test bootstrap for the repository's ``src`` layout.""" + +from __future__ import annotations + +import sys +from pathlib import Path + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +SOURCE_ROOT = PROJECT_ROOT / "src" + +if str(SOURCE_ROOT) not in sys.path: + sys.path.insert(0, str(SOURCE_ROOT)) diff --git a/tests/integration/__pycache__/test_application_cli.cpython-310.pyc b/tests/integration/__pycache__/test_application_cli.cpython-310.pyc new file mode 100644 index 0000000..0d184ea Binary files /dev/null and b/tests/integration/__pycache__/test_application_cli.cpython-310.pyc differ diff --git a/tests/integration/__pycache__/test_application_cli.cpython-312-pytest-7.4.4.pyc b/tests/integration/__pycache__/test_application_cli.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..6d6689e Binary files /dev/null and b/tests/integration/__pycache__/test_application_cli.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/integration/__pycache__/test_application_cli.cpython-312.pyc b/tests/integration/__pycache__/test_application_cli.cpython-312.pyc new file mode 100644 index 0000000..625501c Binary files /dev/null and b/tests/integration/__pycache__/test_application_cli.cpython-312.pyc differ diff --git a/tests/integration/__pycache__/test_detection_pipeline.cpython-310.pyc b/tests/integration/__pycache__/test_detection_pipeline.cpython-310.pyc new file mode 100644 index 0000000..a759bda Binary files /dev/null and b/tests/integration/__pycache__/test_detection_pipeline.cpython-310.pyc differ diff --git a/tests/integration/__pycache__/test_detection_pipeline.cpython-312-pytest-7.4.4.pyc b/tests/integration/__pycache__/test_detection_pipeline.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..71abac3 Binary files /dev/null and b/tests/integration/__pycache__/test_detection_pipeline.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/integration/__pycache__/test_detection_pipeline.cpython-312.pyc b/tests/integration/__pycache__/test_detection_pipeline.cpython-312.pyc new file mode 100644 index 0000000..5c7bc30 Binary files /dev/null and b/tests/integration/__pycache__/test_detection_pipeline.cpython-312.pyc differ diff --git a/tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312-pytest-7.4.4.pyc b/tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..0002559 Binary files /dev/null and b/tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312.pyc b/tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312.pyc new file mode 100644 index 0000000..f87e365 Binary files /dev/null and b/tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312.pyc differ diff --git a/tests/integration/__pycache__/test_process_worker.cpython-310.pyc b/tests/integration/__pycache__/test_process_worker.cpython-310.pyc new file mode 100644 index 0000000..003ee57 Binary files /dev/null and b/tests/integration/__pycache__/test_process_worker.cpython-310.pyc differ diff --git a/tests/integration/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc b/tests/integration/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..ddb0121 Binary files /dev/null and b/tests/integration/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/integration/__pycache__/test_process_worker.cpython-312.pyc b/tests/integration/__pycache__/test_process_worker.cpython-312.pyc new file mode 100644 index 0000000..61dd317 Binary files /dev/null and b/tests/integration/__pycache__/test_process_worker.cpython-312.pyc differ diff --git a/tests/integration/__pycache__/test_runtime.cpython-310.pyc b/tests/integration/__pycache__/test_runtime.cpython-310.pyc new file mode 100644 index 0000000..95a32e9 Binary files /dev/null and b/tests/integration/__pycache__/test_runtime.cpython-310.pyc differ diff --git a/tests/integration/__pycache__/test_runtime.cpython-312-pytest-7.4.4.pyc b/tests/integration/__pycache__/test_runtime.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..e19efaa Binary files /dev/null and b/tests/integration/__pycache__/test_runtime.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/integration/__pycache__/test_runtime.cpython-312.pyc b/tests/integration/__pycache__/test_runtime.cpython-312.pyc new file mode 100644 index 0000000..fb877a3 Binary files /dev/null and b/tests/integration/__pycache__/test_runtime.cpython-312.pyc differ diff --git a/tests/integration/test_application_cli.py b/tests/integration/test_application_cli.py new file mode 100644 index 0000000..0194d6b --- /dev/null +++ b/tests/integration/test_application_cli.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +import asyncio +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path + +from cmvr_edge_ai.application import ( + ApplicationState, + EdgeAIApplication, + create_default_registry, + validate_application, +) +from cmvr_edge_ai.cli import main +from cmvr_edge_ai.config import load_config + + +PROJECT_ROOT = Path(__file__).resolve().parents[2] +SMOKE_CONFIG = PROJECT_ROOT / "configs" / "smoke.yaml" +DETECTION_CONFIG = PROJECT_ROOT / "detect_server" / "pipeline.yaml" + + +def test_application_runs_the_finite_smoke_pipeline() -> None: + async def exercise(): # type: ignore[no-untyped-def] + loop = asyncio.get_running_loop() + host_executor = ThreadPoolExecutor(max_workers=1) + loop.set_default_executor(host_executor) + application = EdgeAIApplication( + load_config(SMOKE_CONFIG), + create_default_registry(discover_entry_points=False), + ) + try: + await application.start() + running_health = await application.health() + await application.wait() + await application.stop() + # The application owns only its bounded executor. Stopping it must + # not poison an embedding event loop's pre-existing executor. + host_executor_result = await asyncio.to_thread(lambda: "still-usable") + return application, running_health, host_executor_result + finally: + # Python 3.12 Runner shuts its default executor down from another + # helper thread. Explicit cleanup keeps this regression deterministic + # in restricted containers where that helper cannot wake the loop. + host_executor.shutdown(wait=True, cancel_futures=True) + loop._default_executor = None # type: ignore[attr-defined] + + application, running_health, host_executor_result = asyncio.run(exercise()) + + assert running_health["state"] == "running" + assert set(running_health["pipelines"]) == {"smoke"} + assert application.pipelines == ("smoke",) + assert application.state is ApplicationState.STOPPED + assert host_executor_result == "still-usable" + + +def test_cli_validates_and_runs_smoke_config( + capsys, monkeypatch +) -> None: # type: ignore[no-untyped-def] + # ``configure_logging(force=True)`` intentionally owns process logging in + # production. Avoid leaking that global CLI side effect into later tests. + monkeypatch.setattr("cmvr_edge_ai.cli.configure_logging", lambda *_: None) + assert main(["validate", "--config", str(SMOKE_CONFIG)]) == 0 + validation_output = capsys.readouterr() + assert "configuration is valid; pipelines: smoke" in validation_output.out + assert validation_output.err == "" + + assert main(["run", "--config", str(SMOKE_CONFIG), "--log-level", "WARNING"]) == 0 + run_output = capsys.readouterr() + assert run_output.err == "" + + +def test_cli_lists_versioned_builtin_plugins(capsys) -> None: # type: ignore[no-untyped-def] + assert main(["plugins"]) == 0 + + output = capsys.readouterr().out + assert "core.sequence_source@1\tsource" in output + assert "detection.model@1\toperator" in output + assert "media.video_decoder.pyav@1\toperator" in output + assert "safety.robot_command_gate@1\toperator" in output + + +def test_cli_lists_detection_model_metadata(capsys) -> None: # type: ignore[no-untyped-def] + assert main(["models"]) == 0 + + output = capsys.readouterr().out + assert ( + "construction-ppe-yolov8@1\tConstruction PPE YOLOv8s\t" "ultralytics-yolo" + ) in output + assert "No-Helmet" in output + assert "No-Vest" in output + assert ( + "ppe-6classes-yolov8n@1\tPPE Detection YOLOv8n (6 Classes)\t" + "ultralytics-yolo" + ) in output + assert "Gloves,Vest,goggles,helmet,mask,safety_shoe" in output + + +def test_real_detection_config_compiles_without_loading_optional_runtimes() -> None: + config = load_config(DETECTION_CONFIG) + compiled = validate_application( + config, + create_default_registry(discover_entry_points=False), + ) + + assert len(compiled) == 1 + assert ( + config.pipelines["detection"].nodes["platform"].params["failure_mode"] + == "log_and_drop" + ) + assert set(compiled[0].plugin_specs) == { + "camera", + "decoder", + "detector", + "repeat_gate", + "platform", + } diff --git a/tests/integration/test_detection_pipeline.py b/tests/integration/test_detection_pipeline.py new file mode 100644 index 0000000..4e24398 --- /dev/null +++ b/tests/integration/test_detection_pipeline.py @@ -0,0 +1,223 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Mapping, Sequence +from concurrent.futures import ThreadPoolExecutor +from io import BytesIO +from typing import Any + +import pytest + +from cmvr_edge_ai.application import create_default_registry +from cmvr_edge_ai.compiler import compile_pipeline +from cmvr_edge_ai.config import load_config_data +from cmvr_edge_ai.contracts import ( + BoundingBox, + Detection, + DetectionAlert, + ImageFrame, +) +from cmvr_edge_ai.core import Envelope, Sink +from cmvr_edge_ai.detection import ( + DetectionModel, + DetectionModelRegistry, + DetectionModelSpec, +) +from cmvr_edge_ai.plugins import PluginKind, PluginSpec + + +class _AlwaysViolationModel(DetectionModel): + def __init__(self) -> None: + self.load_calls = 0 + self.predict_calls = 0 + self.close_calls = 0 + + def load(self) -> None: + self.load_calls += 1 + + def predict( + self, + frame: ImageFrame, + labels: tuple[str, ...], + confidence: float, + ) -> Sequence[Detection]: + del frame, confidence + self.predict_calls += 1 + if "No-Helmet" not in labels: + return () + return ( + Detection( + "No-Helmet", + 0.95, + BoundingBox(1.0, 2.0, 20.0, 40.0), + ), + ) + + def close(self) -> None: + self.close_calls += 1 + + +class _CollectAlertSink(Sink): + def __init__(self) -> None: + self.alerts: list[Envelope[Any]] = [] + + async def consume( + self, envelope: Envelope[Any], input_port: str = "alerts" + ) -> None: + del input_port + self.alerts.append(envelope) + + +async def _keep_event_loop_responsive() -> None: + while True: + await asyncio.sleep(0.01) + + +def test_decoded_frames_flow_through_registered_model_and_repeat_gate() -> None: + pillow_image = pytest.importorskip("PIL.Image") + model = _AlwaysViolationModel() + models = DetectionModelRegistry() + models.register( + DetectionModelSpec( + model_id="fake-ppe@1", + name="Fake PPE", + supported_labels=("No-Helmet",), + factory=lambda options: model, + backend="fake", + ) + ) + registry = create_default_registry( + discover_entry_points=False, + model_registry=models, + ) + sink = _CollectAlertSink() + + def sink_factory(node_id: str, params: Mapping[str, Any]) -> Sink: + del node_id, params + return sink + + registry.register( + PluginSpec( + plugin_id="test.alert_sink@1", + kind=PluginKind.SINK, + factory=sink_factory, + inputs={"alerts": "DetectionAlert/v1"}, + ) + ) + width, height = 64, 48 + frames = ( + ImageFrame(bytes((0, 0, 255)) * (width * height), width, height, "BGR8"), + ImageFrame(bytes((0, 255, 0)) * (width * height), width, height, "BGR8"), + ImageFrame(bytes((255, 0, 0)) * (width * height), width, height, "BGR8"), + ) + config = load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "pipelines": { + "ppe": { + "nodes": { + "frames": { + "uses": "core.sequence_source@1", + "with": { + "items": list(frames), + "schema_name": "ImageFrame", + }, + }, + "detector": { + "uses": "detection.model@1", + "with": { + "model": "fake-ppe@1", + "detect_labels": ["No-Helmet"], + "attach_frame": True, + }, + }, + "gate": { + "uses": "detection.repeat_gate@1", + "with": { + "time_source": "received", + "alert_image": { + "enabled": True, + "jpeg_quality": 85, + }, + "rules": [ + { + "id": "no-helmet", + "labels": ["No-Helmet"], + "min_hits": 2, + "window_ms": 10_000, + "cooldown_ms": 30_000, + } + ], + }, + }, + "sink": {"uses": "test.alert_sink@1"}, + }, + "edges": [ + {"from": "frames.output", "to": "detector.frames"}, + { + "from": "detector.detections", + "to": "gate.detections", + }, + {"from": "gate.alerts", "to": "sink.alerts"}, + ], + } + }, + } + ) + + gate_health_details: list[str] = [] + + async def scenario() -> None: + loop = asyncio.get_running_loop() + executor = ThreadPoolExecutor(max_workers=2) + loop.set_default_executor(executor) + ticker = asyncio.create_task(_keep_event_loop_responsive()) + try: + compiled = compile_pipeline( + config, + "ppe", + registry, + metadata={"thread_executor": executor}, + ) + await compiled.runtime.run() + gate_health = await compiled.runtime.nodes["gate"].health() + gate_health_details.append(gate_health.detail) + finally: + ticker.cancel() + await asyncio.gather(ticker, return_exceptions=True) + executor.shutdown(wait=True, cancel_futures=True) + loop._default_executor = None # type: ignore[attr-defined] + + asyncio.run(scenario()) + + assert model.load_calls == 1 + assert model.predict_calls == 3 + assert model.close_calls == 1 + assert len(sink.alerts) == 1 + alert = sink.alerts[0].payload + assert isinstance(alert, DetectionAlert) + assert alert.rule_id == "no-helmet" + assert alert.hit_count == 2 + assert alert.labels == ("No-Helmet",) + assert sink.alerts[0].sequence == 1 + assert alert.image is not None + assert alert.image.media_type == "image/jpeg" + assert (alert.image.width, alert.image.height) == (width, height) + assert alert.image.data.startswith(b"\xff\xd8") + + with pillow_image.open(BytesIO(alert.image.data)) as decoded: + decoded.load() + assert decoded.format == "JPEG" + assert decoded.mode == "RGB" + assert decoded.size == (width, height) + # This pixel lies outside the annotation and proves that the evidence + # image came from sequence 1 (green), the frame that reached min_hits. + red, green, blue = decoded.getpixel((width - 1, height - 1)) + assert green > 200 + assert red < 40 + assert blue < 40 + + assert gate_health_details + assert "processed_frames=3" in gate_health_details[0] + assert "alerts_emitted=1" in gate_health_details[0] + assert "alert_images_encoded=1" in gate_health_details[0] diff --git a/tests/integration/test_dual_detection_pipeline.py b/tests/integration/test_dual_detection_pipeline.py new file mode 100644 index 0000000..39d7511 --- /dev/null +++ b/tests/integration/test_dual_detection_pipeline.py @@ -0,0 +1,349 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Mapping, Sequence +from concurrent.futures import ThreadPoolExecutor +from typing import Any + +from cmvr_edge_ai.application import ( + create_default_model_registry, + create_default_registry, +) +from cmvr_edge_ai.compiler import compile_pipeline +from cmvr_edge_ai.config import load_config_data +from cmvr_edge_ai.contracts import ( + BoundingBox, + Detection, + DetectionAlert, + DetectionResult, + ImageFrame, +) +from cmvr_edge_ai.core import Envelope, Sink +from cmvr_edge_ai.detection import ( + DetectionModel, + DetectionModelRegistry, + DetectionModelSpec, +) +from cmvr_edge_ai.plugins import PluginKind, PluginSpec + + +SIX_CLASS_MODEL_ID = "ppe-6classes-yolov8n@1" +SIX_CLASS_LABELS = ( + "Gloves", + "Vest", + "goggles", + "helmet", + "mask", + "safety_shoe", +) + + +class _FixedDetectionModel(DetectionModel): + def __init__(self, label: str) -> None: + self.label = label + self.load_calls = 0 + self.close_calls = 0 + self.frames: list[ImageFrame] = [] + self.selected_labels: list[tuple[str, ...]] = [] + + def load(self) -> None: + self.load_calls += 1 + + def predict( + self, + frame: ImageFrame, + labels: tuple[str, ...], + confidence: float, + ) -> Sequence[Detection]: + del confidence + self.frames.append(frame) + self.selected_labels.append(labels) + return ( + Detection( + label=self.label, + confidence=0.95, + box=BoundingBox(1.0, 2.0, 20.0, 30.0), + ), + ) + + def close(self) -> None: + self.close_calls += 1 + + +class _CollectSink(Sink): + def __init__(self) -> None: + self.envelopes: list[Envelope[Any]] = [] + self.input_ports: list[str] = [] + + async def consume( + self, + envelope: Envelope[Any], + input_port: str = "input", + ) -> None: + self.envelopes.append(envelope) + self.input_ports.append(input_port) + + +async def _keep_event_loop_responsive() -> None: + while True: + await asyncio.sleep(0.01) + + +def test_default_registry_contains_six_class_ppe_model_contract() -> None: + registry = create_default_model_registry(discover_entry_points=False) + + spec = registry.resolve(SIX_CLASS_MODEL_ID) + + assert spec.model_id == SIX_CLASS_MODEL_ID + assert spec.supported_labels == SIX_CLASS_LABELS + + +def test_decoder_fans_out_to_alert_and_raw_detection_branches() -> None: + ppe_model = _FixedDetectionModel("No-Helmet") + six_class_model = _FixedDetectionModel("helmet") + models = DetectionModelRegistry() + models.register( + DetectionModelSpec( + model_id="construction-ppe-yolov8@1", + name="Test Construction PPE", + supported_labels=("No-Helmet",), + factory=lambda options: ppe_model, + backend="fake", + ) + ) + models.register( + DetectionModelSpec( + model_id=SIX_CLASS_MODEL_ID, + name="Test Six-Class PPE", + supported_labels=SIX_CLASS_LABELS, + factory=lambda options: six_class_model, + backend="fake", + ) + ) + registry = create_default_registry( + discover_entry_points=False, + model_registry=models, + ) + alert_sink = _CollectSink() + raw_sink = _CollectSink() + registry.register( + PluginSpec( + plugin_id="test.dual_alert_sink@1", + kind=PluginKind.SINK, + factory=lambda node_id, params: alert_sink, + inputs={"alerts": "DetectionAlert/v1"}, + ) + ) + registry.register( + PluginSpec( + plugin_id="test.dual_raw_detection_sink@1", + kind=PluginKind.SINK, + factory=lambda node_id, params: raw_sink, + inputs={"detections": "DetectionResult/v1"}, + ) + ) + width, height = 4, 3 + frames = ( + ImageFrame(bytes((0, 0, 255)) * (width * height), width, height, "BGR8"), + ImageFrame(bytes((0, 255, 0)) * (width * height), width, height, "BGR8"), + ) + config = load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "pipelines": { + "dual_detection": { + "nodes": { + "source": { + "uses": "core.sequence_source@1", + "with": { + "items": list(frames), + "schema_name": "ImageFrame", + }, + }, + "decoder": {"uses": "media.video_decoder.pyav@1"}, + "ppe_detector": { + "uses": "detection.model@1", + "with": { + "model": "construction-ppe-yolov8@1", + "detect_labels": ["No-Helmet"], + }, + }, + "ppe_gate": { + "uses": "detection.repeat_gate@1", + "with": { + "time_source": "received", + "rules": [ + { + "id": "no-helmet", + "labels": ["No-Helmet"], + "min_hits": 2, + "window_ms": 60_000, + "cooldown_ms": 60_000, + "scope": "source", + } + ], + }, + }, + "alert_sink": {"uses": "test.dual_alert_sink@1"}, + "six_class_detector": { + "uses": "detection.model@1", + "with": { + "model": SIX_CLASS_MODEL_ID, + "detect_labels": list(SIX_CLASS_LABELS), + "attach_frame": False, + }, + }, + "raw_sink": { + "uses": "test.dual_raw_detection_sink@1" + }, + }, + "edges": [ + { + "from": "source.output", + "to": "decoder.frames", + "qos": { + "profile": "video_contiguous", + "capacity": 4, + "overflow": "block", + }, + }, + { + "from": "decoder.frames", + "to": "ppe_detector.frames", + "qos": { + "profile": "telemetry", + "capacity": 4, + "overflow": "block", + }, + }, + { + "from": "decoder.frames", + "to": "six_class_detector.frames", + "qos": { + "profile": "telemetry", + "capacity": 4, + "overflow": "block", + }, + }, + { + "from": "ppe_detector.detections", + "to": "ppe_gate.detections", + "qos": { + "profile": "telemetry", + "capacity": 4, + "overflow": "block", + }, + }, + { + "from": "ppe_gate.alerts", + "to": "alert_sink.alerts", + "qos": { + "profile": "telemetry", + "capacity": 4, + "overflow": "block", + }, + }, + { + "from": "six_class_detector.detections", + "to": "raw_sink.detections", + "qos": { + "profile": "telemetry", + "capacity": 4, + "overflow": "block", + }, + }, + ], + } + }, + } + ) + + async def scenario(): # type: ignore[no-untyped-def] + loop = asyncio.get_running_loop() + executor = ThreadPoolExecutor(max_workers=2) + loop.set_default_executor(executor) + ticker = asyncio.create_task(_keep_event_loop_responsive()) + try: + compiled = compile_pipeline( + config, + "dual_detection", + registry, + metadata={"thread_executor": executor}, + ) + await compiled.runtime.run() + return compiled + finally: + ticker.cancel() + await asyncio.gather(ticker, return_exceptions=True) + executor.shutdown(wait=True, cancel_futures=True) + loop._default_executor = None # type: ignore[attr-defined] + + compiled = asyncio.run(scenario()) + + assert compiled.plugin_specs["ppe_gate"].outputs == { + "alerts": "DetectionAlert/v1" + } + assert compiled.plugin_specs["six_class_detector"].outputs == { + "detections": "DetectionResult/v1" + } + + assert ppe_model.load_calls == ppe_model.close_calls == 1 + assert six_class_model.load_calls == six_class_model.close_calls == 1 + assert len(ppe_model.frames) == len(six_class_model.frames) == len(frames) + assert all( + ppe_frame is six_class_frame + for ppe_frame, six_class_frame in zip( + ppe_model.frames, + six_class_model.frames, + ) + ) + assert ppe_model.selected_labels == [("No-Helmet",)] * len(frames) + assert six_class_model.selected_labels == [SIX_CLASS_LABELS] * len(frames) + + assert len(alert_sink.envelopes) == 1 + alert_envelope = alert_sink.envelopes[0] + assert alert_sink.input_ports == ["alerts"] + assert alert_envelope.schema == "DetectionAlert/v1" + assert alert_envelope.source_id == "source" + assert alert_envelope.sequence == 1 + assert isinstance(alert_envelope.payload, DetectionAlert) + assert alert_envelope.payload.model_id == "construction-ppe-yolov8@1" + assert alert_envelope.payload.rule_id == "no-helmet" + + assert raw_sink.input_ports == ["detections", "detections"] + assert [envelope.schema for envelope in raw_sink.envelopes] == [ + "DetectionResult/v1", + "DetectionResult/v1", + ] + assert [envelope.source_id for envelope in raw_sink.envelopes] == [ + "source", + "source", + ] + assert [envelope.sequence for envelope in raw_sink.envelopes] == [0, 1] + assert all( + isinstance(envelope.payload, DetectionResult) + for envelope in raw_sink.envelopes + ) + assert all( + envelope.payload.model_id == SIX_CLASS_MODEL_ID + for envelope in raw_sink.envelopes + ) + assert all( + envelope.payload.source_frame is None + for envelope in raw_sink.envelopes + ) + assert alert_envelope.trace_id == raw_sink.envelopes[1].trace_id + + stats = compiled.runtime.edge_stats() + ppe_fanout = next( + value + for name, value in stats.items() + if name.endswith("decoder.frames->ppe_detector.frames") + ) + six_class_fanout = next( + value + for name, value in stats.items() + if name.endswith("decoder.frames->six_class_detector.frames") + ) + assert ppe_fanout.enqueued == len(frames) + assert six_class_fanout.enqueued == len(frames) diff --git a/tests/integration/test_process_worker.py b/tests/integration/test_process_worker.py new file mode 100644 index 0000000..6cd9683 --- /dev/null +++ b/tests/integration/test_process_worker.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +import asyncio +from concurrent.futures import ThreadPoolExecutor +from time import sleep +from typing import Any + +import pytest + +from cmvr_edge_ai.workers import ( + PersistentProcessWorker, + ProcessWorkerError, + RemoteWorkerError, +) + + +def _worker_handler(payload: Any) -> Any: + if payload == "explode": + raise RuntimeError("intentional remote failure") + if payload == "slow": + sleep(0.3) + return "late-result" + return payload * 2 + + +def _worker_factory(): # type: ignore[no-untyped-def] + """Top-level factory so the spawn start method can import and pickle it.""" + + return _worker_handler + + +async def _keep_restricted_event_loop_responsive() -> None: + """Provide wakeups where sandboxed self-pipe notifications are unavailable.""" + + while True: + await asyncio.sleep(0.01) + + +def test_spawn_worker_returns_results_and_surfaces_remote_errors() -> None: + async def exercise(): # type: ignore[no-untyped-def] + loop = asyncio.get_running_loop() + host_executor = ThreadPoolExecutor(max_workers=2) + loop.set_default_executor(host_executor) + ticker = asyncio.create_task(_keep_restricted_event_loop_responsive()) + worker = PersistentProcessWorker( + _worker_factory, + start_method="spawn", + queue_capacity=1, + ) + try: + await worker.start() + assert await worker.submit(21, timeout_s=10) == 42 + with pytest.raises(RemoteWorkerError) as captured: + await worker.submit("explode", timeout_s=10) + assert captured.value.remote_type == "RuntimeError" + assert "intentional remote failure" in str(captured.value) + finally: + await worker.stop(timeout_s=5) + ticker.cancel() + await asyncio.gather(ticker, return_exceptions=True) + host_executor.shutdown(wait=True, cancel_futures=True) + loop._default_executor = None # type: ignore[attr-defined] + return worker + + worker = asyncio.run(exercise()) + + assert worker.is_alive is False + + +def test_timed_out_request_poisons_worker_and_rejects_later_submits() -> None: + async def exercise(): # type: ignore[no-untyped-def] + loop = asyncio.get_running_loop() + host_executor = ThreadPoolExecutor(max_workers=2) + loop.set_default_executor(host_executor) + ticker = asyncio.create_task(_keep_restricted_event_loop_responsive()) + worker = PersistentProcessWorker( + _worker_factory, + start_method="spawn", + queue_capacity=1, + ) + try: + await worker.start() + with pytest.raises(TimeoutError, match="timed out"): + await worker.submit("slow", timeout_s=0.05) + assert worker.is_alive is True + with pytest.raises(ProcessWorkerError, match="worker is poisoned"): + await worker.submit(22, timeout_s=1) + finally: + await worker.stop(timeout_s=2) + ticker.cancel() + await asyncio.gather(ticker, return_exceptions=True) + host_executor.shutdown(wait=True, cancel_futures=True) + loop._default_executor = None # type: ignore[attr-defined] + return worker + + worker = asyncio.run(exercise()) + + assert worker.is_alive is False diff --git a/tests/integration/test_runtime.py b/tests/integration/test_runtime.py new file mode 100644 index 0000000..098df83 --- /dev/null +++ b/tests/integration/test_runtime.py @@ -0,0 +1,235 @@ +from __future__ import annotations + +import asyncio +from typing import Any + +from cmvr_edge_ai.core import ( + ComponentContext, + Edge, + Envelope, + PipelineRuntime, + RuntimeState, + Sink, + Source, +) + + +class ListSource(Source): + def __init__(self, items: list[str]) -> None: + self.items = items + + async def messages(self): # type: ignore[no-untyped-def] + for sequence, item in enumerate(self.items): + yield Envelope(item, source_id="source", sequence=sequence) + + +class CollectSink(Sink): + def __init__(self, delay_s: float = 0.0) -> None: + self.delay_s = delay_s + self.envelopes: list[Envelope[Any]] = [] + self.input_ports: list[str] = [] + self.stop_calls = 0 + + async def consume(self, envelope: Envelope[Any], input_port: str = "input") -> None: + if self.delay_s: + await asyncio.sleep(self.delay_s) + self.envelopes.append(envelope) + self.input_ports.append(input_port) + + async def stop(self) -> None: + self.stop_calls += 1 + + +class BurstThenWaitSource(Source): + def __init__(self, items: list[str]) -> None: + self.items = items + self.produced = asyncio.Event() + self.release = asyncio.Event() + self.stop_calls = 0 + + async def setup(self, context: ComponentContext) -> None: + self.shutdown_event = context.shutdown_event + + async def messages(self): # type: ignore[no-untyped-def] + for sequence, item in enumerate(self.items): + yield Envelope(item, source_id="burst", sequence=sequence) + self.produced.set() + await self.release.wait() + + async def stop(self) -> None: + self.stop_calls += 1 + + +class SetupBlockingSource(Source): + def __init__( + self, + setup_entered: asyncio.Event, + allow_setup: asyncio.Event, + run_forever: asyncio.Event, + ) -> None: + self.setup_entered = setup_entered + self.allow_setup = allow_setup + self.run_forever = run_forever + self.stop_calls = 0 + + async def setup(self, context: ComponentContext) -> None: + del context + self.setup_entered.set() + await self.allow_setup.wait() + + async def messages(self): # type: ignore[no-untyped-def] + await self.run_forever.wait() + if False: + yield Envelope("unreachable") + + async def stop(self) -> None: + self.stop_calls += 1 + + +class BlockingStopSource(Source): + def __init__( + self, + run_forever: asyncio.Event, + stop_entered: asyncio.Event, + stop_cancelled: asyncio.Event, + ) -> None: + self.run_forever = run_forever + self.stop_entered = stop_entered + self.stop_cancelled = stop_cancelled + + async def messages(self): # type: ignore[no-untyped-def] + await self.run_forever.wait() + if False: + yield Envelope("unreachable") + + async def stop(self) -> None: + self.stop_entered.set() + try: + await asyncio.Event().wait() + except asyncio.CancelledError: + self.stop_cancelled.set() + raise + + +def test_runtime_fan_out_delivers_each_envelope_to_each_edge() -> None: + async def exercise(): # type: ignore[no-untyped-def] + source = ListSource(["one", "two", "three"]) + left = CollectSink() + right = CollectSink() + runtime = PipelineRuntime( + {"source": source, "left": left, "right": right}, + [ + Edge("source", "left", capacity=2, name="left-edge"), + Edge("source", "right", capacity=2, name="right-edge"), + ], + pipeline_id="fanout", + ) + await runtime.run() + return runtime, left, right + + runtime, left, right = asyncio.run(exercise()) + + assert runtime.state is RuntimeState.STOPPED + assert [item.payload for item in left.envelopes] == ["one", "two", "three"] + assert [item.payload for item in right.envelopes] == ["one", "two", "three"] + assert all( + left_item is right_item + for left_item, right_item in zip(left.envelopes, right.envelopes) + ) + assert left.input_ports == ["input"] * 3 + assert set(runtime.edge_stats()) == {"left-edge", "right-edge"} + + +def test_graceful_stop_drains_already_accepted_messages_and_stops_once() -> None: + async def exercise(): # type: ignore[no-untyped-def] + source = BurstThenWaitSource(["one", "two", "three"]) + sink = CollectSink(delay_s=0.01) + runtime = PipelineRuntime( + {"source": source, "sink": sink}, + [Edge("source", "sink", capacity=3, name="drain-edge")], + pipeline_id="graceful", + shutdown_timeout_s=1, + ) + + await runtime.start() + await asyncio.wait_for(source.produced.wait(), timeout=1) + await runtime.stop(graceful=True) + return source, sink, runtime + + source, sink, runtime = asyncio.run(exercise()) + + assert [item.payload for item in sink.envelopes] == ["one", "two", "three"] + assert source.stop_calls == 1 + assert sink.stop_calls == 1 + assert runtime.state is RuntimeState.STOPPED + stats = runtime.edge_stats()["drain-edge"] + assert stats.enqueued == stats.dequeued == 3 + assert stats.discarded_on_close == 0 + assert stats.closed is True + + +def test_stop_racing_start_waits_for_startup_then_tears_down_once() -> None: + async def exercise(): # type: ignore[no-untyped-def] + setup_entered = asyncio.Event() + allow_setup = asyncio.Event() + source = SetupBlockingSource( + setup_entered, + allow_setup, + asyncio.Event(), + ) + sink = CollectSink() + runtime = PipelineRuntime( + {"source": source, "sink": sink}, + [Edge("source", "sink")], + pipeline_id="start-stop-race", + ) + + start_task = asyncio.create_task(runtime.start()) + await asyncio.wait_for(setup_entered.wait(), timeout=1) + stop_task = asyncio.create_task(runtime.stop(graceful=False, timeout_s=1)) + await asyncio.sleep(0) + assert runtime.state is RuntimeState.STARTING + assert stop_task.done() is False + + allow_setup.set() + await asyncio.wait_for(asyncio.gather(start_task, stop_task), timeout=1) + return runtime, source, sink + + runtime, source, sink = asyncio.run(exercise()) + + assert runtime.state is RuntimeState.STOPPED + assert runtime.shutdown_event.is_set() + assert source.stop_calls == 1 + assert sink.stop_calls == 1 + + +def test_blocking_component_stop_is_bounded_by_shutdown_timeout() -> None: + async def exercise(): # type: ignore[no-untyped-def] + stop_entered = asyncio.Event() + stop_cancelled = asyncio.Event() + source = BlockingStopSource( + asyncio.Event(), + stop_entered, + stop_cancelled, + ) + sink = CollectSink() + runtime = PipelineRuntime( + {"source": source, "sink": sink}, + [Edge("source", "sink")], + pipeline_id="bounded-stop", + ) + await runtime.start() + + loop = asyncio.get_running_loop() + started_at = loop.time() + await runtime.stop(graceful=False, timeout_s=0.05) + elapsed = loop.time() - started_at + await asyncio.sleep(0) + return runtime, stop_entered, stop_cancelled, elapsed + + runtime, stop_entered, stop_cancelled, elapsed = asyncio.run(exercise()) + + assert stop_entered.is_set() + assert stop_cancelled.is_set() + assert elapsed < 0.5 + assert runtime.state is RuntimeState.STOPPED diff --git a/tests/unit/__pycache__/test_agv_connector.cpython-310.pyc b/tests/unit/__pycache__/test_agv_connector.cpython-310.pyc new file mode 100644 index 0000000..5e58ff1 Binary files /dev/null and b/tests/unit/__pycache__/test_agv_connector.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_agv_connector.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_agv_connector.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..5310bd1 Binary files /dev/null and b/tests/unit/__pycache__/test_agv_connector.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_agv_connector.cpython-312.pyc b/tests/unit/__pycache__/test_agv_connector.cpython-312.pyc new file mode 100644 index 0000000..fb7b923 Binary files /dev/null and b/tests/unit/__pycache__/test_agv_connector.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_bidi_stream.cpython-310.pyc b/tests/unit/__pycache__/test_bidi_stream.cpython-310.pyc new file mode 100644 index 0000000..3e005d2 Binary files /dev/null and b/tests/unit/__pycache__/test_bidi_stream.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_bidi_stream.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_bidi_stream.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..2818914 Binary files /dev/null and b/tests/unit/__pycache__/test_bidi_stream.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_bidi_stream.cpython-312.pyc b/tests/unit/__pycache__/test_bidi_stream.cpython-312.pyc new file mode 100644 index 0000000..3388cd5 Binary files /dev/null and b/tests/unit/__pycache__/test_bidi_stream.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_builtin_plugins.cpython-310.pyc b/tests/unit/__pycache__/test_builtin_plugins.cpython-310.pyc new file mode 100644 index 0000000..9298218 Binary files /dev/null and b/tests/unit/__pycache__/test_builtin_plugins.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_builtin_plugins.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_builtin_plugins.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..11ee504 Binary files /dev/null and b/tests/unit/__pycache__/test_builtin_plugins.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_builtin_plugins.cpython-312.pyc b/tests/unit/__pycache__/test_builtin_plugins.cpython-312.pyc new file mode 100644 index 0000000..ed5d0db Binary files /dev/null and b/tests/unit/__pycache__/test_builtin_plugins.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_camera_connector.cpython-310.pyc b/tests/unit/__pycache__/test_camera_connector.cpython-310.pyc new file mode 100644 index 0000000..9b312e7 Binary files /dev/null and b/tests/unit/__pycache__/test_camera_connector.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_camera_connector.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_camera_connector.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..4d49ea2 Binary files /dev/null and b/tests/unit/__pycache__/test_camera_connector.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_camera_connector.cpython-312.pyc b/tests/unit/__pycache__/test_camera_connector.cpython-312.pyc new file mode 100644 index 0000000..22da0f3 Binary files /dev/null and b/tests/unit/__pycache__/test_camera_connector.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_compiler.cpython-310.pyc b/tests/unit/__pycache__/test_compiler.cpython-310.pyc new file mode 100644 index 0000000..b037d58 Binary files /dev/null and b/tests/unit/__pycache__/test_compiler.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_compiler.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_compiler.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..1ddd4d5 Binary files /dev/null and b/tests/unit/__pycache__/test_compiler.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_compiler.cpython-312.pyc b/tests/unit/__pycache__/test_compiler.cpython-312.pyc new file mode 100644 index 0000000..c6b7087 Binary files /dev/null and b/tests/unit/__pycache__/test_compiler.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_config.cpython-310.pyc b/tests/unit/__pycache__/test_config.cpython-310.pyc new file mode 100644 index 0000000..baa5b86 Binary files /dev/null and b/tests/unit/__pycache__/test_config.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_config.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_config.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..64adfef Binary files /dev/null and b/tests/unit/__pycache__/test_config.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_config.cpython-312.pyc b/tests/unit/__pycache__/test_config.cpython-312.pyc new file mode 100644 index 0000000..579c03a Binary files /dev/null and b/tests/unit/__pycache__/test_config.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_detection_annotation.cpython-310.pyc b/tests/unit/__pycache__/test_detection_annotation.cpython-310.pyc new file mode 100644 index 0000000..9be4a73 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_annotation.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_detection_annotation.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_detection_annotation.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..7e44e1d Binary files /dev/null and b/tests/unit/__pycache__/test_detection_annotation.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_detection_annotation.cpython-312.pyc b/tests/unit/__pycache__/test_detection_annotation.cpython-312.pyc new file mode 100644 index 0000000..3f42023 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_annotation.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_detection_compile.cpython-310.pyc b/tests/unit/__pycache__/test_detection_compile.cpython-310.pyc new file mode 100644 index 0000000..ed7645f Binary files /dev/null and b/tests/unit/__pycache__/test_detection_compile.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_detection_compile.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_detection_compile.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..3377a95 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_compile.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_detection_compile.cpython-312.pyc b/tests/unit/__pycache__/test_detection_compile.cpython-312.pyc new file mode 100644 index 0000000..ebfb71e Binary files /dev/null and b/tests/unit/__pycache__/test_detection_compile.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_detection_operator.cpython-310.pyc b/tests/unit/__pycache__/test_detection_operator.cpython-310.pyc new file mode 100644 index 0000000..4b77faf Binary files /dev/null and b/tests/unit/__pycache__/test_detection_operator.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_detection_operator.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_detection_operator.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..c4dac3f Binary files /dev/null and b/tests/unit/__pycache__/test_detection_operator.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_detection_operator.cpython-312.pyc b/tests/unit/__pycache__/test_detection_operator.cpython-312.pyc new file mode 100644 index 0000000..145b892 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_operator.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_detection_registry.cpython-310.pyc b/tests/unit/__pycache__/test_detection_registry.cpython-310.pyc new file mode 100644 index 0000000..5040f89 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_registry.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_detection_registry.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_detection_registry.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..d21dab4 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_registry.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_detection_registry.cpython-312.pyc b/tests/unit/__pycache__/test_detection_registry.cpython-312.pyc new file mode 100644 index 0000000..0c7ff3f Binary files /dev/null and b/tests/unit/__pycache__/test_detection_registry.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_detection_trigger.cpython-310.pyc b/tests/unit/__pycache__/test_detection_trigger.cpython-310.pyc new file mode 100644 index 0000000..9b3a253 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_trigger.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_detection_trigger.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_detection_trigger.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..7c82595 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_trigger.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_detection_trigger.cpython-312.pyc b/tests/unit/__pycache__/test_detection_trigger.cpython-312.pyc new file mode 100644 index 0000000..81b97e9 Binary files /dev/null and b/tests/unit/__pycache__/test_detection_trigger.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_edge_queue.cpython-310.pyc b/tests/unit/__pycache__/test_edge_queue.cpython-310.pyc new file mode 100644 index 0000000..eb810c7 Binary files /dev/null and b/tests/unit/__pycache__/test_edge_queue.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_edge_queue.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_edge_queue.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..8c70fde Binary files /dev/null and b/tests/unit/__pycache__/test_edge_queue.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_edge_queue.cpython-312.pyc b/tests/unit/__pycache__/test_edge_queue.cpython-312.pyc new file mode 100644 index 0000000..05a674f Binary files /dev/null and b/tests/unit/__pycache__/test_edge_queue.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_http_json_sink.cpython-310.pyc b/tests/unit/__pycache__/test_http_json_sink.cpython-310.pyc new file mode 100644 index 0000000..edf798c Binary files /dev/null and b/tests/unit/__pycache__/test_http_json_sink.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_http_json_sink.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_http_json_sink.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..166a2dd Binary files /dev/null and b/tests/unit/__pycache__/test_http_json_sink.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_http_json_sink.cpython-312.pyc b/tests/unit/__pycache__/test_http_json_sink.cpython-312.pyc new file mode 100644 index 0000000..9dd3b12 Binary files /dev/null and b/tests/unit/__pycache__/test_http_json_sink.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_safety_gate.cpython-310.pyc b/tests/unit/__pycache__/test_safety_gate.cpython-310.pyc new file mode 100644 index 0000000..aea0571 Binary files /dev/null and b/tests/unit/__pycache__/test_safety_gate.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_safety_gate.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_safety_gate.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..d562a43 Binary files /dev/null and b/tests/unit/__pycache__/test_safety_gate.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_safety_gate.cpython-312.pyc b/tests/unit/__pycache__/test_safety_gate.cpython-312.pyc new file mode 100644 index 0000000..27624e1 Binary files /dev/null and b/tests/unit/__pycache__/test_safety_gate.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_video_decoder.cpython-310.pyc b/tests/unit/__pycache__/test_video_decoder.cpython-310.pyc new file mode 100644 index 0000000..83dafcd Binary files /dev/null and b/tests/unit/__pycache__/test_video_decoder.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_video_decoder.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_video_decoder.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..b334af0 Binary files /dev/null and b/tests/unit/__pycache__/test_video_decoder.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_video_decoder.cpython-312.pyc b/tests/unit/__pycache__/test_video_decoder.cpython-312.pyc new file mode 100644 index 0000000..0f6c891 Binary files /dev/null and b/tests/unit/__pycache__/test_video_decoder.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_yolo_model.cpython-310.pyc b/tests/unit/__pycache__/test_yolo_model.cpython-310.pyc new file mode 100644 index 0000000..d229f10 Binary files /dev/null and b/tests/unit/__pycache__/test_yolo_model.cpython-310.pyc differ diff --git a/tests/unit/__pycache__/test_yolo_model.cpython-312-pytest-7.4.4.pyc b/tests/unit/__pycache__/test_yolo_model.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..d923ab6 Binary files /dev/null and b/tests/unit/__pycache__/test_yolo_model.cpython-312-pytest-7.4.4.pyc differ diff --git a/tests/unit/__pycache__/test_yolo_model.cpython-312.pyc b/tests/unit/__pycache__/test_yolo_model.cpython-312.pyc new file mode 100644 index 0000000..abbc439 Binary files /dev/null and b/tests/unit/__pycache__/test_yolo_model.cpython-312.pyc differ diff --git a/tests/unit/test_agv_connector.py b/tests/unit/test_agv_connector.py new file mode 100644 index 0000000..ff28921 --- /dev/null +++ b/tests/unit/test_agv_connector.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +import pytest + +from cmvr_edge_ai.connectors.cmvr_es import CmvrAgvCommandSink + + +VELOCITY_LIMITS = { + "vx": {"min": -0.5, "max": 0.5}, + "vy": {"min": -0.3, "max": 0.3}, + "wz": {"min": -1.0, "max": 1.0}, +} + + +def test_agv_sink_requires_a_fixed_device() -> None: + with pytest.raises(ValueError, match="fixed, non-empty device_id"): + CmvrAgvCommandSink("agv", {"endpoint": "cmvr_es"}) + + +def test_unsafe_velocity_override_requires_a_real_boolean() -> None: + with pytest.raises(ValueError, match="must be a boolean"): + CmvrAgvCommandSink( + "agv", + { + "endpoint": "cmvr_es", + "device_id": "src1100", + # Environment expansion deliberately leaves plugin params as + # strings. The dangerous override must not use bool("false"). + "unsafe_allow_unleased_velocity": "false", + "velocity_limits": VELOCITY_LIMITS, + }, + ) + + +def test_unsafe_velocity_override_requires_sink_side_limits() -> None: + with pytest.raises(ValueError, match="requires explicit velocity_limits"): + CmvrAgvCommandSink( + "agv", + { + "endpoint": "cmvr_es", + "device_id": "src1100", + "unsafe_allow_unleased_velocity": True, + }, + ) diff --git a/tests/unit/test_bidi_stream.py b/tests/unit/test_bidi_stream.py new file mode 100644 index 0000000..e37d857 --- /dev/null +++ b/tests/unit/test_bidi_stream.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import asyncio + +import pytest + +from cmvr_edge_ai.transports.grpc import ( + BidiRequestStream, + BidiRequestStreamClosed, +) + + +def test_close_wakes_blocked_sender_without_accepting_its_request() -> None: + async def exercise(): # type: ignore[no-untyped-def] + stream = BidiRequestStream[str](capacity=1) + await stream.send("accepted") + blocked_sender = asyncio.create_task(stream.send("blocked")) + await asyncio.sleep(0) + assert blocked_sender.done() is False + + await stream.close() + with pytest.raises(BidiRequestStreamClosed, match="closed"): + await blocked_sender + drained = [request async for request in stream] + return drained, stream.stats() + + drained, stats = asyncio.run(exercise()) + + assert drained == ["accepted"] + assert stats.enqueued == 1 + assert stats.blocked_puts == 1 + assert stats.closed is True + + +def test_bidirectional_request_stream_allows_exactly_one_consumer() -> None: + async def exercise() -> None: + stream = BidiRequestStream[str]() + await stream.send("first") + first_consumer = stream.__aiter__() + assert await anext(first_consumer) == "first" + + second_consumer = stream.__aiter__() + with pytest.raises(RuntimeError, match="exactly one consumer"): + await anext(second_consumer) + + await stream.close() + with pytest.raises(StopAsyncIteration): + await anext(first_consumer) + + asyncio.run(exercise()) diff --git a/tests/unit/test_builtin_plugins.py b/tests/unit/test_builtin_plugins.py new file mode 100644 index 0000000..d6922c3 --- /dev/null +++ b/tests/unit/test_builtin_plugins.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +import asyncio + +from cmvr_edge_ai.plugins import PluginRegistry, register_builtin_plugins + + +def test_builtin_source_cannot_emit_an_undeclared_dynamic_port() -> None: + registry = PluginRegistry() + register_builtin_plugins(registry) + source = registry.create( + "core.sequence_source@1", + "source", + {"items": ["message"], "output_port": "undeclared"}, + ) + + async def collect(): # type: ignore[no-untyped-def] + return [emission async for emission in source.messages()] + + emissions = asyncio.run(collect()) + declared_ports = registry.resolve("core.sequence_source@1").outputs + + assert [emission.port for emission in emissions] == ["output"] + assert all(emission.port in declared_ports for emission in emissions) diff --git a/tests/unit/test_camera_connector.py b/tests/unit/test_camera_connector.py new file mode 100644 index 0000000..bbc1fdb --- /dev/null +++ b/tests/unit/test_camera_connector.py @@ -0,0 +1,576 @@ +from __future__ import annotations + +import asyncio +import logging +from collections import deque +from types import SimpleNamespace +from typing import Any + +import pytest + +import cmvr_edge_ai.connectors.cmvr_es.camera as camera_module +from cmvr_edge_ai.connectors.cmvr_es import ( + CmvrCameraRgbStreamSource, + CmvrServiceError, +) +from cmvr_edge_ai.contracts import ImageFrame +from cmvr_edge_ai.core import Emission + + +class _FakeCall: + def __init__(self, *events: Any) -> None: + self._events = deque(events) + self.cancel_count = 0 + + def __aiter__(self) -> _FakeCall: + return self + + async def __anext__(self) -> Any: + if not self._events: + raise StopAsyncIteration + event = self._events.popleft() + if isinstance(event, BaseException): + raise event + return event + + def cancel(self) -> bool: + self.cancel_count += 1 + return True + + +class _BlockingCall: + def __init__(self) -> None: + self.entered = asyncio.Event() + self._released = asyncio.Event() + self.cancel_count = 0 + + def __aiter__(self) -> _BlockingCall: + return self + + async def __anext__(self) -> Any: + self.entered.set() + await self._released.wait() + raise asyncio.CancelledError + + def cancel(self) -> bool: + self.cancel_count += 1 + self._released.set() + return True + + +class _ImmediateRetryEvent(asyncio.Event): + """Exercise reconnect paths without waiting for wall-clock backoff.""" + + async def wait(self) -> bool: + if self.is_set(): + return True + raise TimeoutError + + +class _OneProgressTickEvent: + """Cause exactly one heartbeat tick, then terminate its logging loop.""" + + def __init__(self) -> None: + self._done = False + + def is_set(self) -> bool: + return self._done + + async def wait(self) -> bool: + self._done = True + raise TimeoutError + + +class _FakeStub: + def __init__( + self, + *calls: Any, + start_feedbacks: tuple[Any, ...] = (), + ) -> None: + self._calls = deque(calls) + self._start_feedbacks = deque(start_feedbacks) + self.events: list[str] = [] + self.start_requests: list[Any] = [] + self.start_timeouts: list[float | None] = [] + self.start_count = 0 + self.open_count = 0 + + async def StartCamera( + self, + request: Any, + *, + timeout: float | None = None, + ) -> Any: + self.events.append("start") + self.start_requests.append(request) + self.start_timeouts.append(timeout) + self.start_count += 1 + if not self._start_feedbacks: + return _start_feedback() + feedback = self._start_feedbacks.popleft() + if isinstance(feedback, BaseException): + raise feedback + return feedback + + def GetRGBImageStream(self, request_stream: Any) -> Any: + # The connector owns request-stream lifetime. This fake only exercises + # response and reconnect behavior, so protobuf request objects are not + # imported or inspected. + del request_stream + self.events.append("stream") + self.open_count += 1 + if not self._calls: + raise AssertionError("camera connector opened an unexpected stream") + return self._calls.popleft() + + +def _start_feedback( + *, + success: bool = True, + error_message: str = "", +) -> Any: + return SimpleNamespace( + header=SimpleNamespace( + success=success, + error_message=error_message, + ) + ) + + +def _response( + remote_sequence: int, + *, + data: bytes = b"encoded-frame", + codec: str = "H264", + key_frame: bool = False, + width: int = 640, + height: int = 480, + seconds: int = 0, + nanos: int = 0, + success: bool = True, + error_message: str = "", +) -> Any: + return SimpleNamespace( + header=SimpleNamespace( + success=success, + error_message=error_message, + timestamp=SimpleNamespace(seconds=seconds, nanos=nanos), + ), + color_frame=SimpleNamespace( + data=data, + width=width, + height=height, + codec=codec, + is_key_frame=key_frame, + ), + intrinsics=SimpleNamespace( + fx=100.0, + fy=101.0, + cx=50.0, + cy=51.0, + coeffs=(0.1, 0.2, 0.3, 0.4, 0.5), + ), + seq_no=remote_sequence, + ) + + +def _source(stub: Any, **params: Any) -> CmvrCameraRgbStreamSource: + configured = { + "endpoint": "cmvr_es", + "device_id": "camera-1", + "pixel_format": "BGR8", + "reconnect_initial_s": 0.001, + "reconnect_max_s": 0.002, + **params, + } + source = CmvrCameraRgbStreamSource("camera", configured) + # setup() is deliberately bypassed: these unit tests exercise stream state + # without importing generated protobuf bindings or constructing gRPC objects. + source._stub = stub # type: ignore[attr-defined] + source._start_request = object() # type: ignore[attr-defined] + source._stream_request = object() # type: ignore[attr-defined] + source._shutdown_event = asyncio.Event() # type: ignore[attr-defined] + source._rpc_timeout_s = 2.5 # type: ignore[attr-defined] + return source + + +def test_reconnect_uses_local_monotonic_sequence_and_new_session_id( + monkeypatch: pytest.MonkeyPatch, +) -> None: + session_ids = iter(("session-a", "session-b")) + monkeypatch.setattr( + camera_module, + "uuid4", + lambda: SimpleNamespace(hex=next(session_ids)), + ) + first_call = _FakeCall( + _response( + 900, + data=b"first", + key_frame=True, + seconds=12, + nanos=34, + ) + ) + second_call = _FakeCall(_response(3, data=b"second")) + + stub = _FakeStub(first_call, second_call) + + async def exercise(): # type: ignore[no-untyped-def] + source = _source(stub) + stream = source.messages() + first = await asyncio.wait_for(anext(stream), timeout=1) + second = await asyncio.wait_for(anext(stream), timeout=1) + await stream.aclose() + return first, second + + first, second = asyncio.run(exercise()) + + assert isinstance(first, Emission) + assert isinstance(second, Emission) + assert first.port == second.port == "frames" + assert [first.envelope.sequence, second.envelope.sequence] == [0, 1] + assert [first.envelope.session_id, second.envelope.session_id] == [ + "session-a", + "session-b", + ] + assert [ + first.envelope.attributes["remote_sequence"], + second.envelope.attributes["remote_sequence"], + ] == [900, 3] + assert first.envelope.attributes["stream_session_id"] == "session-a" + assert second.envelope.attributes["stream_session_id"] == "session-b" + assert first.envelope.source_id == second.envelope.source_id == "camera-1" + assert first.envelope.captured_at_ns == 12_000_000_034 + assert second.envelope.captured_at_ns is None + assert isinstance(first.envelope.payload, ImageFrame) + assert first.envelope.payload.data == b"first" + assert first.envelope.payload.codec == "H264" + assert first.envelope.payload.is_key_frame is True + assert first.envelope.payload.pixel_format == "BGR8" + assert first.envelope.payload.intrinsics is not None + assert first.envelope.payload.intrinsics.coefficients == ( + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + ) + assert first_call.cancel_count == 1 + assert second_call.cancel_count == 1 + assert stub.events == ["start", "stream", "start", "stream"] + assert stub.start_count == stub.open_count == 2 + assert stub.start_timeouts == [2.5, 2.5] + assert all(request is not None for request in stub.start_requests) + + +def test_transport_failure_reconnects_to_a_new_call() -> None: + disconnected = _FakeCall(ConnectionError("temporary disconnect")) + recovered = _FakeCall(_response(77, data=b"recovered", key_frame=True)) + stub = _FakeStub(disconnected, recovered) + + async def exercise() -> Emission: + source = _source(stub) + stream = source.messages() + result = await asyncio.wait_for(anext(stream), timeout=1) + await stream.aclose() + assert isinstance(result, Emission) + return result + + emission = asyncio.run(exercise()) + + assert stub.open_count == 2 + assert stub.start_count == 2 + assert stub.events == ["start", "stream", "start", "stream"] + assert disconnected.cancel_count == 1 + assert recovered.cancel_count == 1 + assert emission.envelope.sequence == 0 + assert emission.envelope.attributes["remote_sequence"] == 77 + assert emission.envelope.payload.data == b"recovered" + + +def test_start_camera_rejection_prevents_stream_open() -> None: + stub = _FakeStub( + _FakeCall(_response(1)), + start_feedbacks=( + _start_feedback( + success=False, + error_message="camera is unavailable", + ), + ), + ) + + async def exercise() -> None: + source = _source(stub, reconnect=False) + with pytest.raises(CmvrServiceError, match="camera is unavailable"): + await asyncio.wait_for(anext(source.messages()), timeout=1) + + asyncio.run(exercise()) + + assert stub.events == ["start"] + assert stub.start_count == 1 + assert stub.start_timeouts == [2.5] + assert stub.open_count == 0 + + +def test_start_camera_transport_failure_is_retried_before_stream_open() -> None: + recovered = _FakeCall(_response(5, data=b"after-start-retry")) + stub = _FakeStub( + recovered, + start_feedbacks=( + ConnectionError("StartCamera unavailable"), + _start_feedback(), + ), + ) + + async def exercise() -> Emission: + source = _source(stub) + emission = await asyncio.wait_for(anext(source.messages()), timeout=1) + await source.stop() + assert isinstance(emission, Emission) + return emission + + emission = asyncio.run(exercise()) + + assert stub.events == ["start", "start", "stream"] + assert stub.start_count == 2 + assert stub.start_timeouts == [2.5, 2.5] + assert stub.open_count == 1 + assert emission.envelope.payload.data == b"after-start-retry" + + +def test_camera_stream_logs_the_complete_lifecycle( + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + now_ns = 0 + + def advancing_clock() -> int: + nonlocal now_ns + now_ns += 2_000_000_000 + return now_ns + + elapsed_s = 0.0 + + def advancing_timer() -> float: + nonlocal elapsed_s + elapsed_s += 0.05 + return elapsed_s + + monkeypatch.setattr(camera_module, "monotonic_ns", advancing_clock) + monkeypatch.setattr(camera_module, "perf_counter", advancing_timer) + first_session = _FakeCall( + _response(10, data=b"first", key_frame=True), + _response(11, data=b"second"), + ConnectionError("stream interrupted"), + ) + recovered_session = _FakeCall(_response(12, data=b"recovered")) + stub = _FakeStub(first_session, recovered_session) + + async def exercise() -> None: + source = _source(stub, stream_log_interval_s=1) + source._shutdown_event = _ImmediateRetryEvent() # type: ignore[attr-defined] + stream = source.messages() + await anext(stream) + await anext(stream) + await anext(stream) + await source.stop() + await stream.aclose() + + with caplog.at_level(logging.INFO, logger=camera_module.__name__): + asyncio.run(exercise()) + + messages = [record.getMessage() for record in caplog.records] + expected_phrases = ( + "camera start requested", + "camera start succeeded", + "camera stream opening", + "camera stream first frame", + "camera stream disconnected", + "camera source stopping", + "camera source stopped", + ) + for phrase in expected_phrases: + assert any(phrase in message for message in messages), messages + + assert sum("camera start requested" in message for message in messages) == 2 + assert sum("camera start succeeded" in message for message in messages) == 2 + assert sum("camera stream opening" in message for message in messages) == 2 + first_frame_messages = [ + message for message in messages if "camera stream first frame" in message + ] + assert len(first_frame_messages) == 2 + assert "remote_sequence=10" in first_frame_messages[0] + assert "remote_sequence=12" in first_frame_messages[1] + + +def test_camera_stream_progress_log_reports_window_statistics( + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr(camera_module, "monotonic_ns", lambda: 3_000_000_000) + stats = camera_module._StreamStats( + session_id="session-progress", + opened_ns=0, + window_started_ns=1_000_000_000, + frames=3, + received_bytes=600, + key_frames=1, + window_frames=2, + window_bytes=500, + window_key_frames=1, + last_frame_ns=2_500_000_000, + last_remote_sequence=42, + codec="H264", + width=640, + height=480, + ) + source = _source(_FakeStub(), stream_log_interval_s=1) + + async def exercise() -> None: + await source._log_stream_progress( # type: ignore[attr-defined] + stats, + _OneProgressTickEvent(), # type: ignore[arg-type] + ) + + with caplog.at_level(logging.INFO, logger=camera_module.__name__): + asyncio.run(exercise()) + + messages = [ + record.getMessage() + for record in caplog.records + if "camera stream progress" in record.getMessage() + ] + assert len(messages) == 1 + message = messages[0] + assert "session=session-progress" in message + assert "total_frames=3" in message + assert "window_frames=2" in message + assert "window_bytes=500" in message + assert "last_remote_sequence=42" in message + assert "codec=H264" in message + assert "width=640" in message + assert "height=480" in message + + +def test_reconnect_false_propagates_the_disconnect() -> None: + failed_call = _FakeCall(ConnectionError("camera cable disconnected")) + stub = _FakeStub(failed_call) + + async def exercise() -> None: + source = _source(stub, reconnect=False) + with pytest.raises(ConnectionError, match="camera cable disconnected"): + await asyncio.wait_for(anext(source.messages()), timeout=1) + + asyncio.run(exercise()) + + assert stub.open_count == 1 + assert stub.start_count == 1 + assert stub.events == ["start", "stream"] + assert failed_call.cancel_count == 1 + + +def test_max_reconnect_attempts_raises_after_the_allowed_retries() -> None: + first_failure = _FakeCall(ConnectionError("first disconnect")) + second_failure = _FakeCall(ConnectionError("second disconnect")) + stub = _FakeStub(first_failure, second_failure) + + async def exercise() -> None: + source = _source(stub, max_reconnect_attempts=1) + with pytest.raises( + CmvrServiceError, + match="exceeded max_reconnect_attempts", + ): + await asyncio.wait_for(anext(source.messages()), timeout=1) + + asyncio.run(exercise()) + + assert stub.open_count == 2 + assert stub.start_count == 2 + assert stub.events == ["start", "stream", "start", "stream"] + assert first_failure.cancel_count == 1 + assert second_failure.cancel_count == 1 + + +def test_stop_cancels_the_active_stream_call() -> None: + async def exercise(): # type: ignore[no-untyped-def] + call = _BlockingCall() + stub = _FakeStub(call) + source = _source(stub) + stream = source.messages() + pending = asyncio.create_task(anext(stream)) + await asyncio.wait_for(call.entered.wait(), timeout=1) + + await source.stop() + with pytest.raises(asyncio.CancelledError): + await asyncio.wait_for(pending, timeout=1) + await stream.aclose() + return call, source, stub + + call, source, stub = asyncio.run(exercise()) + + assert call.cancel_count == 1 + assert stub.events == ["start", "stream"] + assert source._call is None # type: ignore[attr-defined] + + +@pytest.mark.parametrize( + ("params", "message"), + [ + ({"typo": True}, "unknown camera source parameter"), + ({"device_id": None}, "non-empty device_id"), + ({"device_id": " "}, "non-empty device_id"), + ({"device_id": 1}, "non-empty device_id"), + ({"pixel_format": ""}, "pixel_format must be a non-empty string"), + ({"pixel_format": 1}, "pixel_format must be a non-empty string"), + ({"reconnect": "true"}, "reconnect must be a boolean"), + ({"reconnect": 1}, "reconnect must be a boolean"), + ({"reconnect_initial_s": True}, "must be a positive number"), + ({"reconnect_initial_s": 0}, "must be a positive number"), + ({"reconnect_initial_s": -1}, "must be a positive number"), + ({"reconnect_initial_s": float("nan")}, "must be a positive number"), + ({"reconnect_initial_s": float("inf")}, "must be a positive number"), + ({"reconnect_max_s": 0}, "must be a positive number"), + ({"reconnect_max_s": float("nan")}, "must be a positive number"), + ({"stream_log_interval_s": True}, "must be a positive number"), + ({"stream_log_interval_s": 0}, "must be a positive number"), + ({"stream_log_interval_s": -1}, "must be a positive number"), + ({"stream_log_interval_s": float("nan")}, "must be a positive number"), + ({"stream_log_interval_s": float("inf")}, "must be a positive number"), + ( + {"reconnect_initial_s": 2, "reconnect_max_s": 1}, + "reconnect_max_s must be >= reconnect_initial_s", + ), + ({"max_reconnect_attempts": True}, "must be an integer or null"), + ({"max_reconnect_attempts": 1.5}, "must be an integer or null"), + ({"max_reconnect_attempts": "1"}, "must be an integer or null"), + ({"max_reconnect_attempts": -1}, "must not be negative"), + ], +) +def test_camera_reconnect_parameters_are_strictly_validated( + params: dict[str, Any], + message: str, +) -> None: + configured: dict[str, Any] = {"device_id": "camera-1"} + configured.update(params) + + with pytest.raises(ValueError, match=message): + CmvrCameraRgbStreamSource("camera", configured) + + +def test_numeric_strings_are_accepted_for_environment_expanded_delays() -> None: + source = CmvrCameraRgbStreamSource( + "camera", + { + "device_id": "camera-1", + "reconnect_initial_s": "0.25", + "reconnect_max_s": "2.5", + "max_reconnect_attempts": 0, + "stream_log_interval_s": "12.5", + }, + ) + + assert source._reconnect_initial_s == 0.25 # type: ignore[attr-defined] + assert source._reconnect_max_s == 2.5 # type: ignore[attr-defined] + assert source._max_reconnect_attempts == 0 # type: ignore[attr-defined] + assert source._stream_log_interval_s == 12.5 # type: ignore[attr-defined] diff --git a/tests/unit/test_compiler.py b/tests/unit/test_compiler.py new file mode 100644 index 0000000..59759af --- /dev/null +++ b/tests/unit/test_compiler.py @@ -0,0 +1,372 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +import pytest + +from cmvr_edge_ai.compiler import PipelineCompileError, compile_pipeline +from cmvr_edge_ai.config import load_config_data +from cmvr_edge_ai.core import Envelope, Operator, Sink, Source +from cmvr_edge_ai.plugins import PluginKind, PluginRegistry, PluginSpec + + +class EmptySource(Source): + async def messages(self): # type: ignore[no-untyped-def] + if False: + yield Envelope("unused") + + +class IdentityOperator(Operator): + async def process( + self, envelope: Envelope[Any], input_port: str = "input" + ) -> Envelope[Any]: + del input_port + return envelope + + +class NullSink(Sink): + async def consume(self, envelope: Envelope[Any], input_port: str = "input") -> None: + del envelope, input_port + + +def _source_factory(node_id: str, params: Mapping[str, Any]) -> EmptySource: + del node_id, params + return EmptySource() + + +def _operator_factory(node_id: str, params: Mapping[str, Any]) -> IdentityOperator: + del node_id, params + return IdentityOperator() + + +def _sink_factory(node_id: str, params: Mapping[str, Any]) -> NullSink: + del node_id, params + return NullSink() + + +def _registry() -> PluginRegistry: + registry = PluginRegistry() + registry.register( + PluginSpec( + "test.text_source@1", + PluginKind.SOURCE, + _source_factory, + outputs={"events": "TextEvent/v1"}, + ) + ) + registry.register( + PluginSpec( + "test.text_operator@1", + PluginKind.OPERATOR, + _operator_factory, + inputs={"input": "TextEvent/v1"}, + outputs={"events": "TextEvent/v1"}, + ) + ) + registry.register( + PluginSpec( + "test.audio_sink@1", + PluginKind.SINK, + _sink_factory, + inputs={"input": "AudioChunk/v1"}, + ) + ) + registry.register( + PluginSpec( + "test.any_sink@1", + PluginKind.SINK, + _sink_factory, + inputs={"input": "*"}, + ) + ) + registry.register( + PluginSpec( + "test.command_source@1", + PluginKind.SOURCE, + _source_factory, + outputs={"commands": "RobotCommand/v1"}, + ) + ) + registry.register( + PluginSpec( + "test.approved_source@1", + PluginKind.SOURCE, + _source_factory, + outputs={"commands": "ApprovedRobotCommand/v1"}, + ) + ) + registry.register( + PluginSpec( + "test.safety_gate@1", + PluginKind.OPERATOR, + _operator_factory, + inputs={"input": "RobotCommand/v1"}, + outputs={"commands": "ApprovedRobotCommand/v1"}, + tags=frozenset({"safety_gate"}), + ) + ) + registry.register( + PluginSpec( + "test.approved_operator@1", + PluginKind.OPERATOR, + _operator_factory, + inputs={"input": "ApprovedRobotCommand/v1"}, + outputs={"commands": "ApprovedRobotCommand/v1"}, + ) + ) + registry.register( + PluginSpec( + "test.actuator@1", + PluginKind.SINK, + _sink_factory, + inputs={"commands": "ApprovedRobotCommand/v1"}, + tags=frozenset({"actuator"}), + ) + ) + return registry + + +def _config(nodes: dict[str, dict[str, Any]], edges: list[dict[str, Any]]): + return load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "pipelines": { + "test": { + "nodes": nodes, + "edges": edges, + } + }, + } + ) + + +@pytest.mark.parametrize( + ("edge", "message"), + [ + ( + {"from": "source.missing", "to": "sink.input"}, + "unknown output port", + ), + ( + {"from": "source.events", "to": "sink.missing"}, + "unknown input port", + ), + ], +) +def test_compile_rejects_unknown_plugin_ports( + edge: dict[str, str], message: str +) -> None: + config = _config( + { + "source": {"uses": "test.text_source@1"}, + "sink": {"uses": "test.any_sink@1"}, + }, + [edge], + ) + + with pytest.raises(PipelineCompileError, match=message): + compile_pipeline(config, "test", _registry()) + + +def test_compile_rejects_incompatible_schemas() -> None: + config = _config( + { + "source": {"uses": "test.text_source@1"}, + "sink": {"uses": "test.audio_sink@1"}, + }, + [{"from": "source.events", "to": "sink.input"}], + ) + + with pytest.raises(PipelineCompileError, match="schema mismatch"): + compile_pipeline(config, "test", _registry()) + + +def test_compile_accepts_wildcard_schema() -> None: + config = _config( + { + "source": {"uses": "test.text_source@1"}, + "sink": {"uses": "test.any_sink@1"}, + }, + [{"from": "source.events", "to": "sink.input"}], + ) + + compiled = compile_pipeline(config, "test", _registry()) + + assert compiled.pipeline_id == "test" + assert compiled.runtime.edges[0].source_port == "events" + + +def test_video_contiguous_qos_requires_lossless_overflow() -> None: + nodes = { + "source": {"uses": "test.text_source@1"}, + "operator": {"uses": "test.text_operator@1"}, + } + accepted = _config( + nodes, + [ + { + "from": "source.events", + "to": "operator.input", + "qos": { + "profile": "video_contiguous", + "capacity": 8, + "overflow": "block", + }, + } + ], + ) + + compiled = compile_pipeline(accepted, "test", _registry()) + assert compiled.runtime.edges[0].capacity == 8 + + rejected = _config( + nodes, + [ + { + "from": "source.events", + "to": "operator.input", + "qos": { + "profile": "video_contiguous", + "capacity": 2, + "overflow": "drop_oldest", + }, + } + ], + ) + with pytest.raises(PipelineCompileError, match="does not allow"): + compile_pipeline(rejected, "test", _registry()) + + +def test_compile_rejects_direct_path_into_actuator() -> None: + config = _config( + { + "source": {"uses": "test.approved_source@1"}, + "actuator": {"uses": "test.actuator@1"}, + }, + [{"from": "source.commands", "to": "actuator.commands"}], + ) + + with pytest.raises(PipelineCompileError, match="must pass through a safety gate"): + compile_pipeline(config, "test", _registry()) + + +def test_compile_accepts_actuator_only_after_safety_gate() -> None: + config = _config( + { + "source": {"uses": "test.command_source@1"}, + "gate": {"uses": "test.safety_gate@1"}, + "actuator": {"uses": "test.actuator@1"}, + }, + [ + {"from": "source.commands", "to": "gate.input"}, + {"from": "gate.commands", "to": "actuator.commands"}, + ], + ) + + compiled = compile_pipeline(config, "test", _registry()) + + assert set(compiled.plugin_specs) == {"source", "gate", "actuator"} + + +def test_compile_rejects_mixed_safe_and_unsafe_actuator_paths() -> None: + config = _config( + { + "source": {"uses": "test.command_source@1"}, + "bypass": {"uses": "test.approved_source@1"}, + "gate": {"uses": "test.safety_gate@1"}, + "actuator": {"uses": "test.actuator@1"}, + }, + [ + {"from": "source.commands", "to": "gate.input"}, + {"from": "gate.commands", "to": "actuator.commands"}, + {"from": "bypass.commands", "to": "actuator.commands"}, + ], + ) + + with pytest.raises(PipelineCompileError, match="unsafe actuator"): + compile_pipeline(config, "test", _registry()) + + +def test_compile_rejects_operator_between_gate_and_actuator() -> None: + config = _config( + { + "source": {"uses": "test.command_source@1"}, + "gate": {"uses": "test.safety_gate@1"}, + "transform": {"uses": "test.approved_operator@1"}, + "actuator": {"uses": "test.actuator@1"}, + }, + [ + {"from": "source.commands", "to": "gate.input"}, + {"from": "gate.commands", "to": "transform.input"}, + {"from": "transform.commands", "to": "actuator.commands"}, + ], + ) + + with pytest.raises(PipelineCompileError, match="safety gate directly"): + compile_pipeline(config, "test", _registry()) + + +@pytest.mark.parametrize( + ("node_options", "message"), + [ + ({"execution": {"mode": "thread"}}, "execution settings not implemented"), + ({"resources": {"memory_mb": 256}}, "resource limits"), + ], +) +def test_compile_rejects_unenforced_node_settings( + node_options: dict[str, Any], message: str +) -> None: + config = _config( + { + "source": { + "uses": "test.text_source@1", + **node_options, + } + }, + [], + ) + + with pytest.raises(PipelineCompileError, match=message): + compile_pipeline(config, "test", _registry()) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("max_processes", 2), + ("process_start_method", "forkserver"), + ("health_bind", "127.0.0.1:8080"), + ("reserved_memory_mb", 256), + ], +) +def test_compile_rejects_reserved_runtime_fields(field: str, value: Any) -> None: + config = load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "runtime": {field: value}, + "pipelines": { + "test": {"nodes": {"source": {"uses": "test.text_source@1"}}} + }, + } + ) + + with pytest.raises(PipelineCompileError, match="reserved but not enforced"): + compile_pipeline(config, "test", _registry()) + + +def test_compile_rejects_pipeline_priority_without_scheduler() -> None: + config = load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "pipelines": { + "test": { + "priority": "high", + "nodes": {"source": {"uses": "test.text_source@1"}}, + } + }, + } + ) + + with pytest.raises(PipelineCompileError, match="no priority scheduler"): + compile_pipeline(config, "test", _registry()) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py new file mode 100644 index 0000000..962c2c9 --- /dev/null +++ b/tests/unit/test_config.py @@ -0,0 +1,145 @@ +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from cmvr_edge_ai.config import ( + MissingEnvironmentVariable, + load_config_data, + load_config_text, +) + + +def test_load_config_expands_environment_at_every_level() -> None: + config = load_config_text( + """ +api_version: cmvr.edge.ai/v1 +runtime: + thread_workers: ${EDGE_THREADS:-2} +endpoints: + robot: + transport: GRPC + target: env://CMVR_TARGET + platform: + transport: http + base_url: ${PLATFORM_URL:-http://127.0.0.1:8080} +pipelines: + detection: + nodes: + source: + uses: core.sequence_source@1 + with: + items: [env://SAMPLE_ITEM] +""", + environ={ + "CMVR_TARGET": "192.168.0.10:50052", + "EDGE_THREADS": "3", + "SAMPLE_ITEM": "frame-1", + }, + ) + + assert config.runtime.thread_workers == 3 + assert config.endpoints["robot"].transport == "grpc" + assert config.endpoints["robot"].target == "192.168.0.10:50052" + assert config.endpoints["platform"].base_url == "http://127.0.0.1:8080" + assert config.pipelines["detection"].nodes["source"].params["items"] == ["frame-1"] + + +def test_load_config_rejects_missing_required_environment_variable() -> None: + with pytest.raises(MissingEnvironmentVariable, match="CMVR_TARGET"): + load_config_text( + """ +api_version: cmvr.edge.ai/v1 +endpoints: + robot: + transport: grpc + target: ${CMVR_TARGET} +pipelines: + smoke: + nodes: + source: + uses: core.sequence_source@1 +""", + environ={}, + ) + + +def test_config_rejects_edges_to_unknown_nodes() -> None: + with pytest.raises(ValidationError, match="references unknown node"): + load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "pipelines": { + "invalid": { + "nodes": { + "source": {"uses": "test.source@1"}, + }, + "edges": [{"from": "source.output", "to": "missing.input"}], + } + }, + } + ) + + +def test_config_rejects_cycles_before_runtime_construction() -> None: + with pytest.raises(ValidationError, match="must be acyclic"): + load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "pipelines": { + "cyclic": { + "nodes": { + "first": {"uses": "test.operator@1"}, + "second": {"uses": "test.operator@1"}, + }, + "edges": [ + {"from": "first.output", "to": "second.input"}, + {"from": "second.output", "to": "first.input"}, + ], + } + }, + } + ) + + +def test_config_models_forbid_unknown_fields() -> None: + with pytest.raises(ValidationError, match="extra_forbidden"): + load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "unexpected": True, + "pipelines": { + "smoke": {"nodes": {"source": {"uses": "test.source@1"}}} + }, + } + ) + + +@pytest.mark.parametrize( + ("endpoint", "message"), + [ + ({"transport": "grpc"}, "gRPC endpoint requires target"), + ({"transport": "http"}, "HTTP endpoint requires base_url"), + ( + { + "transport": "http", + "base_url": "https://platform.example", + "tls": False, + }, + "HTTP endpoint must not set tls", + ), + ], +) +def test_builtin_endpoints_reject_incomplete_or_ambiguous_addresses( + endpoint: dict[str, object], message: str +) -> None: + with pytest.raises(ValidationError, match=message): + load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "endpoints": {"external": endpoint}, + "pipelines": { + "smoke": {"nodes": {"source": {"uses": "test.source@1"}}} + }, + } + ) diff --git a/tests/unit/test_detection_annotation.py b/tests/unit/test_detection_annotation.py new file mode 100644 index 0000000..e96a1d5 --- /dev/null +++ b/tests/unit/test_detection_annotation.py @@ -0,0 +1,169 @@ +from __future__ import annotations + +from io import BytesIO +from typing import Any + +import pytest + +from cmvr_edge_ai.contracts import BoundingBox, Detection, EncodedImage, ImageFrame +from cmvr_edge_ai.detection.annotation import render_annotated_jpeg + + +Image = pytest.importorskip("PIL.Image") +ImageDraw = pytest.importorskip("PIL.ImageDraw") + + +def _solid_frame( + *, + pixel_format: str, + rgb: tuple[int, int, int], + width: int = 64, + height: int = 48, +) -> ImageFrame: + stored_pixel = rgb if pixel_format == "RGB8" else tuple(reversed(rgb)) + return ImageFrame( + data=bytes(stored_pixel) * width * height, + width=width, + height=height, + pixel_format=pixel_format, + ) + + +def _detection( + label: str, + box: BoundingBox, + *, + confidence: float = 0.93, +) -> Detection: + return Detection(label=label, confidence=confidence, box=box) + + +def _record_drawing_calls( + monkeypatch: pytest.MonkeyPatch, +) -> tuple[list[tuple[float, float, float, float]], list[str]]: + rectangle_calls: list[tuple[float, float, float, float]] = [] + text_calls: list[str] = [] + original_rectangle = ImageDraw.ImageDraw.rectangle + original_text = ImageDraw.ImageDraw.text + + def recording_rectangle( + draw: ImageDraw.ImageDraw, + xy: Any, + *args: Any, + **kwargs: Any, + ) -> None: + coordinates = tuple(xy) + if len(coordinates) == 2 and all( + isinstance(point, (tuple, list)) for point in coordinates + ): + coordinates = tuple( + coordinate for point in coordinates for coordinate in point + ) + if len(coordinates) == 4: + rectangle_calls.append(tuple(float(value) for value in coordinates)) + original_rectangle(draw, xy, *args, **kwargs) + + def recording_text( + draw: ImageDraw.ImageDraw, + xy: Any, + text: str, + *args: Any, + **kwargs: Any, + ) -> None: + text_calls.append(text) + original_text(draw, xy, text, *args, **kwargs) + + monkeypatch.setattr(ImageDraw.ImageDraw, "rectangle", recording_rectangle) + monkeypatch.setattr(ImageDraw.ImageDraw, "text", recording_text) + return rectangle_calls, text_calls + + +@pytest.mark.parametrize("pixel_format", ["BGR8", "RGB8"]) +def test_rendered_alert_image_preserves_color_and_is_a_decodable_jpeg( + pixel_format: str, +) -> None: + expected_rgb = (24, 96, 184) + frame = _solid_frame(pixel_format=pixel_format, rgb=expected_rgb) + detection = _detection( + "No-Helmet", + BoundingBox(x_min=8.0, y_min=8.0, x_max=32.0, y_max=32.0), + ) + + encoded = render_annotated_jpeg(frame, (detection,), jpeg_quality=90) + + assert isinstance(encoded, EncodedImage) + assert encoded.media_type == "image/jpeg" + assert (encoded.width, encoded.height) == (frame.width, frame.height) + assert encoded.data.startswith(b"\xff\xd8") + assert encoded.data.endswith(b"\xff\xd9") + + with Image.open(BytesIO(encoded.data)) as decoded: + assert decoded.format == "JPEG" + assert decoded.size == (frame.width, frame.height) + rgb_image = decoded.convert("RGB") + + # Sample well away from the box and its label. JPEG is lossy, so compare + # with a tolerance while still catching an RGB/BGR channel swap. + assert rgb_image.getpixel((frame.width - 3, frame.height - 3)) == pytest.approx( + expected_rgb, + abs=12, + ) + + +def test_renderer_draws_each_detection_box_and_label( + monkeypatch: pytest.MonkeyPatch, +) -> None: + rectangle_calls, text_calls = _record_drawing_calls(monkeypatch) + + frame = _solid_frame(pixel_format="BGR8", rgb=(0, 0, 0)) + detections = ( + _detection( + "No-Helmet", + BoundingBox(x_min=5.0, y_min=10.0, x_max=30.0, y_max=35.0), + ), + _detection( + "No-Vest", + BoundingBox(x_min=34.0, y_min=12.0, x_max=58.0, y_max=40.0), + confidence=0.81, + ), + ) + + render_annotated_jpeg(frame, detections, jpeg_quality=85) + + assert (5.0, 10.0, 30.0, 35.0) in rectangle_calls + assert (34.0, 12.0, 58.0, 40.0) in rectangle_calls + assert any("No-Helmet" in text for text in text_calls) + assert any("No-Vest" in text for text in text_calls) + + +def test_renderer_clips_out_of_bounds_box_and_skips_invalid_boxes( + monkeypatch: pytest.MonkeyPatch, +) -> None: + rectangle_calls, text_calls = _record_drawing_calls(monkeypatch) + frame = _solid_frame(pixel_format="RGB8", rgb=(0, 0, 0)) + detections = ( + _detection( + "Clipped", + BoundingBox(x_min=-5.0, y_min=-10.0, x_max=80.0, y_max=60.0), + ), + _detection( + "Reversed", + BoundingBox(x_min=30.0, y_min=30.0, x_max=10.0, y_max=10.0), + ), + _detection( + "Outside", + BoundingBox(x_min=70.0, y_min=5.0, x_max=90.0, y_max=20.0), + ), + _detection( + "Not-Finite", + BoundingBox(x_min=float("nan"), y_min=1.0, x_max=10.0, y_max=10.0), + ), + ) + + render_annotated_jpeg(frame, detections, jpeg_quality=85) + + assert (0.0, 0.0, 63.0, 47.0) in rectangle_calls + assert any("Clipped" in text for text in text_calls) + assert all("Reversed" not in text for text in text_calls) + assert all("Outside" not in text for text in text_calls) + assert all("Not-Finite" not in text for text in text_calls) diff --git a/tests/unit/test_detection_compile.py b/tests/unit/test_detection_compile.py new file mode 100644 index 0000000..38cbe15 --- /dev/null +++ b/tests/unit/test_detection_compile.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import pytest + +from cmvr_edge_ai.application import create_default_registry +from cmvr_edge_ai.compiler import PipelineCompileError, compile_pipeline +from cmvr_edge_ai.config import load_config_data + + +def _config( + rule_label: str, + *, + attach_frame: bool = False, + alert_image: bool = False, +): # type: ignore[no-untyped-def] + return load_config_data( + { + "api_version": "cmvr.edge.ai/v1", + "pipelines": { + "detection": { + "nodes": { + "source": { + "uses": "core.sequence_source@1", + "with": { + "items": [], + "schema_name": "ImageFrame", + }, + }, + "detector": { + "uses": "detection.model@1", + "with": { + "model": "construction-ppe-yolov8@1", + "detect_labels": ["No-Helmet"], + "attach_frame": attach_frame, + "model_options": {"weights": "/not-loaded.pt"}, + }, + }, + "gate": { + "uses": "detection.repeat_gate@1", + "with": { + "alert_image": {"enabled": alert_image}, + "rules": [ + { + "id": "ppe-rule", + "labels": [rule_label], + "min_hits": 2, + "window_ms": 1000, + } + ] + }, + }, + }, + "edges": [ + {"from": "source.output", "to": "detector.frames"}, + { + "from": "detector.detections", + "to": "gate.detections", + }, + ], + } + }, + } + ) + + +def test_compile_accepts_rule_label_selected_by_detector() -> None: + compiled = compile_pipeline( + _config("No-Helmet"), + "detection", + create_default_registry(discover_entry_points=False), + ) + + assert set(compiled.plugin_specs) == {"source", "detector", "gate"} + + +def test_compile_rejects_rule_label_ignored_by_detector() -> None: + with pytest.raises(PipelineCompileError, match="No-Vest"): + compile_pipeline( + _config("No-Vest"), + "detection", + create_default_registry(discover_entry_points=False), + ) + + +def test_compile_accepts_alert_images_when_detector_attaches_frames() -> None: + compiled = compile_pipeline( + _config("No-Helmet", attach_frame=True, alert_image=True), + "detection", + create_default_registry(discover_entry_points=False), + ) + + assert compiled.runtime.nodes["detector"].attach_frame is True + assert compiled.runtime.nodes["gate"].alert_image_enabled is True + + +def test_compile_rejects_alert_images_without_attached_frames() -> None: + with pytest.raises( + PipelineCompileError, + match="enables alert_image.*attach_frame=true", + ): + compile_pipeline( + _config("No-Helmet", attach_frame=False, alert_image=True), + "detection", + create_default_registry(discover_entry_points=False), + ) diff --git a/tests/unit/test_detection_operator.py b/tests/unit/test_detection_operator.py new file mode 100644 index 0000000..f7d88f6 --- /dev/null +++ b/tests/unit/test_detection_operator.py @@ -0,0 +1,442 @@ +from __future__ import annotations + +import asyncio +import logging +import threading +from collections.abc import Awaitable, Callable, Mapping, Sequence +from concurrent.futures import ThreadPoolExecutor +from typing import Any, TypeVar + +import pytest + +from cmvr_edge_ai.contracts import BoundingBox, Detection, DetectionResult, ImageFrame +from cmvr_edge_ai.core.component import ComponentContext, Emission +from cmvr_edge_ai.core.envelope import Envelope +from cmvr_edge_ai.detection.base import DetectionModel +from cmvr_edge_ai.detection.operator import DetectionOperator +from cmvr_edge_ai.detection.registry import ( + DetectionModelRegistry, + DetectionModelSpec, + UnknownDetectionModelError, +) + + +ResultT = TypeVar("ResultT") + + +class RecordingDetectionModel(DetectionModel): + def __init__(self, detections: Sequence[Detection] = ()) -> None: + self.detections = tuple(detections) + self.load_calls = 0 + self.predict_calls = 0 + self.close_calls = 0 + self.predict_arguments: list[tuple[ImageFrame, tuple[str, ...], float]] = [] + self.call_threads: dict[str, list[int]] = { + "load": [], + "predict": [], + "close": [], + } + + def load(self) -> None: + self.load_calls += 1 + self.call_threads["load"].append(threading.get_ident()) + + def predict( + self, + frame: ImageFrame, + labels: tuple[str, ...], + confidence: float, + ) -> Sequence[Detection]: + self.predict_calls += 1 + self.predict_arguments.append((frame, labels, confidence)) + self.call_threads["predict"].append(threading.get_ident()) + return self.detections + + def close(self) -> None: + self.close_calls += 1 + self.call_threads["close"].append(threading.get_ident()) + + +def _detection(label: str, confidence: float) -> Detection: + return Detection( + label=label, + confidence=confidence, + box=BoundingBox(x_min=1.0, y_min=2.0, x_max=11.0, y_max=22.0), + ) + + +def _frame(*, codec: str = "none") -> ImageFrame: + return ImageFrame( + data=bytes(range(12)), + width=2, + height=2, + pixel_format="BGR8", + codec=codec, + is_key_frame=codec != "none", + ) + + +def _envelope(frame: ImageFrame | None = None) -> Envelope[ImageFrame]: + return Envelope( + payload=_frame() if frame is None else frame, + schema_name="ImageFrame", + schema_version=1, + source_id="camera-right", + sequence=42, + captured_at_ns=1_234_000_000, + received_at_ns=1_235_000_000, + deadline_ns=1_500_000_000, + trace_id="trace-detection-42", + session_id="camera-session", + attributes={"site": "park-a"}, + ) + + +def _registry( + model: RecordingDetectionModel, + *, + labels: tuple[str, ...] = ("Worker", "No-Helmet", "No-Vest"), + factory_options: list[dict[str, Any]] | None = None, +) -> DetectionModelRegistry: + def factory(options: Mapping[str, Any]) -> DetectionModel: + if factory_options is not None: + factory_options.append(dict(options)) + return model + + registry = DetectionModelRegistry() + registry.register( + DetectionModelSpec( + model_id="construction-ppe@1", + name="Construction PPE", + supported_labels=labels, + factory=factory, + backend="fake", + ) + ) + return registry + + +def _context(executor: ThreadPoolExecutor) -> ComponentContext: + return ComponentContext( + pipeline_id="detection-test", + node_id="detector", + shutdown_event=asyncio.Event(), + metadata={"thread_executor": executor}, + ) + + +async def _keep_restricted_event_loop_responsive() -> None: + """Provide wakeups where sandboxed self-pipe notifications are unavailable.""" + + while True: + await asyncio.sleep(0.01) + + +def _run_with_thread_executor( + exercise: Callable[[ThreadPoolExecutor], Awaitable[ResultT]], +) -> ResultT: + async def runner() -> ResultT: + loop = asyncio.get_running_loop() + executor = ThreadPoolExecutor(max_workers=1) + loop.set_default_executor(executor) + ticker = asyncio.create_task(_keep_restricted_event_loop_responsive()) + try: + return await exercise(executor) + finally: + ticker.cancel() + await asyncio.gather(ticker, return_exceptions=True) + executor.shutdown(wait=True, cancel_futures=True) + # asyncio.run() otherwise tries to shut down the same executor again. + loop._default_executor = None # type: ignore[attr-defined] + + return asyncio.run(runner()) + + +def test_operator_loads_once_predicts_and_closes_idempotently() -> None: + model = RecordingDetectionModel([_detection("Worker", 0.9)]) + options_seen: list[dict[str, Any]] = [] + operator = DetectionOperator( + "detector", + { + "model": "construction-ppe@1", + "model_options": {"weights": "/models/ppe.pt", "device": "cpu"}, + }, + model_registry=_registry(model, factory_options=options_seen), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + result = await operator.process(_envelope()) + await operator.stop() + await operator.stop() + + assert isinstance(result, Emission) + assert model.load_calls == 1 + assert model.predict_calls == 1 + assert model.close_calls == 1 + assert options_seen == [{"weights": "/models/ppe.pt", "device": "cpu"}] + + _run_with_thread_executor(scenario) + + +def test_operator_filters_selected_labels_and_per_label_confidence() -> None: + selected_worker = _detection("Worker", 0.50) + selected_violation = _detection("No-Helmet", 0.80) + model = RecordingDetectionModel( + [ + _detection("Worker", 0.49), + selected_worker, + _detection("No-Helmet", 0.79), + selected_violation, + _detection("No-Vest", 0.99), + ] + ) + operator = DetectionOperator( + "detector", + { + "model": "construction-ppe@1", + "detect_labels": ["Worker", "No-Helmet"], + "confidence": 0.50, + "label_confidence": {"No-Helmet": 0.80}, + }, + model_registry=_registry(model), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + emission = await operator.process(_envelope()) + await operator.stop() + + assert isinstance(emission, Emission) + payload = emission.envelope.payload + assert isinstance(payload, DetectionResult) + assert payload.detections == (selected_worker, selected_violation) + assert operator.selected_labels == ("Worker", "No-Helmet") + assert model.predict_arguments[0][1:] == (("Worker", "No-Helmet"), 0.50) + + _run_with_thread_executor(scenario) + + +def test_operator_rejects_unknown_model_during_construction() -> None: + with pytest.raises(UnknownDetectionModelError, match="missing@1"): + DetectionOperator( + "detector", + {"model": "missing@1"}, + model_registry=DetectionModelRegistry(), + ) + + +@pytest.mark.parametrize( + "params", + [ + { + "model": "construction-ppe@1", + "detect_labels": ["Unknown-Label"], + }, + { + "model": "construction-ppe@1", + "label_confidence": {"Unknown-Label": 0.9}, + }, + ], +) +def test_operator_rejects_unknown_labels_during_construction( + params: dict[str, Any], +) -> None: + with pytest.raises(ValueError, match="Unknown-Label"): + DetectionOperator( + "detector", + params, + model_registry=_registry(RecordingDetectionModel()), + ) + + +def test_operator_rejects_encoded_frame_without_calling_model() -> None: + model = RecordingDetectionModel() + operator = DetectionOperator( + "detector", + {"model": "construction-ppe@1"}, + model_registry=_registry(model), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + with pytest.raises(ValueError, match="decoded ImageFrame"): + await operator.process(_envelope(_frame(codec="h264"))) + await operator.stop() + + assert model.predict_calls == 0 + + _run_with_thread_executor(scenario) + + +def test_operator_emits_result_metadata_and_preserves_envelope_correlation() -> None: + source = _envelope() + expected_detection = _detection("No-Helmet", 0.93) + model = RecordingDetectionModel([expected_detection]) + operator = DetectionOperator( + "detector", + {"model": "construction-ppe@1"}, + model_registry=_registry(model), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + emission = await operator.process(source) + await operator.stop() + + assert isinstance(emission, Emission) + assert emission.port == "detections" + output = emission.envelope + assert output.schema == "DetectionResult/v1" + assert isinstance(output.payload, DetectionResult) + assert output.payload.detections == (expected_detection,) + assert output.payload.model_id == "construction-ppe@1" + assert output.payload.model_name == "Construction PPE" + assert output.payload.inference_ms >= 0.0 + assert output.payload.source_frame is None + assert operator.attach_frame is False + assert output.source_id == source.source_id + assert output.sequence == source.sequence + assert output.captured_at_ns == source.captured_at_ns + assert output.received_at_ns == source.received_at_ns + assert output.deadline_ns == source.deadline_ns + assert output.trace_id == source.trace_id + assert output.session_id == source.session_id + assert output.attributes == { + "site": "park-a", + "detection_model_id": "construction-ppe@1", + "detection_model_name": "Construction PPE", + } + + _run_with_thread_executor(scenario) + + +def test_operator_attach_frame_keeps_the_decoded_frame_by_reference() -> None: + source = _envelope() + model = RecordingDetectionModel([_detection("No-Helmet", 0.93)]) + operator = DetectionOperator( + "detector", + {"model": "construction-ppe@1", "attach_frame": True}, + model_registry=_registry(model), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + emission = await operator.process(source) + await operator.stop() + + assert isinstance(emission, Emission) + assert isinstance(emission.envelope.payload, DetectionResult) + assert emission.envelope.payload.source_frame is source.payload + assert operator.attach_frame is True + + _run_with_thread_executor(scenario) + + +def test_blocking_model_methods_run_on_the_supplied_thread_pool() -> None: + event_loop_thread = threading.get_ident() + model = RecordingDetectionModel() + operator = DetectionOperator( + "detector", + {"model": "construction-ppe@1"}, + model_registry=_registry(model), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + await operator.process(_envelope()) + await operator.stop() + + assert all(len(thread_ids) == 1 for thread_ids in model.call_threads.values()) + worker_threads = { + thread_id + for thread_ids in model.call_threads.values() + for thread_id in thread_ids + } + assert len(worker_threads) == 1 + assert event_loop_thread not in worker_threads + + _run_with_thread_executor(scenario) + + +def test_max_fps_skips_frames_without_invoking_model_again() -> None: + model = RecordingDetectionModel() + operator = DetectionOperator( + "detector", + {"model": "construction-ppe@1", "max_fps": 0.001}, + model_registry=_registry(model), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + first = await operator.process(_envelope()) + second = await operator.process(_envelope()) + await operator.stop() + + assert isinstance(first, Emission) + assert second is None + assert model.predict_calls == 1 + + _run_with_thread_executor(scenario) + + +def test_operator_logs_model_load_and_configured_inference_heartbeat( + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + timestamps = iter((0, 1_000_000_000, 6_000_000_000)) + monkeypatch.setattr( + "cmvr_edge_ai.detection.operator.monotonic_ns", + lambda: next(timestamps), + ) + model = RecordingDetectionModel([_detection("No-Helmet", 0.93)]) + operator = DetectionOperator( + "detector", + { + "model": "construction-ppe@1", + "detect_labels": ["No-Helmet"], + "inference_log_interval_s": 5, + }, + model_registry=_registry(model), + ) + + async def scenario(executor: ThreadPoolExecutor) -> None: + await operator.setup(_context(executor)) + await operator.process(_envelope()) + await operator.process(_envelope()) + await operator.process(_envelope()) + await operator.stop() + + with caplog.at_level(logging.INFO, logger="cmvr_edge_ai.detection.operator"): + _run_with_thread_executor(scenario) + + messages = [record.getMessage() for record in caplog.records] + assert any( + "detection model loaded node=detector model=construction-ppe@1" in message + for message in messages + ) + inference_messages = [ + message for message in messages if message.startswith("detection inference ") + ] + assert len(inference_messages) == 2 + assert "total_frames=1" in inference_messages[0] + assert "window_frames=1" in inference_messages[0] + assert "window_detections=1" in inference_messages[0] + assert "hit_labels=No-Helmet:1" in inference_messages[0] + assert "total_frames=3" in inference_messages[1] + assert "window_frames=2" in inference_messages[1] + assert "window_detections=2" in inference_messages[1] + assert "hit_labels=No-Helmet:2" in inference_messages[1] + + +@pytest.mark.parametrize("value", [0, -1, 3600.1, True]) +def test_operator_rejects_invalid_inference_log_interval(value: Any) -> None: + with pytest.raises(ValueError, match="inference_log_interval_s"): + DetectionOperator( + "detector", + { + "model": "construction-ppe@1", + "inference_log_interval_s": value, + }, + model_registry=_registry(RecordingDetectionModel()), + ) diff --git a/tests/unit/test_detection_registry.py b/tests/unit/test_detection_registry.py new file mode 100644 index 0000000..5ad32a5 --- /dev/null +++ b/tests/unit/test_detection_registry.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from typing import Any + +import pytest + +from cmvr_edge_ai.contracts import Detection, ImageFrame +from cmvr_edge_ai.detection.base import DetectionModel +from cmvr_edge_ai.detection.registry import ( + DetectionModelRegistry, + DetectionModelRegistryError, + DetectionModelSpec, + DuplicateDetectionModelError, + UnknownDetectionModelError, +) + + +class FakeDetectionModel(DetectionModel): + def load(self) -> None: + pass + + def predict( + self, + frame: ImageFrame, + labels: tuple[str, ...], + confidence: float, + ) -> Sequence[Detection]: + del frame, labels, confidence + return () + + def close(self) -> None: + pass + + +def _factory(params: Mapping[str, Any]) -> DetectionModel: + del params + return FakeDetectionModel() + + +def _spec(model_id: str = "ppe-yolo@1") -> DetectionModelSpec: + return DetectionModelSpec( + model_id=model_id, + name="Construction PPE", + supported_labels=("Worker", "No-Helmet", "No-Vest"), + factory=_factory, + backend="ultralytics", + description="PPE violation detector", + ) + + +def test_detection_model_spec_preserves_order_and_copies_labels() -> None: + labels = ["Worker", "No-Helmet", "No-Vest"] + spec = DetectionModelSpec( + model_id="ppe@2", + name=" PPE detector ", + supported_labels=labels, # type: ignore[arg-type] + factory=_factory, + backend=" yolo ", + ) + labels.append("No-Glove") + + assert spec.name == "PPE detector" + assert spec.backend == "yolo" + assert spec.supported_labels == ("Worker", "No-Helmet", "No-Vest") + + +@pytest.mark.parametrize("model_id", ["ppe", "@1", "ppe@0", "ppe@-1", "ppe@v1"]) +def test_detection_model_spec_requires_positive_version(model_id: str) -> None: + with pytest.raises(ValueError, match="model id"): + DetectionModelSpec( + model_id=model_id, + name="PPE", + supported_labels=("Worker",), + factory=_factory, + backend="fake", + ) + + +@pytest.mark.parametrize( + ("overrides", "exception", "message"), + [ + ({"name": " "}, ValueError, "model name"), + ({"backend": ""}, ValueError, "model backend"), + ({"supported_labels": ()}, ValueError, "must not be empty"), + ({"supported_labels": ("Worker", "")}, ValueError, "non-empty strings"), + ( + {"supported_labels": ("Worker", "Worker")}, + ValueError, + "must be unique", + ), + ({"supported_labels": "Worker"}, TypeError, "ordered collection"), + ({"supported_labels": {"Worker"}}, TypeError, "ordered collection"), + ({"factory": None}, TypeError, "factory must be callable"), + ], +) +def test_detection_model_spec_rejects_invalid_metadata( + overrides: dict[str, Any], exception: type[Exception], message: str +) -> None: + values: dict[str, Any] = { + "model_id": "ppe@1", + "name": "PPE", + "supported_labels": ("Worker",), + "factory": _factory, + "backend": "fake", + } + values.update(overrides) + + with pytest.raises(exception, match=message): + DetectionModelSpec(**values) + + +def test_registry_registers_resolves_and_sorts_specs() -> None: + registry = DetectionModelRegistry() + second = registry.register(_spec("z-model@1")) + first = registry.register(_spec("a-model@2")) + + assert registry.resolve("z-model@1") is second + assert registry.specs() == (first, second) + + +def test_registry_rejects_duplicate_and_reports_available_models() -> None: + registry = DetectionModelRegistry() + registry.register(_spec("ppe@1")) + + with pytest.raises(DuplicateDetectionModelError, match="ppe@1"): + registry.register(_spec("ppe@1")) + with pytest.raises(UnknownDetectionModelError, match=r"available models: ppe@1"): + registry.resolve("missing@1") + + +@dataclass +class _EntryPoint: + name: str + value: Any + + def load(self) -> Any: + return self.value + + +class _EntryPoints(list[_EntryPoint]): + def select(self, *, group: str) -> _EntryPoints: + assert group == DetectionModelRegistry.ENTRY_POINT_GROUP + return self + + +def test_registry_discovers_specs_and_registration_callbacks(monkeypatch) -> None: # type: ignore[no-untyped-def] + def register_callback(registry: DetectionModelRegistry) -> None: + registry.register(_spec("callback@1")) + + entry_points = _EntryPoints( + [ + _EntryPoint("spec", _spec("spec@1")), + _EntryPoint("callback", register_callback), + ] + ) + monkeypatch.setattr( + "cmvr_edge_ai.detection.registry.metadata.entry_points", + lambda: entry_points, + ) + registry = DetectionModelRegistry() + + registry.load_entry_points() + + assert [spec.model_id for spec in registry.specs()] == ["callback@1", "spec@1"] + + +def test_registry_rejects_invalid_entry_point(monkeypatch) -> None: # type: ignore[no-untyped-def] + entry_points = _EntryPoints([_EntryPoint("invalid", object())]) + monkeypatch.setattr( + "cmvr_edge_ai.detection.registry.metadata.entry_points", + lambda: entry_points, + ) + + with pytest.raises(DetectionModelRegistryError, match="invalid"): + DetectionModelRegistry().load_entry_points() diff --git a/tests/unit/test_detection_trigger.py b/tests/unit/test_detection_trigger.py new file mode 100644 index 0000000..8b7a6be --- /dev/null +++ b/tests/unit/test_detection_trigger.py @@ -0,0 +1,692 @@ +from __future__ import annotations + +import asyncio +from dataclasses import asdict +import json +import threading +from concurrent.futures import ThreadPoolExecutor +from typing import Any + +import pytest + +from cmvr_edge_ai.contracts.events import ( + BoundingBox, + Detection, + DetectionAlert, + DetectionResult, +) +from cmvr_edge_ai.contracts.media import EncodedImage, ImageFrame +from cmvr_edge_ai.core import ComponentContext, Emission, Envelope +import cmvr_edge_ai.detection.rules as detection_rules +from cmvr_edge_ai.detection.annotation import ImageAnnotationDependencyError +from cmvr_edge_ai.detection.rules import DetectionTriggerOperator + + +NS_PER_MS = 1_000_000 + + +def _frame(value: int = 0) -> ImageFrame: + return ImageFrame(bytes([value]) * 4 * 4 * 3, 4, 4, "BGR8") + + +async def _keep_restricted_event_loop_responsive() -> None: + while True: + await asyncio.sleep(0.01) + + +def _detection( + label: str = "No-Helmet", + confidence: float = 0.9, + *, + track_id: str | None = None, +) -> Detection: + return Detection( + label=label, + confidence=confidence, + box=BoundingBox(1.0, 2.0, 30.0, 40.0), + track_id=track_id, + ) + + +def _envelope( + sequence: int, + at_ms: float, + *detections: Detection, + source_id: str = "camera-a", + captured: bool = False, + trace_id: str | None = None, + frame: ImageFrame | None = None, +) -> Envelope[DetectionResult]: + timestamp_ns = int(at_ms * NS_PER_MS) + kwargs: dict[str, Any] = { + "payload": DetectionResult( + detections=tuple(detections), + model_id="construction-ppe-yolov8@1", + inference_ms=12.5, + model_name="Construction PPE YOLOv8", + source_frame=frame, + ), + "schema_name": "DetectionResult", + "schema_version": 1, + "source_id": source_id, + "sequence": sequence, + "received_at_ns": timestamp_ns, + "captured_at_ns": timestamp_ns if captured else None, + } + if trace_id is not None: + kwargs["trace_id"] = trace_id + return Envelope(**kwargs) + + +def _operator( + *, + labels: list[str] | None = None, + min_hits: int = 3, + window_ms: float = 1_000, + cooldown_ms: float = 0, + min_confidence: float = 0.5, + scope: str = "source", + time_source: str = "received", + alert_image: dict[str, Any] | None = None, +) -> DetectionTriggerOperator: + params: dict[str, Any] = { + "time_source": time_source, + "rules": [ + { + "id": "missing-ppe", + "labels": labels or ["No-Helmet"], + "min_hits": min_hits, + "window_ms": window_ms, + "cooldown_ms": cooldown_ms, + "min_confidence": min_confidence, + "scope": scope, + } + ], + } + if alert_image is not None: + params["alert_image"] = alert_image + return DetectionTriggerOperator("trigger", params) + + +def _process( + operator: DetectionTriggerOperator, envelope: Envelope[Any] +) -> tuple[Emission, ...] | None: + return asyncio.run(operator.process(envelope)) + + +def _single_alert( + result: tuple[Emission, ...] | None +) -> tuple[Emission, DetectionAlert]: + assert result is not None + assert len(result) == 1 + emission = result[0] + assert isinstance(emission.envelope.payload, DetectionAlert) + return emission, emission.envelope.payload + + +@pytest.mark.parametrize( + ("params", "message"), + [ + ({"rules": [], "unexpected": True}, "unknown detection trigger"), + ({"rules": []}, "non-empty list"), + ( + {"rules": [], "alert_image": "enabled"}, + "alert_image must be a mapping", + ), + ( + {"rules": [], "alert_image": {"unexpected": True}}, + "alert_image has unknown field", + ), + ( + {"rules": [], "alert_image": {"enabled": "true"}}, + "alert_image.enabled must be a boolean", + ), + ( + {"rules": [], "alert_image": {"jpeg_quality": True}}, + "alert_image.jpeg_quality must be an integer", + ), + ( + {"rules": [], "alert_image": {"jpeg_quality": 0}}, + "alert_image.jpeg_quality must be between 1 and 95", + ), + ( + {"rules": [], "alert_image": {"jpeg_quality": 96}}, + "alert_image.jpeg_quality must be between 1 and 95", + ), + ( + { + "rules": [ + { + "id": "r", + "labels": ["No-Helmet"], + "min_hits": 2, + "window_ms": 100, + "typo": True, + } + ] + }, + "unknown field", + ), + ( + { + "rules": [ + { + "id": "r", + "labels": ["No-Helmet", "No-Helmet"], + "min_hits": 2, + "window_ms": 100, + } + ] + }, + "duplicates", + ), + ( + { + "rules": [ + { + "id": "r", + "labels": ["No-Helmet"], + "min_hits": True, + "window_ms": 100, + } + ] + }, + "must be an integer", + ), + ( + { + "time_source": "wall", + "rules": [ + { + "id": "r", + "labels": ["No-Helmet"], + "min_hits": 2, + "window_ms": 100, + } + ], + }, + "time_source", + ), + ], +) +def test_trigger_configuration_is_strict(params: dict[str, Any], message: str) -> None: + with pytest.raises(ValueError, match=message): + DetectionTriggerOperator("trigger", params) + + +def test_source_rule_counts_distinct_frames_not_boxes_and_preserves_metadata() -> None: + operator = _operator(min_hits=3, window_ms=1_000, min_confidence=0.7) + + first = _envelope( + 1, + 0, + _detection(confidence=0.8), + _detection(confidence=0.91), + _detection("No-Vest", confidence=0.99), + ) + assert _process(operator, first) is None + # Replaying a frame with the same source and sequence must not create a hit. + assert _process(operator, _envelope(1, 100, _detection(confidence=0.99))) is None + # Below-threshold detections do not count. + assert _process(operator, _envelope(2, 500, _detection(confidence=0.69))) is None + assert _process(operator, _envelope(2, 500, _detection(confidence=0.85))) is None + + threshold = _envelope( + 3, + 1_000, + _detection(confidence=0.95), + trace_id="threshold-frame", + ) + emission, alert = _single_alert(_process(operator, threshold)) + + assert emission.port == "alerts" + assert emission.envelope.schema == "DetectionAlert/v1" + assert emission.envelope.trace_id == "threshold-frame" + assert emission.envelope.source_id == "camera-a" + assert alert.rule_id == "missing-ppe" + assert alert.model_id == "construction-ppe-yolov8@1" + assert alert.model_name == "Construction PPE YOLOv8" + assert alert.labels == ("No-Helmet",) + assert alert.scope == "source" + assert alert.scope_id == "camera-a" + assert alert.hit_count == 3 + assert alert.window_ms == 1_000 + assert alert.first_seen_ns == 0 + assert alert.last_seen_ns == 1_000 * NS_PER_MS + assert alert.triggered_at_ns == 1_000 * NS_PER_MS + assert alert.max_confidence == pytest.approx(0.95) + assert alert.detections == threshold.payload.detections + assert alert.event_id + + +def test_invalid_detections_are_counted_and_never_reach_an_alert_payload() -> None: + operator = _operator(min_hits=1) + invalid_nan = Detection( + "No-Helmet", + 0.99, + BoundingBox(float("nan"), 2.0, 30.0, 40.0), + ) + invalid_reversed = Detection( + "No-Helmet", + 0.98, + BoundingBox(30.0, 40.0, 1.0, 2.0), + ) + valid = _detection(confidence=0.95) + + assert _process(operator, _envelope(1, 0, invalid_nan, invalid_reversed)) is None + _, alert = _single_alert( + _process( + operator, + _envelope(2, 10, invalid_nan, invalid_reversed, valid), + ) + ) + + assert alert.detections == (valid,) + # The remaining structured alert can be emitted by a strict JSON client. + json.dumps(asdict(alert), allow_nan=False) + health = asyncio.run(operator.health()) + assert health.healthy is False + assert "invalid_detections=4" in health.detail + + +def test_window_lower_boundary_is_inclusive_and_older_hits_expire() -> None: + inclusive = _operator(min_hits=2, window_ms=1_000) + assert _process(inclusive, _envelope(1, 0, _detection())) is None + _, alert = _single_alert(_process(inclusive, _envelope(2, 1_000, _detection()))) + assert alert.first_seen_ns == 0 + + expired = _operator(min_hits=2, window_ms=1_000) + assert _process(expired, _envelope(1, 0, _detection())) is None + assert _process(expired, _envelope(2, 1_001, _detection())) is None + + +def test_cooldown_does_not_accumulate_hits_and_requires_a_fresh_window() -> None: + operator = _operator(min_hits=2, window_ms=1_000, cooldown_ms=500) + assert _process(operator, _envelope(1, 0, _detection())) is None + _single_alert(_process(operator, _envelope(2, 100, _detection()))) + + # The threshold frame itself remains deduplicated even after the hit deque + # was reset, and new frames during cooldown are ignored rather than queued. + assert _process(operator, _envelope(2, 100, _detection())) is None + assert _process(operator, _envelope(3, 200, _detection())) is None + assert _process(operator, _envelope(4, 599, _detection())) is None + assert _process(operator, _envelope(5, 600, _detection())) is None + _, alert = _single_alert(_process(operator, _envelope(6, 700, _detection()))) + assert alert.first_seen_ns == 600 * NS_PER_MS + assert alert.last_seen_ns == 700 * NS_PER_MS + + health = asyncio.run(operator.health()) + assert health.healthy is True + assert "alerts_emitted=2" in health.detail + assert "cooldown_suppressed=2" in health.detail + assert "duplicate_frames=1" in health.detail + + +def test_source_state_is_isolated_per_camera() -> None: + operator = _operator(min_hits=2) + assert ( + _process(operator, _envelope(1, 0, _detection(), source_id="camera-a")) is None + ) + assert ( + _process(operator, _envelope(1, 10, _detection(), source_id="camera-b")) is None + ) + + _, alert = _single_alert( + _process(operator, _envelope(2, 20, _detection(), source_id="camera-a")) + ) + assert alert.scope_id == "camera-a" + + +def test_track_rules_are_isolated_and_can_emit_multiple_alerts_per_frame() -> None: + operator = _operator(min_hits=2, scope="track") + first = _envelope( + 1, + 0, + _detection(track_id="worker-1"), + _detection(confidence=0.95, track_id="worker-1"), + _detection(track_id="worker-2"), + _detection(track_id=None), + ) + assert _process(operator, first) is None + + result = _process( + operator, + _envelope( + 2, + 100, + _detection(track_id="worker-1"), + _detection(track_id="worker-2"), + ), + ) + assert result is not None + assert {item.envelope.payload.scope_id for item in result} == { + "worker-1", + "worker-2", + } + assert all(item.envelope.payload.hit_count == 2 for item in result) + assert all(item.envelope.payload.scope == "track" for item in result) + assert "untracked_detections=1" in asyncio.run(operator.health()).detail + + +def test_multiple_rules_can_trigger_from_one_result() -> None: + operator = DetectionTriggerOperator( + "trigger", + { + "time_source": "received", + "rules": [ + { + "id": "helmet", + "labels": ["No-Helmet"], + "min_hits": 1, + "window_ms": 100, + "cooldown_ms": 0, + "min_confidence": 0.5, + "scope": "source", + }, + { + "id": "vest", + "labels": ["No-Vest"], + "min_hits": 1, + "window_ms": 100, + "cooldown_ms": 0, + "min_confidence": 0.5, + "scope": "source", + }, + ], + }, + ) + result = _process( + operator, + _envelope(1, 0, _detection("No-Helmet"), _detection("No-Vest")), + ) + + assert result is not None + assert [item.envelope.payload.rule_id for item in result] == ["helmet", "vest"] + + +def test_alert_image_is_encoded_once_only_when_the_threshold_frame_triggers( + monkeypatch: pytest.MonkeyPatch, +) -> None: + render_calls: list[ + tuple[int, ImageFrame, tuple[Detection, ...], int] + ] = [] + encoded = EncodedImage(b"jpeg", "image/jpeg", 4, 4) + + def render( + frame: ImageFrame, + detections: tuple[Detection, ...], + *, + jpeg_quality: int, + ) -> EncodedImage: + render_calls.append( + (threading.get_ident(), frame, tuple(detections), jpeg_quality) + ) + return encoded + + monkeypatch.setattr(detection_rules, "ensure_annotation_support", lambda: None) + monkeypatch.setattr(detection_rules, "render_annotated_jpeg", render) + operator = _operator( + min_hits=2, + alert_image={"enabled": True, "jpeg_quality": 73}, + ) + empty_frame = _frame(1) + first_hit_frame = _frame(2) + threshold_frame = _frame(3) + threshold_detection = _detection(confidence=0.96) + + async def scenario() -> tuple[Emission, ...] | None: + event_loop_thread = threading.get_ident() + executor = ThreadPoolExecutor(max_workers=1) + ticker = asyncio.create_task(_keep_restricted_event_loop_responsive()) + try: + await operator.setup( + ComponentContext( + pipeline_id="detection", + node_id="trigger", + shutdown_event=asyncio.Event(), + metadata={"thread_executor": executor}, + ) + ) + assert ( + await operator.process(_envelope(1, 0, frame=empty_frame)) is None + ) + assert ( + await operator.process( + _envelope(2, 100, _detection(), frame=first_hit_frame) + ) + is None + ) + assert render_calls == [] + result = await operator.process( + _envelope( + 3, + 200, + threshold_detection, + frame=threshold_frame, + ) + ) + assert render_calls[0][0] != event_loop_thread + return result + finally: + ticker.cancel() + await asyncio.gather(ticker, return_exceptions=True) + await operator.stop() + executor.shutdown(wait=True, cancel_futures=True) + + result = asyncio.run(scenario()) + _, alert = _single_alert(result) + + assert len(render_calls) == 1 + _, rendered_frame, rendered_detections, quality = render_calls[0] + assert rendered_frame is threshold_frame + assert rendered_detections == (threshold_detection,) + assert quality == 73 + assert alert.image is encoded + health = asyncio.run(operator.health()) + assert health.healthy is True + assert "alert_images_encoded=1" in health.detail + assert "alert_images_attached=1" in health.detail + + +def test_multiple_alerts_from_one_frame_share_one_union_image( + monkeypatch: pytest.MonkeyPatch, +) -> None: + helmet = _detection("No-Helmet", confidence=0.91) + vest = _detection("No-Vest", confidence=0.88) + threshold_frame = _frame(4) + encoded = EncodedImage(b"shared-jpeg", "image/jpeg", 4, 4) + render_calls: list[tuple[ImageFrame, tuple[Detection, ...]]] = [] + + def render( + frame: ImageFrame, + detections: tuple[Detection, ...], + *, + jpeg_quality: int, + ) -> EncodedImage: + assert jpeg_quality == 85 + render_calls.append((frame, tuple(detections))) + return encoded + + async def run_inline( + function: Any, + *args: Any, + executor: Any = None, + **kwargs: Any, + ) -> Any: + del executor + return function(*args, **kwargs) + + monkeypatch.setattr(detection_rules, "render_annotated_jpeg", render) + monkeypatch.setattr(detection_rules, "run_blocking", run_inline) + operator = DetectionTriggerOperator( + "trigger", + { + "time_source": "received", + "alert_image": {"enabled": True}, + "rules": [ + { + "id": "helmet", + "labels": ["No-Helmet"], + "min_hits": 1, + "window_ms": 100, + }, + { + "id": "vest", + "labels": ["No-Vest"], + "min_hits": 1, + "window_ms": 100, + }, + ], + }, + ) + + result = _process( + operator, + _envelope(1, 0, helmet, vest, frame=threshold_frame), + ) + + assert result is not None + assert render_calls == [(threshold_frame, (helmet, vest))] + assert [item.envelope.payload.rule_id for item in result] == ["helmet", "vest"] + assert all(item.envelope.payload.image is encoded for item in result) + health = asyncio.run(operator.health()) + assert "alert_images_encoded=1" in health.detail + assert "alert_images_attached=2" in health.detail + + +def test_missing_threshold_frame_is_fail_soft_and_commits_cooldown() -> None: + operator = _operator( + min_hits=1, + cooldown_ms=500, + alert_image={"enabled": True}, + ) + + _, alert = _single_alert(_process(operator, _envelope(1, 0, _detection()))) + assert alert.image is None + # The alert state is committed even though its evidence image is missing. + assert _process(operator, _envelope(1, 100, _detection())) is None + assert _process(operator, _envelope(2, 100, _detection())) is None + + health = asyncio.run(operator.health()) + assert health.healthy is False + assert "alerts_emitted=1" in health.detail + assert "duplicate_frames=1" in health.detail + assert "cooldown_suppressed=1" in health.detail + assert "alert_image_missing_frames=1" in health.detail + assert "alert_images_encoded=0" in health.detail + + +def test_renderer_failure_is_fail_soft_and_commits_cooldown( + monkeypatch: pytest.MonkeyPatch, +) -> None: + render_calls = 0 + + def fail_render(*args: Any, **kwargs: Any) -> EncodedImage: + nonlocal render_calls + render_calls += 1 + raise ValueError("bad packed frame") + + async def run_inline( + function: Any, + *args: Any, + executor: Any = None, + **kwargs: Any, + ) -> Any: + del executor + return function(*args, **kwargs) + + monkeypatch.setattr(detection_rules, "render_annotated_jpeg", fail_render) + monkeypatch.setattr(detection_rules, "run_blocking", run_inline) + operator = _operator( + min_hits=1, + cooldown_ms=500, + alert_image={"enabled": True}, + ) + frame = _frame(5) + + _, alert = _single_alert( + _process(operator, _envelope(1, 0, _detection(), frame=frame)) + ) + assert alert.image is None + assert render_calls == 1 + assert _process(operator, _envelope(1, 100, _detection(), frame=frame)) is None + assert _process(operator, _envelope(2, 100, _detection(), frame=frame)) is None + + health = asyncio.run(operator.health()) + assert health.healthy is False + assert "alerts_emitted=1" in health.detail + assert "duplicate_frames=1" in health.detail + assert "cooldown_suppressed=1" in health.detail + assert "alert_image_failures=1" in health.detail + assert "alert_images_encoded=0" in health.detail + + +def test_pillow_dependency_is_checked_only_when_alert_images_are_enabled( + monkeypatch: pytest.MonkeyPatch, +) -> None: + checks = 0 + + def missing_pillow() -> None: + nonlocal checks + checks += 1 + raise ImageAnnotationDependencyError("install the 'image' extra") + + monkeypatch.setattr(detection_rules, "ensure_annotation_support", missing_pillow) + context = ComponentContext( + pipeline_id="detection", + node_id="trigger", + shutdown_event=asyncio.Event(), + ) + + asyncio.run(_operator(min_hits=1).setup(context)) + assert checks == 0 + + enabled = _operator( + min_hits=1, + alert_image={"enabled": True}, + ) + with pytest.raises(ImageAnnotationDependencyError, match="image.*extra"): + asyncio.run(enabled.setup(context)) + assert checks == 1 + + +def test_time_source_is_explicit_and_captured_requires_a_timestamp() -> None: + captured = _operator(min_hits=2, window_ms=10, time_source="captured") + with pytest.raises(ValueError, match="requires captured_at_ns"): + _process(captured, _envelope(1, 0, _detection())) + + auto = _operator(min_hits=2, window_ms=10, time_source="auto") + first = _envelope(1, 0, _detection(), captured=True) + second = Envelope( + payload=first.payload, + schema_name="DetectionResult", + source_id="camera-a", + sequence=2, + captured_at_ns=20 * NS_PER_MS, + received_at_ns=5 * NS_PER_MS, + ) + assert _process(auto, first) is None + # auto prefers capture time, so the first hit is outside this 10 ms window. + assert _process(auto, second) is None + + received = _operator(min_hits=2, window_ms=10, time_source="received") + assert _process(received, first) is None + _single_alert(_process(received, second)) + + +def test_wrong_payload_and_out_of_order_frames_fail_closed() -> None: + operator = _operator(min_hits=2) + with pytest.raises(TypeError, match="expected DetectionResult"): + _process( + operator, + Envelope( + "not-a-detection", + source_id="camera-a", + sequence=1, + received_at_ns=0, + ), + ) + + assert _process(operator, _envelope(1, 100, _detection())) is None + assert _process(operator, _envelope(2, 99, _detection())) is None + assert "stale_frames=1" in asyncio.run(operator.health()).detail diff --git a/tests/unit/test_edge_queue.py b/tests/unit/test_edge_queue.py new file mode 100644 index 0000000..560c6cb --- /dev/null +++ b/tests/unit/test_edge_queue.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import asyncio + +import pytest + +from cmvr_edge_ai.core import ( + EdgeQueue, + EdgeQueueClosed, + EdgeQueueFull, + OverflowPolicy, + PutResult, +) + + +def test_drop_oldest_keeps_the_freshest_items_and_updates_stats() -> None: + async def exercise(): # type: ignore[no-untyped-def] + queue = EdgeQueue[int](2, OverflowPolicy.DROP_OLDEST, name="camera") + assert await queue.put(1) is PutResult.ENQUEUED + assert await queue.put(2) is PutResult.ENQUEUED + assert await queue.put(3) is PutResult.DROPPED_OLDEST + assert [await queue.get(), await queue.get()] == [2, 3] + await queue.close() + with pytest.raises(EdgeQueueClosed, match="camera"): + await queue.get() + return queue.stats() + + stats = asyncio.run(exercise()) + + assert stats.enqueued == 3 + assert stats.dequeued == 2 + assert stats.dropped_oldest == 1 + assert stats.high_watermark == 2 + assert stats.closed is True + + +def test_drop_newest_preserves_queued_items() -> None: + async def exercise(): # type: ignore[no-untyped-def] + queue = EdgeQueue[str](2, "drop_newest") + await queue.put("first") + await queue.put("second") + result = await queue.put("third") + items = [await queue.get(), await queue.get()] + return result, items, queue.stats() + + result, items, stats = asyncio.run(exercise()) + + assert result is PutResult.DROPPED_NEWEST + assert items == ["first", "second"] + assert stats.enqueued == 2 + assert stats.dropped_newest == 1 + + +def test_reject_policy_raises_without_mutating_contents() -> None: + async def exercise(): # type: ignore[no-untyped-def] + queue = EdgeQueue[str](1, "reject", name="control") + await queue.put("accepted") + with pytest.raises(EdgeQueueFull, match="control"): + await queue.put("rejected") + return await queue.get(), queue.stats() + + item, stats = asyncio.run(exercise()) + + assert item == "accepted" + assert stats.enqueued == 1 + assert stats.rejected == 1 + + +def test_block_policy_releases_producer_after_consumer_makes_space() -> None: + async def exercise(): # type: ignore[no-untyped-def] + queue = EdgeQueue[str](1, "block") + await queue.put("first") + pending_put = asyncio.create_task(queue.put("second")) + await asyncio.sleep(0) + assert pending_put.done() is False + + first = await queue.get() + put_result = await asyncio.wait_for(pending_put, timeout=1) + second = await queue.get() + return first, second, put_result, queue.stats() + + first, second, put_result, stats = asyncio.run(exercise()) + + assert (first, second) == ("first", "second") + assert put_result is PutResult.ENQUEUED + assert stats.blocked_puts == 1 diff --git a/tests/unit/test_http_json_sink.py b/tests/unit/test_http_json_sink.py new file mode 100644 index 0000000..a7079d6 --- /dev/null +++ b/tests/unit/test_http_json_sink.py @@ -0,0 +1,505 @@ +from __future__ import annotations + +import asyncio +import base64 +from copy import deepcopy +from dataclasses import dataclass +import json +import logging +from typing import Any + +import pytest + +import cmvr_edge_ai.connectors.platform.http_json as http_json +from cmvr_edge_ai.connectors.platform.http_json import PlatformHttpJsonSink +from cmvr_edge_ai.contracts import ( + BoundingBox, + Detection, + DetectionAlert, + DetectionResult, + EncodedImage, + ImageFrame, +) +from cmvr_edge_ai.core.component import ComponentContext +from cmvr_edge_ai.core.envelope import Envelope +from cmvr_edge_ai.transports.http import HttpClientPool, HttpClientSettings + + +class FakeHttpStatusError(RuntimeError): + def __init__(self, status_code: int) -> None: + self.status_code = status_code + super().__init__(f"HTTP status {status_code}") + + +@dataclass +class FakeResponse: + status_code: int + + def raise_for_status(self) -> None: + if self.status_code >= 400: + raise FakeHttpStatusError(self.status_code) + + +class FakeHttpClient: + def __init__(self, outcomes: list[FakeResponse | BaseException]) -> None: + self._outcomes = list(outcomes) + self.calls: list[dict[str, Any]] = [] + + async def post( + self, + path: str, + *, + json: dict[str, Any], + headers: dict[str, str], + ) -> FakeResponse: + self.calls.append( + { + "path": path, + "json": deepcopy(json), + "body_id": id(json), + "headers": dict(headers), + } + ) + if not self._outcomes: + raise AssertionError("fake HTTP client has no configured outcome") + outcome = self._outcomes.pop(0) + if isinstance(outcome, BaseException): + raise outcome + return outcome + + +class FakeHttpClientPool(HttpClientPool): + def __init__(self, client: FakeHttpClient) -> None: + self.client = client + self.get_calls: list[tuple[str, HttpClientSettings]] = [] + + async def get( + self, endpoint_id: str, settings: HttpClientSettings + ) -> FakeHttpClient: + self.get_calls.append((endpoint_id, settings)) + return self.client + + +def _alert( + event_id: str = "ppe-event-123", + *, + image: EncodedImage | None = None, +) -> DetectionAlert: + detection = Detection( + label="No-Helmet", + confidence=0.94, + box=BoundingBox(x_min=10.0, y_min=20.0, x_max=100.0, y_max=200.0), + ) + return DetectionAlert( + event_id=event_id, + rule_id="no-helmet-rule", + model_id="construction-ppe@1", + model_name="Construction PPE", + labels=("No-Helmet",), + scope="source", + scope_id="right-camera", + hit_count=3, + window_ms=2_000.0, + first_seen_ns=1_000_000_000, + last_seen_ns=2_000_000_000, + triggered_at_ns=2_000_000_000, + max_confidence=0.94, + detections=(detection,), + image=image, + ) + + +def _envelope(payload: Any, *, trace_id: str = "trace-fallback-456") -> Envelope[Any]: + return Envelope( + payload=payload, + schema_name=( + "DetectionAlert" if isinstance(payload, DetectionAlert) else "opaque" + ), + schema_version=1, + source_id="right-camera", + sequence=17, + captured_at_ns=2_000_000_000, + received_at_ns=2_010_000_000, + trace_id=trace_id, + attributes={"site": "park-a"}, + ) + + +async def _setup_sink( + client: FakeHttpClient, + **params: Any, +) -> PlatformHttpJsonSink: + sink = PlatformHttpJsonSink( + "platform", + { + "endpoint": "platform", + "path": "/v1/detection-alerts", + **params, + }, + ) + pool = FakeHttpClientPool(client) + context = ComponentContext( + pipeline_id="detection", + node_id="platform", + shutdown_event=asyncio.Event(), + metadata={ + "endpoints": { + "platform": { + "base_url": "http://platform.invalid", + "timeout_s": 1.0, + "metadata": {}, + "options": {}, + } + }, + "http_client_pool": pool, + }, + ) + await sink.setup(context) + assert pool.get_calls[0][0] == "platform" + return sink + + +def test_detection_alert_event_id_is_the_idempotency_key() -> None: + async def scenario() -> None: + client = FakeHttpClient([FakeResponse(200)]) + sink = await _setup_sink(client, max_attempts=1) + + await sink.consume(_envelope(_alert()), input_port="alerts") + + assert len(client.calls) == 1 + call = client.calls[0] + assert call["headers"] == {"Idempotency-Key": "ppe-event-123"} + assert call["path"] == "/v1/detection-alerts" + assert call["json"]["input_port"] == "alerts" + assert call["json"]["payload"]["event_id"] == "ppe-event-123" + assert call["json"]["payload"]["image"] is None + + asyncio.run(scenario()) + + +def test_detection_alert_image_has_a_flat_base64_wire_shape() -> None: + async def scenario() -> None: + jpeg = b"\xff\xd8\xff\xe0annotated-image\xff\xd9" + image = EncodedImage( + data=jpeg, + media_type="image/jpeg", + width=1280, + height=720, + ) + client = FakeHttpClient([FakeResponse(200)]) + sink = await _setup_sink(client, max_attempts=1) + + await sink.consume(_envelope(_alert(image=image)), input_port="alerts") + + body = client.calls[0]["json"] + payload = body["payload"] + assert payload["image"] == { + "media_type": "image/jpeg", + "width": 1280, + "height": 720, + "encoding": "base64", + "data": base64.b64encode(jpeg).decode("ascii"), + } + assert ( + base64.b64decode(payload["image"]["data"], validate=True) == jpeg + ) + assert payload["detections"][0] == { + "label": "No-Helmet", + "confidence": 0.94, + "box": { + "x_min": 10.0, + "y_min": 20.0, + "x_max": 100.0, + "y_max": 200.0, + }, + "track_id": None, + } + # The fake receives the pre-serialization dict; make sure a real JSON + # encoder can consume the exact body without any remaining bytes. + json.dumps(body) + + asyncio.run(scenario()) + + +def test_transient_detection_source_frame_is_never_serialized() -> None: + async def scenario() -> None: + raw_frame = ImageFrame(bytes(range(12)), 2, 2, "BGR8") + result = DetectionResult( + detections=( + Detection( + "No-Helmet", + 0.9, + BoundingBox(1.0, 0.0, 2.0, 2.0), + ), + ), + model_id="construction-ppe@1", + inference_ms=3.5, + source_frame=raw_frame, + ) + client = FakeHttpClient([FakeResponse(200)]) + sink = await _setup_sink(client, max_attempts=1) + + await sink.consume(_envelope(result), input_port="detections") + + payload = client.calls[0]["json"]["payload"] + assert "source_frame" not in payload + assert payload["detections"][0]["label"] == "No-Helmet" + assert base64.b64encode(raw_frame.data).decode("ascii") not in json.dumps( + client.calls[0]["json"] + ) + + asyncio.run(scenario()) + + +def test_payload_without_event_id_falls_back_to_envelope_trace_id() -> None: + async def scenario() -> None: + client = FakeHttpClient([FakeResponse(204)]) + sink = await _setup_sink(client, max_attempts=1) + + await sink.consume(_envelope({"status": "ok"})) + + assert client.calls[0]["headers"] == {"Idempotency-Key": "trace-fallback-456"} + + asyncio.run(scenario()) + + +@pytest.mark.parametrize( + "outcomes", + [ + [FakeResponse(503), FakeResponse(503), FakeResponse(200)], + [OSError("offline"), TimeoutError("timed out"), FakeResponse(200)], + ], + ids=("retryable-status", "network-errors"), +) +def test_retryable_failures_retry_to_max_attempts_with_an_unchanged_body( + outcomes: list[FakeResponse | BaseException], + monkeypatch: pytest.MonkeyPatch, +) -> None: + async def scenario() -> None: + encode_calls = 0 + original_b64encode = http_json.base64.b64encode + + def recording_b64encode(value: bytes) -> bytes: + nonlocal encode_calls + encode_calls += 1 + return original_b64encode(value) + + monkeypatch.setattr(http_json.base64, "b64encode", recording_b64encode) + client = FakeHttpClient(outcomes) + sink = await _setup_sink( + client, + max_attempts=3, + retry_initial_s=0, + retry_max_s=0, + ) + envelope = _envelope( + _alert(image=EncodedImage(b"jpeg", "image/jpeg", 2, 2)) + ) + + await sink.consume(envelope) + + assert len(client.calls) == 3 + first_body = client.calls[0]["json"] + assert all(call["json"] == first_body for call in client.calls) + assert len({call["body_id"] for call in client.calls}) == 1 + assert all( + call["headers"] == {"Idempotency-Key": "ppe-event-123"} + for call in client.calls + ) + assert encode_calls == 1 + + asyncio.run(scenario()) + + +def test_final_retryable_non_success_response_is_raised() -> None: + async def scenario() -> None: + client = FakeHttpClient([FakeResponse(503), FakeResponse(503)]) + sink = await _setup_sink( + client, + max_attempts=2, + retry_initial_s=0, + retry_max_s=0, + ) + + with pytest.raises(FakeHttpStatusError, match="503"): + await sink.consume(_envelope(_alert())) + + assert len(client.calls) == 2 + + asyncio.run(scenario()) + + +@pytest.mark.parametrize( + "outcome", + [FakeResponse(400), ValueError("invalid request body")], + ids=("non-retryable-status", "non-retryable-exception"), +) +def test_non_retryable_failure_is_raised_without_retry( + outcome: FakeResponse | BaseException, +) -> None: + async def scenario() -> None: + client = FakeHttpClient([outcome, FakeResponse(200)]) + sink = await _setup_sink( + client, + max_attempts=3, + retry_initial_s=0, + retry_max_s=0, + ) + + with pytest.raises((FakeHttpStatusError, ValueError)): + await sink.consume(_envelope(_alert())) + + assert len(client.calls) == 1 + + asyncio.run(scenario()) + + +@pytest.mark.parametrize( + ("outcomes", "expected_attempts", "expected_status"), + [ + ([OSError("offline"), TimeoutError("timed out")], 2, "none"), + ([FakeResponse(503), FakeResponse(503)], 2, "503"), + ([FakeResponse(400)], 1, "400"), + ], + ids=( + "network-errors", + "retryable-status", + "non-retryable-status", + ), +) +def test_log_and_drop_warns_without_failing_the_sink( + outcomes: list[FakeResponse | BaseException], + expected_attempts: int, + expected_status: str, + caplog: pytest.LogCaptureFixture, +) -> None: + async def scenario() -> None: + client = FakeHttpClient(outcomes) + sink = await _setup_sink( + client, + failure_mode="log_and_drop", + max_attempts=2, + retry_initial_s=0, + retry_max_s=0, + ) + + await sink.consume(_envelope(_alert()), input_port="alerts") + + assert len(client.calls) == expected_attempts + + with caplog.at_level(logging.WARNING, logger=http_json.__name__): + asyncio.run(scenario()) + + dropped = [ + record.getMessage() + for record in caplog.records + if "HTTP report dropped" in record.getMessage() + ] + assert len(dropped) == 1 + assert "failure_mode=log_and_drop" in dropped[0] + assert "action=drop" in dropped[0] + assert f"attempts={expected_attempts}" in dropped[0] + assert f"status={expected_status}" in dropped[0] + assert "trace_id=trace-fallback-456" in dropped[0] + + +def test_log_and_drop_never_swallows_cancellation() -> None: + async def scenario() -> None: + client = FakeHttpClient([asyncio.CancelledError()]) + sink = await _setup_sink( + client, + failure_mode="log_and_drop", + max_attempts=1, + ) + + with pytest.raises(asyncio.CancelledError): + await sink.consume(_envelope(_alert())) + + asyncio.run(scenario()) + + +def test_log_and_drop_continues_with_the_next_envelope() -> None: + async def scenario() -> None: + client = FakeHttpClient([OSError("offline"), FakeResponse(204)]) + sink = await _setup_sink( + client, + failure_mode="log_and_drop", + max_attempts=1, + ) + + await sink.consume(_envelope(_alert("dropped-event"))) + await sink.consume(_envelope(_alert("delivered-event"))) + + assert len(client.calls) == 2 + assert client.calls[0]["headers"] == { + "Idempotency-Key": "dropped-event" + } + assert client.calls[1]["headers"] == { + "Idempotency-Key": "delivered-event" + } + + asyncio.run(scenario()) + + +def test_log_and_drop_does_not_swallow_an_unknown_client_error() -> None: + async def scenario() -> None: + client = FakeHttpClient([ValueError("client programming error")]) + sink = await _setup_sink( + client, + failure_mode="log_and_drop", + max_attempts=3, + ) + + with pytest.raises(ValueError, match="client programming error"): + await sink.consume(_envelope(_alert())) + + assert len(client.calls) == 1 + + asyncio.run(scenario()) + + +def test_log_and_drop_success_does_not_emit_a_dropped_warning( + caplog: pytest.LogCaptureFixture, +) -> None: + async def scenario() -> None: + client = FakeHttpClient([FakeResponse(204)]) + sink = await _setup_sink( + client, + failure_mode="log_and_drop", + max_attempts=1, + ) + await sink.consume(_envelope(_alert())) + + with caplog.at_level(logging.WARNING, logger=http_json.__name__): + asyncio.run(scenario()) + + assert not any( + "HTTP report dropped" in record.getMessage() for record in caplog.records + ) + + +@pytest.mark.parametrize( + ("params", "message"), + [ + ({"max_attempts": 0}, "max_attempts must be a positive integer"), + ({"max_attempts": True}, "max_attempts must be a positive integer"), + ({"retry_initial_s": -0.1}, "retry_initial_s must be a non-negative"), + ({"retry_max_s": False}, "retry_max_s must be a non-negative"), + ( + {"retry_initial_s": 2.0, "retry_max_s": 1.0}, + "retry_max_s must be >= retry_initial_s", + ), + ({"retry_statuses": "503"}, "retry_statuses must be a sequence"), + ({"retry_statuses": [99]}, "must be between 100 and 599"), + ({"retry_statuses": [600]}, "must be between 100 and 599"), + ({"retry_statuses": ["not-a-status"]}, "must be a sequence"), + ({"failure_mode": "warn"}, "must be 'raise' or 'log_and_drop'"), + ({"failure_mode": ""}, "must be 'raise' or 'log_and_drop'"), + ({"failure_mode": None}, "must be 'raise' or 'log_and_drop'"), + ({"failure_mode": True}, "must be 'raise' or 'log_and_drop'"), + ({"failure_mode": []}, "must be 'raise' or 'log_and_drop'"), + ], +) +def test_retry_configuration_is_validated(params: dict[str, Any], message: str) -> None: + with pytest.raises(ValueError, match=message): + PlatformHttpJsonSink("platform", {"endpoint": "platform", **params}) diff --git a/tests/unit/test_safety_gate.py b/tests/unit/test_safety_gate.py new file mode 100644 index 0000000..64c21af --- /dev/null +++ b/tests/unit/test_safety_gate.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Mapping +from time import time_ns +from typing import Any + +import pytest + +from cmvr_edge_ai.contracts import ApprovedRobotCommand, RobotCommand +from cmvr_edge_ai.core import Envelope +from cmvr_edge_ai.plugins.builtin import CommandSafetyGate + + +def _envelope( + action: str, + *, + valid_until_ns: int, + sequence: int = 1, + arguments: Mapping[str, Any] | None = None, +) -> Envelope[RobotCommand]: + return Envelope( + RobotCommand( + device_id="src1100", + action=action, + valid_until_ns=valid_until_ns, + sequence=sequence, + arguments={} if arguments is None else arguments, + ), + schema_name="RobotCommand", + ) + + +def test_command_safety_gate_is_fail_closed() -> None: + gate = CommandSafetyGate( + "gate", + { + "allowed_actions": ["emergency_stop"], + "allowed_devices": ["src1100"], + }, + ) + future = time_ns() + 5_000_000_000 + + command_envelope = _envelope("emergency_stop", valid_until_ns=future) + accepted = asyncio.run(gate.process(command_envelope)) + expired = asyncio.run( + gate.process(_envelope("emergency_stop", valid_until_ns=time_ns() - 1)) + ) + unauthorized = asyncio.run( + gate.process(_envelope("set_velocity", valid_until_ns=future)) + ) + + assert accepted is not None + assert accepted.port == "commands" + assert accepted.envelope.schema == "ApprovedRobotCommand/v1" + assert isinstance(accepted.envelope.payload, ApprovedRobotCommand) + assert accepted.envelope.payload.command is command_envelope.payload + assert accepted.envelope.payload.approved_by == "gate" + assert accepted.envelope.trace_id == command_envelope.trace_id + assert expired is None + assert unauthorized is None + assert asyncio.run(gate.health()).detail == "rejected_commands=2" + + +def test_velocity_gate_requires_limits_and_rejects_replays() -> None: + with pytest.raises(ValueError, match="requires argument_limits"): + CommandSafetyGate("gate", {"allowed_actions": ["set_velocity"]}) + + gate = CommandSafetyGate( + "gate", + { + "allowed_actions": ["set_velocity"], + "allowed_devices": ["src1100"], + "argument_limits": { + "set_velocity": { + "vx": {"min": -1.0, "max": 1.0}, + "vy": {"min": -1.0, "max": 1.0}, + "wz": {"min": -2.0, "max": 2.0}, + } + }, + }, + ) + future = time_ns() + 5_000_000_000 + accepted = _envelope( + "set_velocity", + valid_until_ns=future, + sequence=10, + arguments={"vx": 0.5, "vy": 0.0, "wz": 0.25}, + ) + + approved = asyncio.run(gate.process(accepted)) + assert approved is not None + assert isinstance(approved.envelope.payload, ApprovedRobotCommand) + assert asyncio.run(gate.process(accepted)) is None + outside_limits = asyncio.run( + gate.process( + _envelope( + "set_velocity", + valid_until_ns=future, + sequence=11, + arguments={"vx": 1.5, "vy": 0.0, "wz": 0.0}, + ) + ) + ) + assert outside_limits is None + assert asyncio.run(gate.health()).detail == "rejected_commands=2" diff --git a/tests/unit/test_video_decoder.py b/tests/unit/test_video_decoder.py new file mode 100644 index 0000000..17b9416 --- /dev/null +++ b/tests/unit/test_video_decoder.py @@ -0,0 +1,445 @@ +from __future__ import annotations + +import asyncio +import threading +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass, field +from typing import Any + +import pytest + +import cmvr_edge_ai.detection.video as video_module +from cmvr_edge_ai.contracts import CameraIntrinsics, ImageFrame +from cmvr_edge_ai.core import ComponentContext, Emission, Envelope +from cmvr_edge_ai.detection.video import ( + DecodedVideoFrame, + VideoDecodeError, + VideoDecoderOperator, +) + + +@dataclass +class _BackendLog: + created_codecs: list[str] = field(default_factory=list) + decoded_packets: list[bytes] = field(default_factory=list) + worker_threads: list[int] = field(default_factory=list) + close_count: int = 0 + + +class _FakeBackend: + def __init__(self, codec: str, log: _BackendLog) -> None: + self._log = log + self._closed = False + log.created_codecs.append(codec) + log.worker_threads.append(threading.get_ident()) + + def decode(self, data: bytes) -> tuple[DecodedVideoFrame, ...]: + if self._closed: + raise RuntimeError("fake decoder is closed") + self._log.decoded_packets.append(data) + self._log.worker_threads.append(threading.get_ident()) + if data == b"decode-error": + raise VideoDecodeError("synthetic packet discontinuity") + if data == b"buffered": + return () + if data == b"two-frames": + return ( + DecodedVideoFrame(b"first", 2, 2), + DecodedVideoFrame(b"second", 3, 2), + ) + return (DecodedVideoFrame(b"decoded:" + data, 2, 2),) + + def close(self) -> None: + if not self._closed: + self._closed = True + self._log.close_count += 1 + self._log.worker_threads.append(threading.get_ident()) + + +def _factory(log: _BackendLog): # type: ignore[no-untyped-def] + return lambda codec: _FakeBackend(codec, log) + + +def _image( + *, + codec: str = "H264", + key_frame: bool = False, + width: int = 640, + height: int = 480, + data: bytes = b"packet", + intrinsics: CameraIntrinsics | None = None, +) -> ImageFrame: + return ImageFrame( + data=data, + width=width, + height=height, + pixel_format="encoded", + codec=codec, + is_key_frame=key_frame, + intrinsics=intrinsics, + ) + + +def _envelope( + image: Any, + *, + source_id: str = "camera-1", + session_id: str | None = "session-1", + sequence: int = 0, +) -> Envelope[Any]: + return Envelope( + payload=image, + schema_name="ImageFrame", + schema_version=1, + source_id=source_id, + session_id=session_id, + sequence=sequence, + captured_at_ns=123, + received_at_ns=456, + deadline_ns=999, + trace_id="trace-1", + attributes={"transport": "test"}, + ) + + +async def _setup(operator: VideoDecoderOperator, executor: Any = None) -> None: + metadata = {} if executor is None else {"thread_executor": executor} + await operator.setup( + ComponentContext( + pipeline_id="test-pipeline", + node_id="decoder", + shutdown_event=asyncio.Event(), + metadata=metadata, + ) + ) + + +async def _keep_restricted_event_loop_responsive() -> None: + """Provide wakeups where sandboxed self-pipe notifications are unavailable.""" + + while True: + await asyncio.sleep(0.01) + + +def _run_with_ticker(coroutine: Any) -> Any: + async def runner() -> Any: + loop = asyncio.get_running_loop() + host_executor = ThreadPoolExecutor(max_workers=2) + loop.set_default_executor(host_executor) + ticker = asyncio.create_task(_keep_restricted_event_loop_responsive()) + try: + return await coroutine + finally: + ticker.cancel() + await asyncio.gather(ticker, return_exceptions=True) + host_executor.shutdown(wait=True, cancel_futures=True) + # Avoid a second shutdown by asyncio.run() after ownership above. + loop._default_executor = None # type: ignore[attr-defined] + + return asyncio.run(runner()) + + +def test_raw_frame_is_passed_through_without_loading_a_backend() -> None: + factory_calls: list[str] = [] + + def forbidden_factory(codec: str): # type: ignore[no-untyped-def] + factory_calls.append(codec) + raise AssertionError("raw frames must not construct a decoder") + + operator = VideoDecoderOperator("decoder", {}, backend_factory=forbidden_factory) + envelope = _envelope( + ImageFrame( + data=b"raw-bgr", + width=2, + height=2, + pixel_format="BGR8", + codec="none", + ) + ) + + async def exercise() -> Emission: + await _setup(operator) + result = await operator.process(envelope) + await operator.stop() + assert isinstance(result, Emission) + return result + + emission = _run_with_ticker(exercise()) + + assert emission.port == "frames" + assert emission.envelope is envelope + assert factory_calls == [] + + +def test_waits_for_keyframe_decodes_off_loop_and_preserves_envelope_metadata() -> None: + log = _BackendLog() + operator = VideoDecoderOperator("decoder", {}, backend_factory=_factory(log)) + intrinsics = CameraIntrinsics(100.0, 101.0, 50.0, 51.0) + event_loop_thread = threading.get_ident() + + async def exercise(): # type: ignore[no-untyped-def] + with ThreadPoolExecutor(max_workers=1) as executor: + await _setup(operator, executor) + dropped = await operator.process( + _envelope(_image(key_frame=False), sequence=10) + ) + key_envelope = _envelope( + _image( + codec=".H264", + key_frame=True, + data=b"key", + intrinsics=intrinsics, + ), + sequence=11, + ) + decoded = await operator.process(key_envelope) + health = await operator.health() + await operator.stop() + return dropped, key_envelope, decoded, health + + dropped, original, result, health = _run_with_ticker(exercise()) + + assert dropped is None + assert isinstance(result, tuple) and len(result) == 1 + emission = result[0] + assert emission.port == "frames" + assert isinstance(emission.envelope.payload, ImageFrame) + assert emission.envelope.payload == ImageFrame( + data=b"decoded:key", + width=2, + height=2, + pixel_format="BGR8", + codec="none", + is_key_frame=False, + intrinsics=intrinsics, + ) + assert emission.envelope.source_id == original.source_id + assert emission.envelope.session_id == original.session_id + assert emission.envelope.sequence == original.sequence + assert emission.envelope.trace_id == original.trace_id + assert emission.envelope.captured_at_ns == original.captured_at_ns + assert emission.envelope.received_at_ns == original.received_at_ns + assert emission.envelope.attributes == original.attributes + assert log.created_codecs == ["h264"] + assert log.decoded_packets == [b"key"] + assert log.close_count == 1 + assert log.worker_threads + assert all(thread_id != event_loop_thread for thread_id in log.worker_threads) + assert "decoded_frames=1" in health.detail + assert "dropped_until_keyframe=1" in health.detail + + +@pytest.mark.parametrize( + ("change", "expected_codec"), + [ + ({"source_id": "camera-2"}, "h264"), + ({"session_id": "session-2"}, "h264"), + ({"codec": "H265"}, "hevc"), + ({"width": 800}, "h264"), + ({"height": 600}, "h264"), + ({"sequence": 4}, "h264"), + ], +) +def test_stream_identity_change_or_sequence_gap_resets_and_waits_for_keyframe( + change: dict[str, Any], + expected_codec: str, +) -> None: + log = _BackendLog() + operator = VideoDecoderOperator("decoder", {}, backend_factory=_factory(log)) + + async def exercise(): # type: ignore[no-untyped-def] + await _setup(operator) + first = await operator.process( + _envelope(_image(key_frame=True, data=b"initial"), sequence=1) + ) + + source_id = str(change.get("source_id", "camera-1")) + session_id = change.get("session_id", "session-1") + sequence = int(change.get("sequence", 2)) + codec = str(change.get("codec", "H264")) + width = int(change.get("width", 640)) + height = int(change.get("height", 480)) + changed_image = _image( + codec=codec, + key_frame=False, + width=width, + height=height, + data=b"must-drop", + ) + dropped = await operator.process( + _envelope( + changed_image, + source_id=source_id, + session_id=session_id, + sequence=sequence, + ) + ) + recovered = await operator.process( + _envelope( + _image( + codec=codec, + key_frame=True, + width=width, + height=height, + data=b"recovered", + ), + source_id=source_id, + session_id=session_id, + sequence=sequence + 1, + ) + ) + await operator.stop() + return first, dropped, recovered + + first, dropped, recovered = _run_with_ticker(exercise()) + + assert isinstance(first, tuple) + assert dropped is None + assert isinstance(recovered, tuple) + assert log.created_codecs == ["h264", expected_codec] + assert log.decoded_packets == [b"initial", b"recovered"] + assert log.close_count == 2 + + +def test_recoverable_decode_error_resets_and_suppresses_until_next_keyframe() -> None: + log = _BackendLog() + operator = VideoDecoderOperator("decoder", {}, backend_factory=_factory(log)) + + async def exercise(): # type: ignore[no-untyped-def] + await _setup(operator) + failed = await operator.process( + _envelope( + _image(key_frame=True, data=b"decode-error"), + sequence=1, + ) + ) + suppressed = await operator.process( + _envelope(_image(key_frame=False, data=b"p-frame"), sequence=2) + ) + recovered = await operator.process( + _envelope(_image(key_frame=True, data=b"new-key"), sequence=3) + ) + health = await operator.health() + await operator.stop() + await operator.stop() + return failed, suppressed, recovered, health + + failed, suppressed, recovered, health = _run_with_ticker(exercise()) + + assert failed is None + assert suppressed is None + assert isinstance(recovered, tuple) + assert log.created_codecs == ["h264", "h264"] + assert log.decoded_packets == [b"decode-error", b"new-key"] + assert log.close_count == 2 + assert "decode_errors=1" in health.detail + assert "resets=1" in health.detail + + +def test_backend_can_emit_zero_or_multiple_decoded_frames() -> None: + log = _BackendLog() + operator = VideoDecoderOperator("decoder", {}, backend_factory=_factory(log)) + + async def exercise(): # type: ignore[no-untyped-def] + await _setup(operator) + buffered = await operator.process( + _envelope(_image(key_frame=True, data=b"buffered"), sequence=1) + ) + multiple = await operator.process( + _envelope(_image(key_frame=False, data=b"two-frames"), sequence=2) + ) + await operator.stop() + return buffered, multiple + + buffered, multiple = _run_with_ticker(exercise()) + + assert buffered is None + assert isinstance(multiple, tuple) and len(multiple) == 2 + assert [item.envelope.payload.data for item in multiple] == [b"first", b"second"] + + +def test_raw_transition_closes_an_existing_encoded_decoder() -> None: + log = _BackendLog() + operator = VideoDecoderOperator("decoder", {}, backend_factory=_factory(log)) + raw_envelope = _envelope( + ImageFrame(b"raw", 1, 1, "BGR8", codec="raw"), + sequence=2, + ) + + async def exercise(): # type: ignore[no-untyped-def] + await _setup(operator) + await operator.process( + _envelope(_image(key_frame=True, data=b"key"), sequence=1) + ) + raw = await operator.process(raw_envelope) + await operator.stop() + return raw + + result = _run_with_ticker(exercise()) + + assert isinstance(result, Emission) + assert result.envelope is raw_envelope + assert log.close_count == 1 + + +def test_rejects_wrong_payload_unsupported_codec_and_invalid_dimensions() -> None: + log = _BackendLog() + operator = VideoDecoderOperator("decoder", {}, backend_factory=_factory(log)) + + async def exercise() -> None: + with pytest.raises(TypeError, match="expected ImageFrame"): + await operator.process(_envelope("not-an-image")) + with pytest.raises(ValueError, match="unsupported encoded video codec"): + await operator.process( + _envelope(_image(codec="vp9", key_frame=True), sequence=1) + ) + with pytest.raises(ValueError, match="dimensions must be positive"): + await operator.process( + _envelope(_image(width=0, key_frame=True), sequence=1) + ) + + _run_with_ticker(exercise()) + assert log.created_codecs == [] + + +def test_default_backend_imports_pyav_only_for_an_encoded_keyframe( + monkeypatch: pytest.MonkeyPatch, +) -> None: + imported: list[str] = [] + + def missing_import(name: str): # type: ignore[no-untyped-def] + imported.append(name) + raise ImportError("synthetic missing dependency") + + monkeypatch.setattr(video_module, "import_module", missing_import) + operator = VideoDecoderOperator("decoder", {}) + raw = _envelope(ImageFrame(b"raw", 1, 1, "BGR8", codec="none")) + + async def exercise() -> None: + assert isinstance(await operator.process(raw), Emission) + assert imported == [] + assert ( + await operator.process(_envelope(_image(key_frame=False), sequence=1)) + is None + ) + assert imported == [] + with pytest.raises(RuntimeError, match="optional video dependencies"): + await operator.process(_envelope(_image(key_frame=True), sequence=2)) + + _run_with_ticker(exercise()) + assert imported == ["av"] + + +def test_setup_rejects_a_non_executor_thread_executor() -> None: + operator = VideoDecoderOperator("decoder", {}) + + async def exercise() -> None: + with pytest.raises(TypeError, match="must be an Executor"): + await _setup(operator, object()) + + _run_with_ticker(exercise()) + + +def test_constructor_rejects_unknown_parameters() -> None: + with pytest.raises(ValueError, match="does not support parameters: typo"): + VideoDecoderOperator("decoder", {"typo": True}) diff --git a/tests/unit/test_yolo_model.py b/tests/unit/test_yolo_model.py new file mode 100644 index 0000000..7c77a68 --- /dev/null +++ b/tests/unit/test_yolo_model.py @@ -0,0 +1,378 @@ +from __future__ import annotations + +from pathlib import Path +from types import SimpleNamespace +from typing import Any + +import pytest + +from cmvr_edge_ai.contracts import BoundingBox, Detection, ImageFrame +from cmvr_edge_ai.detection.models import ( + PPE_6CLASS_LABELS, + register_builtin_detection_models, +) +from cmvr_edge_ai.detection.models import yolo as yolo_module +from cmvr_edge_ai.detection.models.yolo import ( + UltralyticsYoloModel, + YoloDependencyError, +) +from cmvr_edge_ai.detection.registry import DetectionModelRegistry + + +class _FakeArray: + def __init__(self, data: bytes, *, channel_reversed: bool = False) -> None: + self.data = data + self.channel_reversed = channel_reversed + self.shape: tuple[int, ...] | None = None + + def reshape(self, shape: tuple[int, ...]) -> _FakeArray: + self.shape = shape + return self + + def __getitem__(self, key: object) -> _FakeArray: + assert key == (slice(None), slice(None), slice(None, None, -1)) + reversed_array = _FakeArray(self.data, channel_reversed=True) + reversed_array.shape = self.shape + return reversed_array + + +class _FakeNumpy: + uint8 = object() + + def __init__(self) -> None: + self.frombuffer_calls: list[tuple[bytes, object]] = [] + + def frombuffer(self, data: bytes, *, dtype: object) -> _FakeArray: + self.frombuffer_calls.append((data, dtype)) + return _FakeArray(data) + + +class _FakeTensor: + """Exercise the detach/cpu/tolist path used by real torch tensors.""" + + def __init__(self, value: list[Any]) -> None: + self.value = value + self.detached = False + self.on_cpu = False + + def detach(self) -> _FakeTensor: + self.detached = True + return self + + def cpu(self) -> _FakeTensor: + self.on_cpu = True + return self + + def tolist(self) -> list[Any]: + return self.value + + +class _FakeBoxes: + def __init__( + self, + xyxy: list[list[float]], + confidence: list[float], + classes: list[float], + ) -> None: + self.xyxy = _FakeTensor(xyxy) + self.conf = _FakeTensor(confidence) + self.cls = _FakeTensor(classes) + + +def _weights(tmp_path: Path) -> Path: + path = tmp_path / "best.pt" + path.write_bytes(b"test-only-placeholder") + return path + + +def _install_fake_runtime( + monkeypatch: pytest.MonkeyPatch, + *, + names: object, + results: list[object] | None = None, +) -> tuple[_FakeNumpy, list[Any]]: + numpy = _FakeNumpy() + created: list[Any] = [] + + class FakeYOLO: + def __init__(self, weights: str, *, task: str) -> None: + self.weights = weights + self.task = task + self.names = names + self.predict_calls: list[dict[str, Any]] = [] + created.append(self) + + def predict(self, **kwargs: Any) -> list[object]: + self.predict_calls.append(kwargs) + return [] if results is None else results + + ultralytics = SimpleNamespace(YOLO=FakeYOLO) + + def fake_import_module(name: str) -> object: + if name == "numpy": + return numpy + if name == "ultralytics": + return ultralytics + raise AssertionError(f"unexpected lazy import: {name}") + + monkeypatch.setattr(yolo_module, "import_module", fake_import_module) + return numpy, created + + +def _frame( + *, + pixel_format: str = "BGR8", + codec: str = "none", + data: bytes = bytes(range(12)), +) -> ImageFrame: + return ImageFrame( + data=data, + width=2, + height=2, + pixel_format=pixel_format, + codec=codec, + ) + + +def test_yolo_dependency_is_loaded_lazily( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + calls: list[str] = [] + fake_numpy = _FakeNumpy() + + def missing_ultralytics(name: str) -> object: + calls.append(name) + if name == "numpy": + return fake_numpy + raise ImportError("ultralytics is intentionally unavailable") + + monkeypatch.setattr(yolo_module, "import_module", missing_ultralytics) + model = UltralyticsYoloModel( + {"weights": str(_weights(tmp_path))}, + expected_labels=("No-Helmet",), + ) + + assert calls == [] + with pytest.raises(YoloDependencyError, match=r"cmvr-edge-ai\[yolo\]"): + model.load() + assert calls == ["numpy", "ultralytics"] + + +def test_yolo_load_requires_checkpoint_labels_to_match_registration_exactly( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + weights = _weights(tmp_path) + _, created = _install_fake_runtime( + monkeypatch, + names={0: "Vest", 1: "No-Helmet"}, + ) + model = UltralyticsYoloModel( + {"weights": str(weights)}, + expected_labels=("No-Helmet", "Vest"), + ) + + with pytest.raises(ValueError, match="checkpoint labels do not match"): + model.load() + assert len(created) == 1 + assert created[0].weights == str(weights) + assert created[0].task == "detect" + + # A complete, ordered mapping is accepted; comparison is not set-based. + _, accepted = _install_fake_runtime( + monkeypatch, + names={0: "No-Helmet", 1: "Vest"}, + ) + matching = UltralyticsYoloModel( + {"weights": str(weights)}, + expected_labels=("No-Helmet", "Vest"), + ) + matching.load() + assert len(accepted) == 1 + + +def test_ppe_6classes_builtin_registration_preserves_checkpoint_label_order() -> None: + registry = DetectionModelRegistry() + register_builtin_detection_models(registry) + + spec = registry.resolve("ppe-6classes-yolov8n@1") + + assert spec.name == "PPE Detection YOLOv8n (6 Classes)" + assert spec.backend == "ultralytics-yolo" + assert spec.supported_labels == ( + "Gloves", + "Vest", + "goggles", + "helmet", + "mask", + "safety_shoe", + ) + assert spec.supported_labels == PPE_6CLASS_LABELS + + +def test_ppe_6classes_factory_enforces_checkpoint_label_order( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + registry = DetectionModelRegistry() + register_builtin_detection_models(registry) + spec = registry.resolve("ppe-6classes-yolov8n@1") + weights = _weights(tmp_path) + model = spec.factory({"weights": str(weights)}) + _install_fake_runtime( + monkeypatch, + names={ + 0: "Vest", + 1: "Gloves", + 2: "goggles", + 3: "helmet", + 4: "mask", + 5: "safety_shoe", + }, + ) + + assert isinstance(model, UltralyticsYoloModel) + with pytest.raises(ValueError, match="checkpoint labels do not match"): + model.load() + + _, created = _install_fake_runtime( + monkeypatch, + names=dict(enumerate(PPE_6CLASS_LABELS)), + ) + matching = spec.factory({"weights": str(weights)}) + matching.load() + + assert isinstance(matching, UltralyticsYoloModel) + assert len(created) == 1 + assert created[0].weights == str(weights) + assert created[0].task == "detect" + + +@pytest.mark.parametrize( + ("frame", "message"), + [ + (_frame(codec="h264"), "decoded ImageFrame"), + (_frame(pixel_format="GRAY8"), "supports BGR8 or RGB8"), + (_frame(data=b"too-short"), r"width\*height\*3"), + ], +) +def test_yolo_rejects_encoded_unsupported_or_malformed_buffers( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + frame: ImageFrame, + message: str, +) -> None: + _install_fake_runtime(monkeypatch, names=("No-Helmet",)) + model = UltralyticsYoloModel( + {"weights": str(_weights(tmp_path))}, + expected_labels=("No-Helmet",), + ) + model.load() + + with pytest.raises(ValueError, match=message): + model.predict(frame, ("No-Helmet",), 0.5) + + +def test_yolo_converts_rgb_to_bgr_but_keeps_bgr_buffer_order( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + numpy, created = _install_fake_runtime(monkeypatch, names=("No-Helmet",)) + model = UltralyticsYoloModel( + {"weights": str(_weights(tmp_path))}, + expected_labels=("No-Helmet",), + ) + model.load() + + model.predict(_frame(pixel_format=" BGR8 "), ("No-Helmet",), 0.5) + bgr_source = created[0].predict_calls[-1]["source"] + assert isinstance(bgr_source, _FakeArray) + assert bgr_source.shape == (2, 2, 3) + assert bgr_source.channel_reversed is False + + model.predict(_frame(pixel_format="RGB8"), ("No-Helmet",), 0.5) + rgb_source = created[0].predict_calls[-1]["source"] + assert isinstance(rgb_source, _FakeArray) + assert rgb_source.shape == (2, 2, 3) + assert rgb_source.channel_reversed is True + assert numpy.frombuffer_calls == [ + (bytes(range(12)), numpy.uint8), + (bytes(range(12)), numpy.uint8), + ] + + +def test_yolo_predict_forwards_options_and_normalizes_boxes( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + boxes = _FakeBoxes( + xyxy=[ + [1.0, 2.0, 30.0, 40.0], + [5.5, 6.5, 50.5, 60.5], + [0.0, 0.0, 1.0, 1.0], + ], + confidence=[0.91, 0.82, 0.99], + classes=[1.0, 2.0, 0.0], + ) + _, created = _install_fake_runtime( + monkeypatch, + names={0: "Worker", 1: "No-Helmet", 2: "No-Vest"}, + results=[SimpleNamespace(boxes=boxes)], + ) + model = UltralyticsYoloModel( + { + "weights": str(_weights(tmp_path)), + "device": "cuda:0", + "imgsz": 320, + "iou": 0.45, + "half": True, + "max_det": 42, + "agnostic_nms": True, + }, + expected_labels=("Worker", "No-Helmet", "No-Vest"), + ) + model.load() + + detections = model.predict( + _frame(), + ("No-Helmet", "No-Vest"), + 0.73, + ) + + call = created[0].predict_calls[-1] + assert call["classes"] == [1, 2] + assert call["conf"] == pytest.approx(0.73) + assert call["iou"] == pytest.approx(0.45) + assert call["imgsz"] == 320 + assert call["device"] == "cuda:0" + assert call["half"] is True + assert call["max_det"] == 42 + assert call["agnostic_nms"] is True + assert call["verbose"] is False + assert detections == ( + Detection( + label="No-Helmet", + confidence=0.91, + box=BoundingBox(1.0, 2.0, 30.0, 40.0), + ), + Detection( + label="No-Vest", + confidence=0.82, + box=BoundingBox(5.5, 6.5, 50.5, 60.5), + ), + ) + assert boxes.xyxy.detached and boxes.xyxy.on_cpu + assert boxes.conf.detached and boxes.conf.on_cpu + assert boxes.cls.detached and boxes.cls.on_cpu + + +def test_yolo_close_is_idempotent_and_requires_reload_before_predict( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _install_fake_runtime(monkeypatch, names=("No-Helmet",)) + model = UltralyticsYoloModel( + {"weights": str(_weights(tmp_path))}, + expected_labels=("No-Helmet",), + ) + model.load() + model.close() + model.close() + + with pytest.raises(RuntimeError, match="has not been loaded"): + model.predict(_frame(), ("No-Helmet",), 0.5) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..a554b26 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1845 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.13" +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "extra != 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +required-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", +] +conflicts = [[ + { package = "cmvr-edge-ai", extra = "yolo" }, + { package = "cmvr-edge-ai", extra = "yolo-cpu" }, +]] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "idna" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "av" +version = "16.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/cd/3a83ffbc3cc25b39721d174487fb0d51a76582f4a1703f98e46170ce83d4/av-16.1.0.tar.gz", hash = "sha256:a094b4fd87a3721dacf02794d3d2c82b8d712c85b9534437e82a8a978c175ffd", size = 4285203, upload-time = "2026-01-11T07:31:33.772Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/51/2217a9249409d2e88e16e3f16f7c0def9fd3e7ffc4238b2ec211f9935bdb/av-16.1.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:2395748b0c34fe3a150a1721e4f3d4487b939520991b13e7b36f8926b3b12295", size = 26942590, upload-time = "2026-01-09T20:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/a7070f4febc76a327c38808e01e2ff6b94531fe0b321af54ea3915165338/av-16.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:72d7ac832710a158eeb7a93242370aa024a7646516291c562ee7f14a7ea881fd", size = 21507910, upload-time = "2026-01-09T20:18:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/ae/30/ec812418cd9b297f0238fe20eb0747d8a8b68d82c5f73c56fe519a274143/av-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6cbac833092e66b6b0ac4d81ab077970b8ca874951e9c3974d41d922aaa653ed", size = 38738309, upload-time = "2026-01-09T20:18:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b8/6c5795bf1f05f45c5261f8bce6154e0e5e86b158a6676650ddd77c28805e/av-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:eb990672d97c18f99c02f31c8d5750236f770ffe354b5a52c5f4d16c5e65f619", size = 40293006, upload-time = "2026-01-09T20:18:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a7/44/5e183bcb9333fc3372ee6e683be8b0c9b515a506894b2d32ff465430c074/av-16.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05ad70933ac3b8ef896a820ea64b33b6cca91a5fac5259cb9ba7fa010435be15", size = 40123516, upload-time = "2026-01-09T20:18:09.955Z" }, + { url = "https://files.pythonhosted.org/packages/12/1d/b5346d582a3c3d958b4d26a2cc63ce607233582d956121eb20d2bbe55c2e/av-16.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d831a1062a3c47520bf99de6ec682bd1d64a40dfa958e5457bb613c5270e7ce3", size = 41463289, upload-time = "2026-01-09T20:18:12.459Z" }, + { url = "https://files.pythonhosted.org/packages/fa/31/acc946c0545f72b8d0d74584cb2a0ade9b7dfe2190af3ef9aa52a2e3c0b1/av-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:358ab910fef3c5a806c55176f2b27e5663b33c4d0a692dafeb049c6ed71f8aff", size = 31754959, upload-time = "2026-01-09T20:18:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/48/d0/b71b65d1b36520dcb8291a2307d98b7fc12329a45614a303ff92ada4d723/av-16.1.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:e88ad64ee9d2b9c4c5d891f16c22ae78e725188b8926eb88187538d9dd0b232f", size = 26927747, upload-time = "2026-01-09T20:18:16.976Z" }, + { url = "https://files.pythonhosted.org/packages/2f/79/720a5a6ccdee06eafa211b945b0a450e3a0b8fc3d12922f0f3c454d870d2/av-16.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cb296073fa6935724de72593800ba86ae49ed48af03960a4aee34f8a611f442b", size = 21492232, upload-time = "2026-01-09T20:18:19.266Z" }, + { url = "https://files.pythonhosted.org/packages/8e/4f/a1ba8d922f2f6d1a3d52419463ef26dd6c4d43ee364164a71b424b5ae204/av-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:720edd4d25aa73723c1532bb0597806d7b9af5ee34fc02358782c358cfe2f879", size = 39291737, upload-time = "2026-01-09T20:18:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/1a/31/fc62b9fe8738d2693e18d99f040b219e26e8df894c10d065f27c6b4f07e3/av-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c7f2bc703d0df260a1fdf4de4253c7f5500ca9fc57772ea241b0cb241bcf972e", size = 40846822, upload-time = "2026-01-09T20:18:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/53/10/ab446583dbce730000e8e6beec6ec3c2753e628c7f78f334a35cad0317f4/av-16.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d69c393809babada7d54964d56099e4b30a3e1f8b5736ca5e27bd7be0e0f3c83", size = 40675604, upload-time = "2026-01-09T20:18:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/31/d7/1003be685277005f6d63fd9e64904ee222fe1f7a0ea70af313468bb597db/av-16.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:441892be28582356d53f282873c5a951592daaf71642c7f20165e3ddcb0b4c63", size = 42015955, upload-time = "2026-01-09T20:18:29.461Z" }, + { url = "https://files.pythonhosted.org/packages/2f/4a/fa2a38ee9306bf4579f556f94ecbc757520652eb91294d2a99c7cf7623b9/av-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:273a3e32de64819e4a1cd96341824299fe06f70c46f2288b5dc4173944f0fd62", size = 31750339, upload-time = "2026-01-09T20:18:32.249Z" }, + { url = "https://files.pythonhosted.org/packages/9c/84/2535f55edcd426cebec02eb37b811b1b0c163f26b8d3f53b059e2ec32665/av-16.1.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:640f57b93f927fba8689f6966c956737ee95388a91bd0b8c8b5e0481f73513d6", size = 26945785, upload-time = "2026-01-09T20:18:34.486Z" }, + { url = "https://files.pythonhosted.org/packages/b6/17/ffb940c9e490bf42e86db4db1ff426ee1559cd355a69609ec1efe4d3a9eb/av-16.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ae3fb658eec00852ebd7412fdc141f17f3ddce8afee2d2e1cf366263ad2a3b35", size = 21481147, upload-time = "2026-01-09T20:18:36.716Z" }, + { url = "https://files.pythonhosted.org/packages/15/c1/e0d58003d2d83c3921887d5c8c9b8f5f7de9b58dc2194356a2656a45cfdc/av-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ee558d9c02a142eebcbe55578a6d817fedfde42ff5676275504e16d07a7f86", size = 39517197, upload-time = "2026-01-11T09:57:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/77/787797b43475d1b90626af76f80bfb0c12cfec5e11eafcfc4151b8c80218/av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2", size = 41174337, upload-time = "2026-01-11T09:57:35.792Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/d90df7f1e3b97fc5554cf45076df5045f1e0a6adf13899e10121229b826c/av-16.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8cf065f9d438e1921dc31fc7aa045790b58aee71736897866420d80b5450f62a", size = 40817720, upload-time = "2026-01-11T09:57:39.039Z" }, + { url = "https://files.pythonhosted.org/packages/80/6f/13c3a35f9dbcebafd03fe0c4cbd075d71ac8968ec849a3cfce406c35a9d2/av-16.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a345877a9d3cc0f08e2bc4ec163ee83176864b92587afb9d08dff50f37a9a829", size = 42267396, upload-time = "2026-01-11T09:57:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "cmvr-edge-ai" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "pydantic" }, + { name = "pyyaml" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "protobuf" }, +] +http = [ + { name = "httpx" }, +] +image = [ + { name = "pillow" }, +] +video = [ + { name = "av" }, +] +yolo = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "ultralytics" }, +] +yolo-cpu = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torch", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torch", version = "2.7.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torchvision", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torchvision", version = "0.22.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "ultralytics" }, +] + +[package.dev-dependencies] +codegen = [ + { name = "grpcio-tools" }, +] +dev = [ + { name = "grpcio-tools" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, +] + +[package.metadata] +requires-dist = [ + { name = "av", marker = "extra == 'video'", specifier = ">=12,<17" }, + { name = "grpcio", marker = "extra == 'grpc'", specifier = ">=1.76,<2" }, + { name = "httpx", marker = "extra == 'http'", specifier = ">=0.27,<1" }, + { name = "numpy", marker = "extra == 'yolo'", specifier = ">=1.24,<3" }, + { name = "numpy", marker = "extra == 'yolo-cpu'", specifier = ">=1.24,<3" }, + { name = "pillow", marker = "extra == 'image'", specifier = ">=10,<13" }, + { name = "protobuf", marker = "extra == 'grpc'", specifier = ">=6.31.1,<7" }, + { name = "pydantic", specifier = ">=2.8,<3" }, + { name = "pyyaml", specifier = ">=6,<7" }, + { name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'yolo-cpu'", specifier = "==2.7.0", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "cmvr-edge-ai", extra = "yolo-cpu" } }, + { name = "torch", marker = "(platform_machine != 'x86_64' and extra == 'yolo-cpu') or (sys_platform != 'linux' and extra == 'yolo-cpu')", specifier = "==2.7.0" }, + { name = "torchvision", marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'yolo-cpu'", specifier = "==0.22.0", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "cmvr-edge-ai", extra = "yolo-cpu" } }, + { name = "torchvision", marker = "(platform_machine != 'x86_64' and extra == 'yolo-cpu') or (sys_platform != 'linux' and extra == 'yolo-cpu')", specifier = "==0.22.0" }, + { name = "ultralytics", marker = "extra == 'yolo'", specifier = "==8.4.31" }, + { name = "ultralytics", marker = "extra == 'yolo-cpu'", specifier = "==8.4.31" }, +] +provides-extras = ["grpc", "http", "video", "image", "yolo", "yolo-cpu"] + +[package.metadata.requires-dev] +codegen = [{ name = "grpcio-tools", specifier = ">=1.76,<2" }] +dev = [ + { name = "grpcio-tools", specifier = ">=1.76,<2" }, + { name = "pytest", specifier = ">=7.4,<9" }, + { name = "pytest-asyncio", specifier = ">=0.23,<1" }, +] +test = [ + { name = "pytest", specifier = ">=7.4,<9" }, + { name = "pytest-asyncio", specifier = ">=0.23,<1" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra != 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "filelock" +version = "3.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/55/1e19b2b56a24a4b94624f7e819e1bb87fa6c5609dbaf621df3aa6568a761/filelock-3.31.1.tar.gz", hash = "sha256:9e0c4e88ebe90833c1beafd3a547ccbc0bf7f491cd3858c3ec7aed63efe02163", size = 196656, upload-time = "2026-07-20T03:14:32.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/61/df1d9db18f188d0ae648956a1decadc0e3b77d0571474370fd01f28a82b1/filelock-3.31.1-py3-none-any.whl", hash = "sha256:9ea33146c780161bf67cb20c7cb26b651566820d65ad8dfdd79422602a2dcfc0", size = 97189, upload-time = "2026-07-20T03:14:31.307Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632, upload-time = "2026-05-14T12:02:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441, upload-time = "2026-05-14T12:02:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346, upload-time = "2026-05-14T12:02:43.41Z" }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184, upload-time = "2026-05-14T12:02:45.742Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967, upload-time = "2026-05-14T12:02:48.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799, upload-time = "2026-05-14T12:02:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704, upload-time = "2026-05-14T12:02:52.523Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666, upload-time = "2026-05-14T12:02:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, +] + +[[package]] +name = "grpcio" +version = "1.82.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/bc/656b89387d6f4ed7e0686c7b64c2ae7e554a759aa58122c8e5fb99392c32/grpcio-1.82.1.tar.gz", hash = "sha256:707b24abd90fcb1e45bcc080577da1dbf9971d107490589b9539af8e1e77b4b5", size = 13187300, upload-time = "2026-07-08T12:36:16.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/14/5d05bfd85c101cbe44a12d7c1cea9c40698e0438cddf3a70019f735b5a27/grpcio-1.82.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:91859d1cac5f47caec5fc40e9f827500cdb54ce5b36450dc9a65616b5af49c17", size = 6177087, upload-time = "2026-07-08T12:34:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/19/2e/c906f8e6d0b54c0137885fff6f7b5883c6bbc381b44a0ba5ea07d7d1579b/grpcio-1.82.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c80c9741dcef192f669876a81957cf7713b441c2f0c43631350d75fa49321d31", size = 11960907, upload-time = "2026-07-08T12:34:10.583Z" }, + { url = "https://files.pythonhosted.org/packages/de/be/ec4aa76cdf25539b9e960cbb9d5739f892ea6cde58078b5293860c1159d3/grpcio-1.82.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b89cff456796d2f0581783726ad017a2c70aff2d27b0f05504c34e2e417f7560", size = 6754802, upload-time = "2026-07-08T12:34:13.082Z" }, + { url = "https://files.pythonhosted.org/packages/e6/dd/47519c2a8fd9db47ec4493f44bd9f5b0175307e07089b1132e54b7b5b19c/grpcio-1.82.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d6e8a08f7038ba7a77f71e250804e4aba84fe91d22cfc54ff43c07b7529c4728", size = 7484535, upload-time = "2026-07-08T12:34:15.164Z" }, + { url = "https://files.pythonhosted.org/packages/63/99/659711e9689c4dd553bcd4eacff9cb9f458f34b60edf7afb3bbc1b0a58a2/grpcio-1.82.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:50fd2fe83426b1b1c6cdc4d72d555223b7dddf8ce07c5bac218b13fc6d684c6f", size = 6919066, upload-time = "2026-07-08T12:34:17.367Z" }, + { url = "https://files.pythonhosted.org/packages/29/39/f2b772356b4f593ffe439795509fcbf675b0ff98211ae8ce2a180f2e559f/grpcio-1.82.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b758540a24d5394a9c578bf9f6126389f474b106ac3d9df1d53de56cb14c9fd9", size = 7525855, upload-time = "2026-07-08T12:34:19.479Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/b28cfffb989a84d8272593498bddd2d68148cce1813ad55189c469b0f1f8/grpcio-1.82.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c4ba4aac238f685743575d9d700003ac16537cce26e7c774993134f530652464", size = 8565122, upload-time = "2026-07-08T12:34:21.951Z" }, + { url = "https://files.pythonhosted.org/packages/97/f9/54956cb0c701190cbc9d7e535c3f84acf0285c6b9ed198a902766e17c3cd/grpcio-1.82.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed6fc621d6f366c88a60f0b971d5afd21d441d9aa561ee688de5b7acdb2cf901", size = 7933872, upload-time = "2026-07-08T12:34:24.539Z" }, + { url = "https://files.pythonhosted.org/packages/76/85/5f9cd1f965bbe4329556a212f178ae0c072b18b446cae05ed32fa8847c53/grpcio-1.82.1-cp310-cp310-win32.whl", hash = "sha256:bd2f45e46fff5b91c10997d0743a987517a7dde67c64c592835c2dcaac66f587", size = 4257373, upload-time = "2026-07-08T12:34:26.566Z" }, + { url = "https://files.pythonhosted.org/packages/93/b0/c4f42f7c69c53d27ed41643421b55908bcbe885b68f5a208135c72917c98/grpcio-1.82.1-cp310-cp310-win_amd64.whl", hash = "sha256:5e171d5f0d6a0af78ea7512783f170a44f80c165259d8773e3a354a7f991f2b5", size = 5006571, upload-time = "2026-07-08T12:34:28.778Z" }, + { url = "https://files.pythonhosted.org/packages/26/5b/e5092af97fa671ca279b3e373251af4bf87d5fbda7dc85f6a616899562a7/grpcio-1.82.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:0ddb18a9a9e1f46692b3567ae4abb3f8d117ce6afea48650f8eca06d8ab5d06f", size = 6181472, upload-time = "2026-07-08T12:34:31.009Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/18053a3a2ca03d0c2a1b8cc7271e705007a16aa5dae84bac00935c5b1a7f/grpcio-1.82.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cf855b1af246720f567b0ce5d0724d45dfa4188eecc3296a2a69257b11b9e94b", size = 11970995, upload-time = "2026-07-08T12:34:33.603Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/21b1acb052876ad00959ec4d1b05fe08607d650bcfa282073bb164c2703c/grpcio-1.82.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb30cb13e25bc13cea70ffc69d6d90c49d36ea6c1d4549e6912f70177834cac", size = 6760127, upload-time = "2026-07-08T12:34:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/3e/12/25eef9c245c54f0061317d13a302357fe8ea03bac240b2b02ececcf54da4/grpcio-1.82.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1e822b2774f719c017cbe700b6e47173b6ae290fb84906f52a5a3c2c60b62e1e", size = 7484377, upload-time = "2026-07-08T12:34:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/1a348767eb9d9bd7765dc4fa8a01723d3bb386d67f981ee5c6f9c02b8b1c/grpcio-1.82.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5dafb1ece8ed45dee7c738f166ec82e19673221ed5ab8967f72858a4685345b2", size = 6924269, upload-time = "2026-07-08T12:34:40.583Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b9/3aae7a03d34c86ea27988db859a6087c186f6c3f53f9b551e07afd989bfa/grpcio-1.82.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06503106e7271e0a49fd5a1ac04747f1e47e87d900476db6fe45bc87ee411f4", size = 7531848, upload-time = "2026-07-08T12:34:43.277Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/3c4afa625d0dac9090707966916284c035fc5b2fb3e2c51e156accee6735/grpcio-1.82.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ff99bc8cafb6a952201c37b995f425e641c93ffa6e072258525feab57290141d", size = 8568217, upload-time = "2026-07-08T12:34:45.502Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/d8489c628e73e20a3d034e7f66912de7b1acb405f01d388f056a88e47924/grpcio-1.82.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:644ae1b94266ac785330f4590a69e52b6a7eb73029043a02209db81c81397d69", size = 7938771, upload-time = "2026-07-08T12:34:48.323Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b7/0a92cfd1658f3a896d4aa12d4efeb7dd4ddfc723725ae22741a5241ea710/grpcio-1.82.1-cp311-cp311-win32.whl", hash = "sha256:e203d2e19d471630084a16c815616f8211dff21c268ab3c5f5bf38417832e074", size = 4256432, upload-time = "2026-07-08T12:34:50.432Z" }, + { url = "https://files.pythonhosted.org/packages/c7/6a/2872c761b025d9ec74386f22a4a7d59c5a5b00ebf718761b33739ffc45de/grpcio-1.82.1-cp311-cp311-win_amd64.whl", hash = "sha256:0d8299c285fe6cc6a1f56badf8d3bc5078c8d20273ee64bafa3783b4bc29a769", size = 5009633, upload-time = "2026-07-08T12:34:52.67Z" }, + { url = "https://files.pythonhosted.org/packages/dc/88/d1350bf3343a2ed87d801584e40609f6c6bd3087926eeca03de50348cf4a/grpcio-1.82.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:c09bd5fa0d5b1fbd773ec349fe61441c3e4ebf168c229aa7538a820bdfad6a58", size = 6144689, upload-time = "2026-07-08T12:34:55.567Z" }, + { url = "https://files.pythonhosted.org/packages/e6/33/71875cdecd27c24ac1385d4783a09853f01b84a825a36aec2a2bc7d0d080/grpcio-1.82.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1eae24810720734598e3e6a1a528d5de0f265fe3fc86575e9ecce424b9ec7379", size = 11952034, upload-time = "2026-07-08T12:34:58.128Z" }, + { url = "https://files.pythonhosted.org/packages/82/b2/d9125df3d8a140dec12cc82c05b7deafedeababcff6496f28b2fd5634d10/grpcio-1.82.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a6bd5daf5bde7b24d7ad2cbaf8bf9eac620d96222016bb5e7ddde930dec0673f", size = 6710772, upload-time = "2026-07-08T12:35:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/88/9b/69e2d1627398b964f34437dc476a5aff5a2cc8e7f247d26272b5674b5faf/grpcio-1.82.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1ecfde669cb687ac020d31ff76debe5dc7a62213335f02262eb6625628da1c03", size = 7450677, upload-time = "2026-07-08T12:35:03.926Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e7/8f855ca29c294956122a2a73023655b9b02602d5111dad2b9b00e7631c68/grpcio-1.82.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:011c8badee95734dee8bf05ce3464756a0ac3ebb8d443afd20c0e2b5e4640ad9", size = 6886855, upload-time = "2026-07-08T12:35:06.174Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f0/fa87e85f49925f44c479d07e58b051e69bcfef6b6d5fbc6749d140f6730a/grpcio-1.82.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b85f4564926fb23114d239392bdcae200db1e6179629edd7d7ab0ab89c96a197", size = 7501323, upload-time = "2026-07-08T12:35:08.49Z" }, + { url = "https://files.pythonhosted.org/packages/67/55/2e0b10ae1d3ef9dcc480b91dc2158f4931fc4675d3af0a2836e39b2a744f/grpcio-1.82.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2c0c8270833395644c3fe6b6a806397955a2bc0538000a19a78b90c05a6c16e0", size = 8536899, upload-time = "2026-07-08T12:35:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/a3d8b0431fa221efc51ee39d73595ede74ba82a43b7c4313192e580face2/grpcio-1.82.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2ba199205ff46c7778290fe1673c91ac8e7e45678dd5c86e9e56fa33ec8788f6", size = 7913892, upload-time = "2026-07-08T12:35:13.944Z" }, + { url = "https://files.pythonhosted.org/packages/b8/92/f2651ec704d9852a56faef394775038afba435b50ce82ab2404d119c3355/grpcio-1.82.1-cp312-cp312-win32.whl", hash = "sha256:06127691866e295c14e84a1fb86356dd962254f6abd0da4ca4b001eea9e89438", size = 4240985, upload-time = "2026-07-08T12:35:16.048Z" }, + { url = "https://files.pythonhosted.org/packages/96/4f/a5fe8bf0d0a1b24855f370293075c931f27de4eb55f0f158786095bf3c11/grpcio-1.82.1-cp312-cp312-win_amd64.whl", hash = "sha256:1fa3223a3a2e1db74f4c2b255189eb7ea875dfba56e221d252ee3fc7b204778e", size = 5001580, upload-time = "2026-07-08T12:35:18.689Z" }, +] + +[[package]] +name = "grpcio-tools" +version = "1.81.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/b3/1c5951352d6777fd7f99a0ccee04617fdfd8a5dbf2918a1f58c8b2b280b8/grpcio_tools-1.81.1.tar.gz", hash = "sha256:a22a3870180927fdd84e2b27d079ef5b7f5f8c6110181b6736afc17a463481f1", size = 6236155, upload-time = "2026-06-11T12:51:21.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/e1/1fcf884902ae7255d8da224cfa638ea88a46d50f62a33d06d35c8960b029/grpcio_tools-1.81.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:9b6ba8a72cfda576508701a7c0bbeebe6f6f9843320d4f12e74efd19ddccd965", size = 2586261, upload-time = "2026-06-11T12:49:21.447Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d7/1815110b2d40ec99dbb0a7e6d7eafd591cd1f1e9bf9d3858cd9cf3ffacbd/grpcio_tools-1.81.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ac47a9ea1224df8b653072614e6f0207e9fbfe63fdabaa5918a60ca5fc931b88", size = 5817509, upload-time = "2026-06-11T12:49:25.958Z" }, + { url = "https://files.pythonhosted.org/packages/23/e8/af99579842b5a555312fa782f32ce0f99bd35b2b7a1243294b2755468857/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eac4bb645ceff0c147cc720a40ae68f97427eaafb4968e866dd8fcc20d3d4831", size = 2634112, upload-time = "2026-06-11T12:49:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/235ad56ac728c49c17e9218c4daccd5831e6ec7af94236bec0cc66c71c68/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cc410b621dd85193766c12dca2e238696199a27a65d2b31b6f0a4c6c0043ff26", size = 2957950, upload-time = "2026-06-11T12:49:29.619Z" }, + { url = "https://files.pythonhosted.org/packages/77/3e/9103e8b4610597bf89db49eb112091c91bf5d63ddef2a951e11a4be05f2b/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b62d254c214faa3773eac709376ae25cf7abff1a76ba5fc4dbcd7b14fc4e4ae6", size = 2697765, upload-time = "2026-06-11T12:49:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/3c/86/beb2a43fbb93570a2305696083f6736566301d957869f463308ec6839f95/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd0b68dc76b10b3384b9b6e9f59202b83dcaafd8098eb644759a69316686acf8", size = 3147588, upload-time = "2026-06-11T12:49:33.748Z" }, + { url = "https://files.pythonhosted.org/packages/af/4d/b0182d9948631cd837a372b6625cf59d6e335d4aab0f425d4b7306619074/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a28d231455ab6e3558299f7d831a73c8be8ee6b7ec614ecf39eb50c0ed15767f", size = 3708798, upload-time = "2026-06-11T12:49:35.979Z" }, + { url = "https://files.pythonhosted.org/packages/23/9b/f452a189d399051d85cf82fe2f27a070efaa52512a2c5e3ae6ef1ae99a1f/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82740248eb6f3b6a38988cb5e64adb7303af9ea5cb4197c8ed08c1fabc767440", size = 3366969, upload-time = "2026-06-11T12:49:37.911Z" }, + { url = "https://files.pythonhosted.org/packages/9b/48/0075cb4f6ae7db280f461de2dbba700b22ae62e351ae13e6e461cd6804de/grpcio_tools-1.81.1-cp310-cp310-win32.whl", hash = "sha256:801d9d8ab5cddf8f8e064225292f0713427011252a07828a6b54e2ed64d534de", size = 1008713, upload-time = "2026-06-11T12:49:39.791Z" }, + { url = "https://files.pythonhosted.org/packages/17/bd/7692bc698259e5645b68720e77e7b176d376f6ae0c9db8b5b750a02f1958/grpcio_tools-1.81.1-cp310-cp310-win_amd64.whl", hash = "sha256:3c8611d6e4e859ac5373422ef27c4b7540cf98c9991c9abc6722613ef72b13aa", size = 1174752, upload-time = "2026-06-11T12:49:41.43Z" }, + { url = "https://files.pythonhosted.org/packages/18/76/14ff87090199a36f914388299a1148d0734a20cea1b0ca8480bae1f373f1/grpcio_tools-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:8161f398f957a376cae7385ea7c8684f439d460ef702b528912da3bcb31fc515", size = 2586251, upload-time = "2026-06-11T12:49:43.514Z" }, + { url = "https://files.pythonhosted.org/packages/87/a8/d5aa99de9d8b2dd2a8192c1779796eda8b0d0f1dd915422e0a8a61b80391/grpcio_tools-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:53ef76cc3b0493ff734a5e8c39d5b519e1822236fcccdfe7677c5e1efd767761", size = 5818063, upload-time = "2026-06-11T12:49:45.975Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cb/2e9a6dbc6a514dd3cd264fb3bf9217937453a4d45dbc3ca6ca4ee34ba1a7/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:690e6dcaa8b8a7886ce206ba344e2127211597e1a1ddab73df9f3d80c8f6707e", size = 2634061, upload-time = "2026-06-11T12:49:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2b/2ccd1a929e6c8ad84a0aa8d66ad9f615b4a8e79d9927373d86aa36b4ba2e/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ad7a997c07bd345e84842e60561e7e2cc090ce6c4e1d2f0407e31b85b40fc49a", size = 2958029, upload-time = "2026-06-11T12:49:50.466Z" }, + { url = "https://files.pythonhosted.org/packages/e7/67/2da8cd312edc348f44f26f82096b25cdb7d2905cd786acc6bf777b169502/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b6bd163ece4535726e5292b845ed80ae9b2cae73ba091c7d6c66033c430e3857", size = 2698031, upload-time = "2026-06-11T12:49:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ba/ad1680fbdf9317c4f1e54c37c96d1f422370df66ac9adbd175c7cb3531d7/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2baa7e735f35b2a648144c03348a126097b13e101d3c242d5edb6ac91437ccbe", size = 3147541, upload-time = "2026-06-11T12:49:54.43Z" }, + { url = "https://files.pythonhosted.org/packages/57/c1/57cd08eef293d713cb8935295e4f08d8f0013480b2ba3aad1af0271eb7ba/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1d602b410b2b2addc434cace9ce4fe2035974a3078228f98ffa049a5c90acc2f", size = 3708524, upload-time = "2026-06-11T12:49:56.544Z" }, + { url = "https://files.pythonhosted.org/packages/52/31/01ea8ca9c82fe2c79b5b594c3ae427d56699bc106b2d91caca129add8b10/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f8cb64f87c45ccca8234fa47e6b21f09e43801ff11b556deecb461b3b3e9f292", size = 3367022, upload-time = "2026-06-11T12:49:59.608Z" }, + { url = "https://files.pythonhosted.org/packages/7d/35/8140cd175602df3d17215cfb28a7ea55b7a67e2b872be76e1ee4af5c4df9/grpcio_tools-1.81.1-cp311-cp311-win32.whl", hash = "sha256:87b25ca0e27373a4a32a629a4ba976f5764b9887dd50d6fe017d38009a0363e8", size = 1008980, upload-time = "2026-06-11T12:50:01.422Z" }, + { url = "https://files.pythonhosted.org/packages/be/86/1bd29ab3c52457702b96536f1f208ab27695322d855f95c9666dfb713019/grpcio_tools-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:204de03b539a4b08772c6553b92bcc112cbc965e0ac22f909f6d133b8ac33a8c", size = 1174840, upload-time = "2026-06-11T12:50:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8a/824a9ca20bcdce8a568bb8c9f98bfeb7fad62129235e6d2ae7576fd1250a/grpcio_tools-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:353b1fafcc739c31ed42271052709595b340d34f27c459beeb78a32938305bb5", size = 2585927, upload-time = "2026-06-11T12:50:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/2f/35/e5f9f671378b1b89a896150d3e4fa2c6ec61a5e1e9e5107ce4c140ccc931/grpcio_tools-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:768f584c2423cbeb6cb6867817a39365b987ff16b8259a3adbc6546b9e303a4e", size = 5815665, upload-time = "2026-06-11T12:50:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/c6/02/631b628e4072e988c669bd8f1b2406ef3c9a4cfcb2625bbf2a308a07b71d/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1680b35a84f4694401819ac4acac42dda6dbc7bb8fc74112fd1a60425a07adf4", size = 2635518, upload-time = "2026-06-11T12:50:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/de/7c/2e3537e3ea3d1c0ddd6766cf6a7c62b487d89fb005713df2781d5f21483a/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f64e665c8ec639278ecf009beb92cbdcc5994f617c1af3d58036e1f70b1423ec", size = 2958252, upload-time = "2026-06-11T12:50:12.677Z" }, + { url = "https://files.pythonhosted.org/packages/35/68/14013cb2942bdac354746b643b4c37dd91906da8dce00f41c616e88bf33d/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f1ae82ad199f43448995715445cc623fb20d3882382e4be61f0da8ccb3f0e", size = 2698439, upload-time = "2026-06-11T12:50:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/bd/45/000c14c0338a7ad36054b9f17ea41842deb7841c05c067dd36cc831bc0f4/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7b6d1e986d5923751bfe2b5cca9c4cb3d5653446e4fa4aacd438033e2dc360a", size = 3152160, upload-time = "2026-06-11T12:50:17.3Z" }, + { url = "https://files.pythonhosted.org/packages/41/97/881930ca3967d2c8a95649bea8ebc991a7cf2331bc96679fd3600450dccc/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f208c207aca639dcb34648d3826c38d7cf3485118fb2065117e9fc4827406b3", size = 3710468, upload-time = "2026-06-11T12:50:19.479Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b5/67baeba7366162652cdc1dbd962289accde07241bc8f42f6f02b305efcc6/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724ecb69af63d2f6d4ccea3e6fa0ca110ed9c5824d48c2f887c631bbb03c1c3c", size = 3370797, upload-time = "2026-06-11T12:50:21.501Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5d/34f2dce2125ccb107e32b57f5a9c1257edcc0793b0d2fef1e8b13a6bac3c/grpcio_tools-1.81.1-cp312-cp312-win32.whl", hash = "sha256:895a6782cec86beac71ccebb4b9848259c6f04a3028b8e42fa8d40cfe5146593", size = 1008453, upload-time = "2026-06-11T12:50:23.358Z" }, + { url = "https://files.pythonhosted.org/packages/8a/be/09da8256ec8d2a5ce8a1acc51cbbc4ca52a462d78ed3412778440a56502e/grpcio_tools-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:0265fd1386b7458302f79542558345880d484f8fa92ae196c0c0268242c5f23a", size = 1174857, upload-time = "2026-06-11T12:50:25.685Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802, upload-time = "2026-03-09T13:12:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216, upload-time = "2026-03-09T13:12:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917, upload-time = "2026-03-09T13:12:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776, upload-time = "2026-03-09T13:12:41.976Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164, upload-time = "2026-03-09T13:12:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656, upload-time = "2026-03-09T13:12:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562, upload-time = "2026-03-09T13:12:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473, upload-time = "2026-03-09T13:12:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035, upload-time = "2026-03-09T13:12:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217, upload-time = "2026-03-09T13:12:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196, upload-time = "2026-03-09T13:12:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389, upload-time = "2026-03-09T13:12:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782, upload-time = "2026-03-09T13:12:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606, upload-time = "2026-03-09T13:15:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537, upload-time = "2026-03-09T13:15:42.071Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888, upload-time = "2026-03-09T13:15:43.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584, upload-time = "2026-03-09T13:15:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390, upload-time = "2026-03-09T13:15:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "cycler", marker = "python_full_version < '3.11'" }, + { name = "fonttools", marker = "python_full_version < '3.11'" }, + { name = "kiwisolver", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pillow", marker = "python_full_version < '3.11'" }, + { name = "pyparsing", marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6f/340b04986e67aac6f66c5145ce68bf72c64bed30f92c8913499a6e6b8f99/matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217", size = 8296625, upload-time = "2026-04-24T00:11:43.376Z" }, + { url = "https://files.pythonhosted.org/packages/bb/2f/127081eb83162053ebb9678ceac64220b93a663e0167432566e9c7c82aab/matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b", size = 8188790, upload-time = "2026-04-24T00:11:46.556Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b7/d8bcec2626c35f96972bff656299fef4578113ea6193c8fdad324710410c/matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37", size = 8769389, upload-time = "2026-04-24T00:11:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/b78e214a527ea732033b7f4d37f7afb504d74ba9d134bd47938230dfb8b1/matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294", size = 9589657, upload-time = "2026-04-24T00:11:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/5f/15/5246f7b43beae19c74dfee651d58d6cc8112e06f77adb4e88cc04f2e3a23/matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65", size = 9651983, upload-time = "2026-04-24T00:11:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/5acecfe672ba0fa1b8c0454f69ce155d1e6fc5852fa7206bf9afaf767121/matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda", size = 8199701, upload-time = "2026-04-24T00:11:58.389Z" }, + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2b/0e92ad0ac446633f928a1563db4aa8add407e1924faf0ded5b95b35afb27/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b", size = 8293058, upload-time = "2026-04-24T00:13:56.339Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/74682fd369f5299ceda438fea2a0662e6383b85c9383fb9cdfcf04713e07/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f", size = 8186627, upload-time = "2026-04-24T00:13:58.623Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e8/368aab88f3c4cd8992800f31abfe0670c3e47540ba20a97e9fdbcde594b3/matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585", size = 8764117, upload-time = "2026-04-24T00:14:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler", marker = "python_full_version >= '3.11'" }, + { name = "fonttools", marker = "python_full_version >= '3.11'" }, + { name = "kiwisolver", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pillow", marker = "python_full_version >= '3.11'" }, + { name = "pyparsing", marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.6.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/eb/ff4b8c503fa1f1796679dce648854d58751982426e4e4b37d6fce49d259c/nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb", size = 393138322, upload-time = "2024-11-20T17:40:25.65Z" }, + { url = "https://files.pythonhosted.org/packages/97/0d/f1f0cadbf69d5b9ef2e4f744c9466cb0a850741d08350736dfdb4aa89569/nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:235f728d6e2a409eddf1df58d5b0921cf80cfa9e72b9f2775ccb7b4a87984668", size = 390794615, upload-time = "2024-11-20T17:39:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/84/f7/985e9bdbe3e0ac9298fcc8cfa51a392862a46a0ffaccbbd56939b62a9c83/nvidia_cublas_cu12-12.6.4.1-py3-none-win_amd64.whl", hash = "sha256:9e4fa264f4d8a4eb0cdbd34beadc029f453b3bafae02401e999cf3d5a5af75f8", size = 434535301, upload-time = "2024-11-20T17:50:41.681Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.6.80" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/8b/2f6230cb715646c3a9425636e513227ce5c93c4d65823a734f4bb86d43c3/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:166ee35a3ff1587f2490364f90eeeb8da06cd867bd5b701bf7f9a02b78bc63fc", size = 8236764, upload-time = "2024-11-20T17:35:41.03Z" }, + { url = "https://files.pythonhosted.org/packages/25/0f/acb326ac8fd26e13c799e0b4f3b2751543e1834f04d62e729485872198d4/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.whl", hash = "sha256:358b4a1d35370353d52e12f0a7d1769fc01ff74a191689d3870b2123156184c4", size = 8236756, upload-time = "2024-10-01T16:57:45.507Z" }, + { url = "https://files.pythonhosted.org/packages/49/60/7b6497946d74bcf1de852a21824d63baad12cd417db4195fc1bfe59db953/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132", size = 8917980, upload-time = "2024-11-20T17:36:04.019Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/120ee57b218d9952c379d1e026c4479c9ece9997a4fb46303611ee48f038/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73", size = 8917972, upload-time = "2024-10-01T16:58:06.036Z" }, + { url = "https://files.pythonhosted.org/packages/1c/81/7796f096afaf726796b1b648f3bc80cafc61fe7f77f44a483c89e6c5ef34/nvidia_cuda_cupti_cu12-12.6.80-py3-none-win_amd64.whl", hash = "sha256:bbe6ae76e83ce5251b56e8c8e61a964f757175682bbad058b170b136266ab00a", size = 5724175, upload-time = "2024-10-01T17:09:47.955Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/2f/72df534873235983cc0a5371c3661bebef7c4682760c275590b972c7b0f9/nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5847f1d6e5b757f1d2b3991a01082a44aad6f10ab3c5c0213fa3e25bddc25a13", size = 23162955, upload-time = "2024-10-01T16:59:50.922Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/46030320b5a80661e88039f59060d1790298b4718944a65a7f2aeda3d9e9/nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53", size = 23650380, upload-time = "2024-10-01T17:00:14.643Z" }, + { url = "https://files.pythonhosted.org/packages/f5/46/d3a1cdda8bb113c80f43a0a6f3a853356d487b830f3483f92d49ce87fa55/nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:f7007dbd914c56bd80ea31bc43e8e149da38f68158f423ba845fc3292684e45a", size = 39026742, upload-time = "2024-10-01T17:10:49.058Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/ea/590b2ac00d772a8abd1c387a92b46486d2679ca6622fd25c18ff76265663/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6116fad3e049e04791c0256a9778c16237837c08b27ed8c8401e2e45de8d60cd", size = 908052, upload-time = "2024-11-20T17:35:19.905Z" }, + { url = "https://files.pythonhosted.org/packages/b7/3d/159023799677126e20c8fd580cca09eeb28d5c5a624adc7f793b9aa8bbfa/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d461264ecb429c84c8879a7153499ddc7b19b5f8d84c204307491989a365588e", size = 908040, upload-time = "2024-10-01T16:57:22.221Z" }, + { url = "https://files.pythonhosted.org/packages/e1/23/e717c5ac26d26cf39a27fbc076240fad2e3b817e5889d671b67f4f9f49c5/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7", size = 897690, upload-time = "2024-11-20T17:35:30.697Z" }, + { url = "https://files.pythonhosted.org/packages/f0/62/65c05e161eeddbafeca24dc461f47de550d9fa8a7e04eb213e32b55cfd99/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8", size = 897678, upload-time = "2024-10-01T16:57:33.821Z" }, + { url = "https://files.pythonhosted.org/packages/fa/76/4c80fa138333cc975743fd0687a745fccb30d167f906f13c1c7f9a85e5ea/nvidia_cuda_runtime_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:86c58044c824bf3c173c49a2dbc7a6c8b53cb4e4dca50068be0bf64e9dab3f7f", size = 891773, upload-time = "2024-10-01T17:09:26.362Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.5.1.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/93/a201a12d3ec1caa8c6ac34c1c2f9eeb696b886f0c36ff23c638b46603bd0/nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9fd4584468533c61873e5fda8ca41bac3a38bcb2d12350830c69b0a96a7e4def", size = 570523509, upload-time = "2024-10-25T19:53:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/2a/78/4535c9c7f859a64781e43c969a3a7e84c54634e319a996d43ef32ce46f83/nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2", size = 570988386, upload-time = "2024-10-25T19:54:26.39Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b2/3f60d15f037fa5419d9d7f788b100ef33ea913ae5315c87ca6d6fa606c35/nvidia_cudnn_cu12-9.5.1.17-py3-none-win_amd64.whl", hash = "sha256:d7af0f8a4f3b4b9dbb3122f2ef553b45694ed9c384d5a75bab197b8eefb79ab8", size = 565440743, upload-time = "2024-10-25T19:55:49.74Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/37/c50d2b2f2c07e146776389e3080f4faf70bcc4fa6e19d65bb54ca174ebc3/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d16079550df460376455cba121db6564089176d9bac9e4f360493ca4741b22a6", size = 200164144, upload-time = "2024-11-20T17:40:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f5/188566814b7339e893f8d210d3a5332352b1409815908dad6a363dcceac1/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8510990de9f96c803a051822618d42bf6cb8f069ff3f48d93a8486efdacb48fb", size = 200164135, upload-time = "2024-10-01T17:03:24.212Z" }, + { url = "https://files.pythonhosted.org/packages/8f/16/73727675941ab8e6ffd86ca3a4b7b47065edcca7a997920b831f8147c99d/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5", size = 200221632, upload-time = "2024-11-20T17:41:32.357Z" }, + { url = "https://files.pythonhosted.org/packages/60/de/99ec247a07ea40c969d904fc14f3a356b3e2a704121675b75c366b694ee1/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca", size = 200221622, upload-time = "2024-10-01T17:03:58.79Z" }, + { url = "https://files.pythonhosted.org/packages/b4/38/36fd800cec8f6e89b7c1576edaaf8076e69ec631644cdbc1b5f2e2b5a9df/nvidia_cufft_cu12-11.3.0.4-py3-none-win_amd64.whl", hash = "sha256:6048ebddfb90d09d2707efb1fd78d4e3a77cb3ae4dc60e19aab6be0ece2ae464", size = 199356881, upload-time = "2024-10-01T17:13:01.861Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.11.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/66/cc9876340ac68ae71b15c743ddb13f8b30d5244af344ec8322b449e35426/nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159", size = 1142103, upload-time = "2024-11-20T17:42:11.83Z" }, + { url = "https://files.pythonhosted.org/packages/17/bf/cc834147263b929229ce4aadd62869f0b195e98569d4c28b23edc72b85d9/nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:8f57a0051dcf2543f6dc2b98a98cb2719c37d3cee1baba8965d57f3bbc90d4db", size = 1066155, upload-time = "2024-11-20T17:41:49.376Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.7.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/ac/36543605358a355632f1a6faa3e2d5dfb91eab1e4bc7d552040e0383c335/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6e82df077060ea28e37f48a3ec442a8f47690c7499bff392a5938614b56c98d8", size = 56289881, upload-time = "2024-10-01T17:04:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/73/1b/44a01c4e70933637c93e6e1a8063d1e998b50213a6b65ac5a9169c47e98e/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf", size = 56279010, upload-time = "2024-11-20T17:42:50.958Z" }, + { url = "https://files.pythonhosted.org/packages/4a/aa/2c7ff0b5ee02eaef890c0ce7d4f74bc30901871c5e45dee1ae6d0083cd80/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117", size = 56279000, upload-time = "2024-10-01T17:04:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/5362a9396f23f7de1dd8a64369e87c85ffff8216fc8194ace0fa45ba27a5/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:7b2ed8e95595c3591d984ea3603dd66fe6ce6812b886d59049988a712ed06b6e", size = 56289882, upload-time = "2024-11-20T17:42:25.222Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a8/0cd0cec757bd4b4b4ef150fca62ec064db7d08a291dced835a0be7d2c147/nvidia_curand_cu12-10.3.7.77-py3-none-win_amd64.whl", hash = "sha256:6d6d935ffba0f3d439b7cd968192ff068fafd9018dbf1b85b37261b13cfc9905", size = 55783873, upload-time = "2024-10-01T17:13:30.377Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cusparse-cu12", marker = "(sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/17/dbe1aa865e4fdc7b6d4d0dd308fdd5aaab60f939abfc0ea1954eac4fb113/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0ce237ef60acde1efc457335a2ddadfd7610b892d94efee7b776c64bb1cac9e0", size = 157833628, upload-time = "2024-10-01T17:05:05.591Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6e/c2cf12c9ff8b872e92b4a5740701e51ff17689c4d726fca91875b07f655d/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c", size = 158229790, upload-time = "2024-11-20T17:43:43.211Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/baba53585da791d043c10084cf9553e074548408e04ae884cfe9193bd484/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6", size = 158229780, upload-time = "2024-10-01T17:05:39.875Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5f/07d0ba3b7f19be5a5ec32a8679fc9384cfd9fc6c869825e93be9f28d6690/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dbbe4fc38ec1289c7e5230e16248365e375c3673c9c8bac5796e2e20db07f56e", size = 157833630, upload-time = "2024-11-20T17:43:16.77Z" }, + { url = "https://files.pythonhosted.org/packages/d4/53/fff50a0808df7113d77e3bbc7c2b7eaed6f57d5eb80fbe93ead2aea1e09a/nvidia_cusolver_cu12-11.7.1.2-py3-none-win_amd64.whl", hash = "sha256:6813f9d8073f555444a8705f3ab0296d3e1cb37a16d694c5fc8b862a0d8706d7", size = 149287877, upload-time = "2024-10-01T17:13:49.804Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/eb/6681efd0aa7df96b4f8067b3ce7246833dd36830bb4cec8896182773db7d/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d25b62fb18751758fe3c93a4a08eff08effedfe4edf1c6bb5afd0890fe88f887", size = 216451147, upload-time = "2024-11-20T17:44:18.055Z" }, + { url = "https://files.pythonhosted.org/packages/d3/56/3af21e43014eb40134dea004e8d0f1ef19d9596a39e4d497d5a7de01669f/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7aa32fa5470cf754f72d1116c7cbc300b4e638d3ae5304cfa4a638a5b87161b1", size = 216451135, upload-time = "2024-10-01T17:06:03.826Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/b8b7c2f4099a37b96af5c9bb158632ea9e5d9d27d7391d7eb8fc45236674/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73", size = 216561367, upload-time = "2024-11-20T17:44:54.824Z" }, + { url = "https://files.pythonhosted.org/packages/43/ac/64c4316ba163e8217a99680c7605f779accffc6a4bcd0c778c12948d3707/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f", size = 216561357, upload-time = "2024-10-01T17:06:29.861Z" }, + { url = "https://files.pythonhosted.org/packages/45/ef/876ad8e4260e1128e6d4aac803d9d51baf3791ebdb4a9b8d9b8db032b4b0/nvidia_cusparse_cu12-12.5.4.2-py3-none-win_amd64.whl", hash = "sha256:4acb8c08855a26d737398cba8fb6f8f5045d93f82612b4cfd84645a2332ccf20", size = 213712630, upload-time = "2024-10-01T17:14:23.779Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/da/4de092c61c6dea1fc9c936e69308a02531d122e12f1f649825934ad651b5/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8371549623ba601a06322af2133c4a44350575f5a3108fb75f3ef20b822ad5f1", size = 156402859, upload-time = "2024-10-16T02:23:17.184Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9a/72ef35b399b0e183bc2e8f6f558036922d453c4d8237dab26c666a04244b/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46", size = 156785796, upload-time = "2024-10-15T21:29:17.709Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/9e1e394a02a06f694be2c97bbe47288bb7c90ea84c7e9cf88f7b28afe165/nvidia_cusparselt_cu12-0.6.3-py3-none-win_amd64.whl", hash = "sha256:3b325bcbd9b754ba43df5a311488fca11a6b5dc3d11df4d190c000cf1a0765c7", size = 155595972, upload-time = "2024-10-15T22:58:35.426Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.26.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/5b/ca2f213f637305633814ae8c36b153220e40a07ea001966dcd87391f3acb/nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c196e95e832ad30fbbb50381eb3cbd1fadd5675e587a548563993609af19522", size = 291671495, upload-time = "2025-03-13T00:30:07.805Z" }, + { url = "https://files.pythonhosted.org/packages/67/ca/f42388aed0fddd64ade7493dbba36e1f534d4e6fdbdd355c6a90030ae028/nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6", size = 201319755, upload-time = "2025-03-13T00:29:55.296Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/d7/c5383e47c7e9bf1c99d5bd2a8c935af2b6d705ad831a7ec5c97db4d82f4f/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a", size = 19744971, upload-time = "2024-11-20T17:46:53.366Z" }, + { url = "https://files.pythonhosted.org/packages/31/db/dc71113d441f208cdfe7ae10d4983884e13f464a6252450693365e166dcf/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cf4eaa7d4b6b543ffd69d6abfb11efdeb2db48270d94dfd3a452c24150829e41", size = 19270338, upload-time = "2024-11-20T17:46:29.758Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/93c1467b1387387440a4d25102d86b7794535449b689f8e2dc22c1c8ff7f/nvidia_nvjitlink_cu12-12.6.85-py3-none-win_amd64.whl", hash = "sha256:e61120e52ed675747825cdd16febc6a0730537451d867ee58bee3853b1b13d1c", size = 161908572, upload-time = "2024-11-20T17:52:40.124Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/93/80f8a520375af9d7ee44571a6544653a176e53c2b8ccce85b97b83c2491b/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f44f8d86bb7d5629988d61c8d3ae61dddb2015dee142740536bc7481b022fe4b", size = 90549, upload-time = "2024-11-20T17:38:17.387Z" }, + { url = "https://files.pythonhosted.org/packages/2b/53/36e2fd6c7068997169b49ffc8c12d5af5e5ff209df6e1a2c4d373b3a638f/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:adcaabb9d436c9761fca2b13959a2d237c5f9fd406c8e4b723c695409ff88059", size = 90539, upload-time = "2024-10-01T17:00:27.179Z" }, + { url = "https://files.pythonhosted.org/packages/56/9a/fff8376f8e3d084cd1530e1ef7b879bb7d6d265620c95c1b322725c694f4/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2", size = 89276, upload-time = "2024-11-20T17:38:27.621Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/0d0c945463719429b7bd21dece907ad0bde437a2ff12b9b12fee94722ab0/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1", size = 89265, upload-time = "2024-10-01T17:00:38.172Z" }, + { url = "https://files.pythonhosted.org/packages/f7/cd/98a447919d4ed14d407ac82b14b0a0c9c1dbfe81099934b1fc3bfd1e6316/nvidia_nvtx_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:2fb11a4af04a5e6c84073e6404d26588a34afd35379f0855a99797897efa75c0", size = 56434, upload-time = "2024-10-01T17:11:13.124Z" }, +] + +[[package]] +name = "opencv-python" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749, upload-time = "2026-07-02T06:59:53.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443, upload-time = "2026-07-02T05:50:25.466Z" }, + { url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755, upload-time = "2026-07-02T05:51:30.556Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064, upload-time = "2026-07-02T06:53:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711, upload-time = "2026-07-02T06:54:13.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576, upload-time = "2026-07-02T06:54:33.781Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032, upload-time = "2026-07-02T06:55:03.415Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734, upload-time = "2026-07-02T05:49:57.704Z" }, + { url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345, upload-time = "2026-07-02T05:49:54.971Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "polars" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "polars-runtime-32" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/99/fe77f10a13a778705ef05b499fc708c9a0b0a3680d9eb6bc6e1b6a6b9914/polars-1.42.1.tar.gz", hash = "sha256:2fe94f3059334650bd850ae19a9c165dcd5d9cb12cd95ea04de2201662e70e8a", size = 741532, upload-time = "2026-06-30T04:57:51.504Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/6a/edd939cc6fa04b6415aaa9bf19720fc74ead81234b3d38542e0005816d4d/polars-1.42.1-py3-none-any.whl", hash = "sha256:3c0c65cdfa21a621650c4bdcbbccf93964d052fd766c3e70e84a55d961c259fd", size = 837622, upload-time = "2026-06-30T04:56:34.686Z" }, +] + +[[package]] +name = "polars-runtime-32" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/59/15bcc4dac380c6d63efa5446d8317f22671cbd6c9dadd576bd17a334c45a/polars_runtime_32-1.42.1.tar.gz", hash = "sha256:4d4809e1c1b9a6611f6944f27b24abea902b5159e6b6fa262fd716e947af5afd", size = 3045460, upload-time = "2026-06-30T04:57:52.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/29/16ff6e4e91d71e530d3581f45e342a9cc35072ac6b31dcbc2fa33de2569e/polars_runtime_32-1.42.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bbdc26d68ee5b23b0ce227fa0599220aa35b77c826b6b0a6b2d8e7f6c1c36974", size = 53117325, upload-time = "2026-06-30T04:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/04/8e/4f8296fcfd1347f1351342fecf13bf2430d7efbae2f1f45964ec7930a99e/polars_runtime_32-1.42.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f6c0288be940b607dc4a7476c01e67fb6bbee93f5f1dd42c64970274c71008ba", size = 47446251, upload-time = "2026-06-30T04:56:41.459Z" }, + { url = "https://files.pythonhosted.org/packages/88/2e/0d66a7deadc453b890c3391034ca8ab4b05d0beaebbb92a7d65199fba61b/polars_runtime_32-1.42.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635d9dbcae2302ae223afb395d5cd220bffa61a53d0ab6871d17c8bc830101cf", size = 51359402, upload-time = "2026-06-30T04:56:44.595Z" }, + { url = "https://files.pythonhosted.org/packages/5d/10/ffb85fa380bc9c9000dc35f40f44954dde49023018501c54faab94b3a39e/polars_runtime_32-1.42.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d059e8e53cc114ff82f9bd791fd341dc53534a2c745e6f6aa37594c3a93f01fe", size = 57302723, upload-time = "2026-06-30T04:56:47.609Z" }, + { url = "https://files.pythonhosted.org/packages/c2/63/ca50adc62e44224ca5c622a842ba6f35ee87d1d40ef0df7ea2ed6c6edb08/polars_runtime_32-1.42.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f91f0b13588324905682809d270e1de5f1990c908721c8527657d77a044c9919", size = 51515673, upload-time = "2026-06-30T04:56:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/63/c3/08fbbf38deaa17bf34a601d327cb7451074098673c78b7c1a8538dde9794/polars_runtime_32-1.42.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b8bf972d99d48aaaa2582e2bce966a6f43bc815bd8725d15f5cab9e2fb15d17", size = 55217259, upload-time = "2026-06-30T04:56:53.834Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0e/51db89361668fe077a835fc579277f824ba526e7daf7b94d23d25439e0d0/polars_runtime_32-1.42.1-cp310-abi3-win_amd64.whl", hash = "sha256:e9364c26da389a8b7339e4d29e20a3d12af730247e6ed3b7804bddce2477f428", size = 52715432, upload-time = "2026-06-30T04:56:57.109Z" }, + { url = "https://files.pythonhosted.org/packages/76/c5/2fb8592d691bd114de25d9c84300b23541dca7060eac11d7b4bed0327786/polars_runtime_32-1.42.1-cp310-abi3-win_arm64.whl", hash = "sha256:7051226e6b42ffc395a7a9190377cd28649fbfb991b8f85c6271f4e1cfb736fb", size = 46718300, upload-time = "2026-06-30T04:56:59.855Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156, upload-time = "2025-03-25T06:22:28.883Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694, upload-time = "2025-03-25T06:22:27.807Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "torch" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "filelock", marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "fsspec", marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "jinja2", marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.11' and sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cublas-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cuda-cupti-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cuda-runtime-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cudnn-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cufft-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cufile-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-curand-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cusolver-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-cusparselt-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-nccl-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "nvidia-nvtx-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "setuptools", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "sympy", marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "triton", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "typing-extensions", marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/c2/3fb87940fa160d956ee94d644d37b99a24b9c05a4222bf34f94c71880e28/torch-2.7.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c9afea41b11e1a1ab1b258a5c31afbd646d6319042bfe4f231b408034b51128b", size = 99158447, upload-time = "2025-04-23T14:35:10.557Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/91d1de65573fce563f5284e69d9c56b57289625cffbbb6d533d5d56c36a5/torch-2.7.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0b9960183b6e5b71239a3e6c883d8852c304e691c0b2955f7045e8a6d05b9183", size = 865164221, upload-time = "2025-04-23T14:33:27.864Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7e/1b1cc4e0e7cc2666cceb3d250eef47a205f0821c330392cf45eb08156ce5/torch-2.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:2ad79d0d8c2a20a37c5df6052ec67c2078a2c4e9a96dd3a8b55daaff6d28ea29", size = 212521189, upload-time = "2025-04-23T14:34:53.898Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0b/b2b83f30b8e84a51bf4f96aa3f5f65fdf7c31c591cc519310942339977e2/torch-2.7.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:34e0168ed6de99121612d72224e59b2a58a83dae64999990eada7260c5dd582d", size = 68559462, upload-time = "2025-04-23T14:35:39.889Z" }, + { url = "https://files.pythonhosted.org/packages/40/da/7378d16cc636697f2a94f791cb496939b60fb8580ddbbef22367db2c2274/torch-2.7.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2b7813e904757b125faf1a9a3154e1d50381d539ced34da1992f52440567c156", size = 99159397, upload-time = "2025-04-23T14:35:35.304Z" }, + { url = "https://files.pythonhosted.org/packages/0e/6b/87fcddd34df9f53880fa1f0c23af7b6b96c935856473faf3914323588c40/torch-2.7.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fd5cfbb4c3bbadd57ad1b27d56a28008f8d8753733411a140fcfb84d7f933a25", size = 865183681, upload-time = "2025-04-23T14:34:21.802Z" }, + { url = "https://files.pythonhosted.org/packages/13/85/6c1092d4b06c3db1ed23d4106488750917156af0b24ab0a2d9951830b0e9/torch-2.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:58df8d5c2eeb81305760282b5069ea4442791a6bbf0c74d9069b7b3304ff8a37", size = 212520100, upload-time = "2025-04-23T14:35:27.473Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3f/85b56f7e2abcfa558c5fbf7b11eb02d78a4a63e6aeee2bbae3bb552abea5/torch-2.7.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:0a8d43caa342b9986101ec5feb5bbf1d86570b5caa01e9cb426378311258fdde", size = 68569377, upload-time = "2025-04-23T14:35:20.361Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5e/ac759f4c0ab7c01feffa777bd68b43d2ac61560a9770eeac074b450f81d4/torch-2.7.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:36a6368c7ace41ad1c0f69f18056020b6a5ca47bedaca9a2f3b578f5a104c26c", size = 99013250, upload-time = "2025-04-23T14:35:15.589Z" }, + { url = "https://files.pythonhosted.org/packages/9c/58/2d245b6f1ef61cf11dfc4aceeaacbb40fea706ccebac3f863890c720ab73/torch-2.7.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:15aab3e31c16feb12ae0a88dba3434a458874636f360c567caa6a91f6bfba481", size = 865042157, upload-time = "2025-04-23T14:32:56.011Z" }, + { url = "https://files.pythonhosted.org/packages/44/80/b353c024e6b624cd9ce1d66dcb9d24e0294680f95b369f19280e241a0159/torch-2.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:f56d4b2510934e072bab3ab8987e00e60e1262fb238176168f5e0c43a1320c6d", size = 212482262, upload-time = "2025-04-23T14:35:03.527Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8d/b2939e5254be932db1a34b2bd099070c509e8887e0c5a90c498a917e4032/torch-2.7.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:30b7688a87239a7de83f269333651d8e582afffce6f591fff08c046f7787296e", size = 68574294, upload-time = "2025-04-23T14:34:47.098Z" }, +] + +[[package]] +name = "torch" +version = "2.7.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", +] +dependencies = [ + { name = "filelock", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "fsspec", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "jinja2", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "setuptools", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "sympy", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "typing-extensions", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2386859dee6191a2571ce15c65c3e18008d4e6f17d5256d49b4660e5464dcae8", upload-time = "2025-05-14T03:33:25Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c98c4f48f42a2237e079f3de48e8549de2c8cf68cdcf2041564c7794bbce0b59", upload-time = "2025-04-22T18:14:18Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:f874c1ba4c834db5848eaafd6e63dfce87fb44bb2d9234978c3ad47b5b0f37dd", upload-time = "2025-04-22T18:14:19Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ce510375ed79223db3ec144fe14cbcffc8a361ac57f39674397ff2d8db3b2c21", upload-time = "2025-05-14T03:33:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6b7edcbf8bb0b9ac2e6c001434797c5ec3f25394f91eb0ed7aeeeeed9ad4500f", upload-time = "2025-04-22T18:14:20Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:e32f385dc0b5007ca410035c3b91ef4b1b34b142e9bcdb31d3f0224b7748e992", upload-time = "2025-04-22T18:14:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a845b6f3bda3c40f736847dede95d8bfec81fb7e11458cd25973ba13542cf1f6", upload-time = "2025-05-14T03:33:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:64123c05615e27368c7a7816f6e39c6d219998693beabde0b0b9cedf91b5ed8b", upload-time = "2025-04-22T18:14:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:69e25c973bdd7ea24b0fa9f9792114950afaeb8f819e5723819b923f50989175", upload-time = "2025-04-22T18:14:24Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:1d7a6f33868276770a657beec7f77c7726b4da9d0739eff1b3ae64cc9a09d8e3", upload-time = "2025-07-16T16:40:01Z" }, +] + +[[package]] +name = "torchvision" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "(python_full_version < '3.11' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')", + "python_full_version >= '3.12' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform == 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version >= '3.12' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version == '3.11.*' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", + "python_full_version < '3.11' and sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra != 'extra-12-cmvr-edge-ai-yolo-cpu'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version != '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "pillow", marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "torch", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/03/a514766f068b088180f273913e539d08e830be3ae46ef8577ea62584a27c/torchvision-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:72256f1d7ff510b16c9fb4dd488584d0693f40c792f286a9620674438a81ccca", size = 1947829, upload-time = "2025-04-23T14:42:04.652Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e5/ec4b52041cd8c440521b75864376605756bd2d112d6351ea6a1ab25008c1/torchvision-0.22.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:810ea4af3bc63cf39e834f91f4218ff5999271caaffe2456247df905002bd6c0", size = 2512604, upload-time = "2025-04-23T14:41:56.515Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9e/e898a377e674da47e95227f3d7be2c49550ce381eebd8c7831c1f8bb7d39/torchvision-0.22.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6fbca169c690fa2b9b8c39c0ad76d5b8992296d0d03df01e11df97ce12b4e0ac", size = 7446399, upload-time = "2025-04-23T14:41:49.793Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ec/2cdb90c6d9d61410b3df9ca67c210b60bf9b07aac31f800380b20b90386c/torchvision-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:8c869df2e8e00f7b1d80a34439e6d4609b50fe3141032f50b38341ec2b59404e", size = 1716700, upload-time = "2025-04-23T14:42:03.562Z" }, + { url = "https://files.pythonhosted.org/packages/b1/43/28bc858b022f6337326d75f4027d2073aad5432328f01ee1236d847f1b82/torchvision-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:191ea28321fc262d8aa1a7fe79c41ff2848864bf382f9f6ea45c41dde8313792", size = 1947828, upload-time = "2025-04-23T14:42:00.439Z" }, + { url = "https://files.pythonhosted.org/packages/7e/71/ce9a303b94e64fe25d534593522ffc76848c4e64c11e4cbe9f6b8d537210/torchvision-0.22.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6c5620e10ffe388eb6f4744962106ed7cf1508d26e6fdfa0c10522d3249aea24", size = 2514016, upload-time = "2025-04-23T14:41:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/09/42/6908bff012a1dcc4fc515e52339652d7f488e208986542765c02ea775c2f/torchvision-0.22.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ce292701c77c64dd3935e3e31c722c3b8b176a75f76dc09b804342efc1db5494", size = 7447546, upload-time = "2025-04-23T14:41:47.297Z" }, + { url = "https://files.pythonhosted.org/packages/e4/cf/8f9305cc0ea26badbbb3558ecae54c04a245429f03168f7fad502f8a5b25/torchvision-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:e4017b5685dbab4250df58084f07d95e677b2f3ed6c2e507a1afb8eb23b580ca", size = 1716472, upload-time = "2025-04-23T14:42:01.999Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ea/887d1d61cf4431a46280972de665f350af1898ce5006cd046326e5d0a2f2/torchvision-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31c3165418fe21c3d81fe3459e51077c2f948801b8933ed18169f54652796a0f", size = 1947826, upload-time = "2025-04-23T14:41:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/21f8b6122e13ae045b8e49658029c695fd774cd21083b3fa5c3f9c5d3e35/torchvision-0.22.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8f116bc82e0c076e70ba7776e611ed392b9666aa443662e687808b08993d26af", size = 2514571, upload-time = "2025-04-23T14:41:53.458Z" }, + { url = "https://files.pythonhosted.org/packages/7c/48/5f7617f6c60d135f86277c53f9d5682dfa4e66f4697f505f1530e8b69fb1/torchvision-0.22.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ce4dc334ebd508de2c534817c9388e928bc2500cf981906ae8d6e2ca3bf4727a", size = 7446522, upload-time = "2025-04-23T14:41:34.9Z" }, + { url = "https://files.pythonhosted.org/packages/99/94/a015e93955f5d3a68689cc7c385a3cfcd2d62b84655d18b61f32fb04eb67/torchvision-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:24b8c9255c209ca419cc7174906da2791c8b557b75c23496663ec7d73b55bebf", size = 1716664, upload-time = "2025-04-23T14:41:58.019Z" }, +] + +[[package]] +name = "torchvision" +version = "0.22.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version >= '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version != '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (python_full_version < '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "pillow", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torch", version = "2.7.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.0%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1070b55bed045e0f41afe8d6617fc1b6d47088aa3b3430d87f8fcac48f43386", upload-time = "2025-04-22T18:30:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.0%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:619f1a227797622aaffc781d694e66e682ab522fc6de75ac3551641326675cfa", upload-time = "2025-04-22T18:30:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:670082705cfb51a35ae35090b5a0e66ec09e6d9c3845e16417399adec7a17ff2", upload-time = "2025-04-22T18:30:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:b180a2c568665b8ffa442ff3d7dfdba0eaf99c0ea22e987f6c47ade3cc13eadf", upload-time = "2025-04-22T18:30:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:effb34eabe87ae0d811fe2d6e3433ed1eee2d1850ff0670964d1475a06512c73", upload-time = "2025-04-22T18:30:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:e74be2e4efe2253cd145a80a7c21defe2e48125a114445f06bf02640f6579109", upload-time = "2025-04-22T18:30:17Z" }, +] + +[[package]] +name = "triton" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools", marker = "(sys_platform != 'win32' and extra == 'extra-12-cmvr-edge-ai-yolo') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/04/d54d3a6d077c646624dc9461b0059e23fd5d30e0dbe67471e3654aec81f9/triton-3.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fad99beafc860501d7fcc1fb7045d9496cbe2c882b1674640304949165a916e7", size = 156441993, upload-time = "2025-04-09T20:27:25.107Z" }, + { url = "https://files.pythonhosted.org/packages/3c/c5/4874a81131cc9e934d88377fbc9d24319ae1fb540f3333b4e9c696ebc607/triton-3.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3161a2bf073d6b22c4e2f33f951f3e5e3001462b2570e6df9cd57565bdec2984", size = 156528461, upload-time = "2025-04-09T20:27:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/53/ce18470914ab6cfbec9384ee565d23c4d1c55f0548160b1c7b33000b11fd/triton-3.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b68c778f6c4218403a6bd01be7484f6dc9e20fe2083d22dd8aef33e3b87a10a3", size = 156504509, upload-time = "2025-04-09T20:27:40.413Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "ultralytics" +version = "8.4.31" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version >= '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "polars" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torch", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "torch", version = "2.7.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torchvision", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "torchvision", version = "0.22.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "ultralytics-thop" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/89/e3ccefc5cc19fe45c963f82919238e5d40e46ed77f49a0aca50c68fccb75/ultralytics-8.4.31.tar.gz", hash = "sha256:2ebdccde73e7abbcfd2488d3d96818e9f130e8f47a808022a5afc8d9a2b28d51", size = 1028212, upload-time = "2026-03-28T20:16:49.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/3e/686d39b3bda7bf26899e6dd872389282ca4c5f20b7a3e67ac803e41da251/ultralytics-8.4.31-py3-none-any.whl", hash = "sha256:4cbc3bbe1fd024c2acf0b3e38b5cdf60d98515c63f1d957adf79212dd597871f", size = 1218848, upload-time = "2026-03-28T20:16:46.163Z" }, +] + +[[package]] +name = "ultralytics-thop" +version = "2.0.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version < '3.11' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version == '3.11.*' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo') or (python_full_version >= '3.12' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, + { name = "torch", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or extra == 'extra-12-cmvr-edge-ai-yolo'" }, + { name = "torch", version = "2.7.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (platform_machine != 'x86_64' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu') or (sys_platform != 'linux' and extra == 'extra-12-cmvr-edge-ai-yolo' and extra == 'extra-12-cmvr-edge-ai-yolo-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c6/d25cb53e141242f74950744179c21b8798bb09b9e7161465ecda4f577ddf/ultralytics_thop-2.0.20.tar.gz", hash = "sha256:f3595e0d8c6fd0b9f62fc2cd9be921755e2649a05c34f1fabaea0bff7295d641", size = 34682, upload-time = "2026-06-06T11:42:42.184Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/a9/5441a28af447c0ffe193f1f42dfebe2a518ab3e160e9e0d164e8901edead/ultralytics_thop-2.0.20-py3-none-any.whl", hash = "sha256:ef1b326404aeb10954f50db58982ec8dcab5946ee04a9b10e1107ec7ba4d5c22", size = 28954, upload-time = "2026-06-06T11:42:40.796Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +]