From 515eb3f09cddd25a0b6f2ba0cb6a815426596b31 Mon Sep 17 00:00:00 2001 From: xtkuang <87661715@qq.com> Date: Mon, 20 Jul 2026 16:59:37 +0800 Subject: [PATCH] project init --- README.md | 421 ++++ configs/smoke.yaml | 41 + detect_server/README.md | 293 +++ detect_server/pipeline.yaml | 180 ++ docs/architecture.md | 812 ++++++++ pyproject.toml | 91 + .../generate_cmvr_stubs.cpython-312.pyc | Bin 0 -> 5353 bytes scripts/bootstrap.sh | 161 ++ scripts/generate_cmvr_stubs.py | 117 ++ .../agv_command_pb2.cpython-310.pyc | Bin 0 -> 5641 bytes .../agv_command_pb2.cpython-312.pyc | Bin 0 -> 7477 bytes .../agv_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 811 bytes .../agv_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1010 bytes .../agv_service_pb2.cpython-310.pyc | Bin 0 -> 3090 bytes .../agv_service_pb2.cpython-312.pyc | Bin 0 -> 3513 bytes .../agv_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 13824 bytes .../agv_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 33298 bytes .../arm_command_pb2.cpython-310.pyc | Bin 0 -> 5929 bytes .../arm_command_pb2.cpython-312.pyc | Bin 0 -> 7922 bytes .../arm_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 811 bytes .../arm_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1010 bytes .../arm_service_pb2.cpython-310.pyc | Bin 0 -> 2199 bytes .../arm_service_pb2.cpython-312.pyc | Bin 0 -> 2622 bytes .../arm_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 8870 bytes .../arm_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 21152 bytes .../biohead_command_pb2.cpython-310.pyc | Bin 0 -> 6585 bytes .../biohead_command_pb2.cpython-312.pyc | Bin 0 -> 8938 bytes .../biohead_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 819 bytes .../biohead_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1018 bytes .../biohead_service_pb2.cpython-310.pyc | Bin 0 -> 2208 bytes .../biohead_service_pb2.cpython-312.pyc | Bin 0 -> 2632 bytes .../biohead_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 8980 bytes .../biohead_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 20872 bytes .../camera_command_pb2.cpython-310.pyc | Bin 0 -> 6303 bytes .../camera_command_pb2.cpython-312.pyc | Bin 0 -> 8375 bytes .../camera_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 817 bytes .../camera_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1016 bytes .../camera_service_pb2.cpython-310.pyc | Bin 0 -> 2335 bytes .../camera_service_pb2.cpython-312.pyc | Bin 0 -> 2759 bytes .../camera_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 8454 bytes .../camera_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 19034 bytes .../__pycache__/common_pb2.cpython-310.pyc | Bin 0 -> 1965 bytes .../__pycache__/common_pb2.cpython-312.pyc | Bin 0 -> 2565 bytes .../common_pb2_grpc.cpython-310.pyc | Bin 0 -> 801 bytes .../common_pb2_grpc.cpython-312.pyc | Bin 0 -> 1000 bytes .../dexhand_command_pb2.cpython-310.pyc | Bin 0 -> 5196 bytes .../dexhand_command_pb2.cpython-312.pyc | Bin 0 -> 7039 bytes .../dexhand_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 819 bytes .../dexhand_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1018 bytes .../dexhand_service_pb2.cpython-310.pyc | Bin 0 -> 2036 bytes .../dexhand_service_pb2.cpython-312.pyc | Bin 0 -> 2460 bytes .../dexhand_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 6993 bytes .../dexhand_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 15003 bytes .../hlc_command_pb2.cpython-310.pyc | Bin 0 -> 1377 bytes .../hlc_command_pb2.cpython-312.pyc | Bin 0 -> 1887 bytes .../hlc_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 811 bytes .../hlc_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1010 bytes .../hlc_service_pb2.cpython-310.pyc | Bin 0 -> 1203 bytes .../hlc_service_pb2.cpython-312.pyc | Bin 0 -> 1625 bytes .../hlc_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 3047 bytes .../hlc_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 4346 bytes .../microphone_command_pb2.cpython-310.pyc | Bin 0 -> 3682 bytes .../microphone_command_pb2.cpython-312.pyc | Bin 0 -> 5029 bytes ...icrophone_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 825 bytes ...icrophone_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1024 bytes .../microphone_service_pb2.cpython-310.pyc | Bin 0 -> 1913 bytes .../microphone_service_pb2.cpython-312.pyc | Bin 0 -> 2337 bytes ...icrophone_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 6287 bytes ...icrophone_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 13216 bytes .../speaker_command_pb2.cpython-310.pyc | Bin 0 -> 3899 bytes .../speaker_command_pb2.cpython-312.pyc | Bin 0 -> 5332 bytes .../speaker_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 819 bytes .../speaker_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1018 bytes .../speaker_service_pb2.cpython-310.pyc | Bin 0 -> 1852 bytes .../speaker_service_pb2.cpython-312.pyc | Bin 0 -> 2276 bytes .../speaker_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 6243 bytes .../speaker_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 13260 bytes .../system_command_pb2.cpython-310.pyc | Bin 0 -> 2865 bytes .../system_command_pb2.cpython-312.pyc | Bin 0 -> 3859 bytes .../system_command_pb2_grpc.cpython-310.pyc | Bin 0 -> 817 bytes .../system_command_pb2_grpc.cpython-312.pyc | Bin 0 -> 1016 bytes .../system_service_pb2.cpython-310.pyc | Bin 0 -> 1556 bytes .../system_service_pb2.cpython-312.pyc | Bin 0 -> 1980 bytes .../system_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 4664 bytes .../system_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 8830 bytes .../test_service_pb2.cpython-310.pyc | Bin 0 -> 1248 bytes .../test_service_pb2.cpython-312.pyc | Bin 0 -> 1750 bytes .../test_service_pb2_grpc.cpython-310.pyc | Bin 0 -> 3035 bytes .../test_service_pb2_grpc.cpython-312.pyc | Bin 0 -> 4190 bytes src/cmvr/api/agv_command_pb2.py | 102 + src/cmvr/api/agv_command_pb2.pyi | 215 ++ src/cmvr/api/agv_command_pb2_grpc.py | 24 + src/cmvr/api/agv_service_pb2.py | 38 + src/cmvr/api/agv_service_pb2.pyi | 6 + src/cmvr/api/agv_service_pb2_grpc.py | 943 +++++++++ src/cmvr/api/arm_command_pb2.py | 109 + src/cmvr/api/arm_command_pb2.pyi | 318 +++ src/cmvr/api/arm_command_pb2_grpc.py | 24 + src/cmvr/api/arm_service_pb2.py | 38 + src/cmvr/api/arm_service_pb2.pyi | 6 + src/cmvr/api/arm_service_pb2_grpc.py | 614 ++++++ src/cmvr/api/biohead_command_pb2.py | 125 ++ src/cmvr/api/biohead_command_pb2.pyi | 308 +++ src/cmvr/api/biohead_command_pb2_grpc.py | 24 + src/cmvr/api/biohead_service_pb2.py | 37 + src/cmvr/api/biohead_service_pb2.pyi | 5 + src/cmvr/api/biohead_service_pb2_grpc.py | 581 ++++++ src/cmvr/api/camera_command_pb2.py | 111 + src/cmvr/api/camera_command_pb2.pyi | 273 +++ src/cmvr/api/camera_command_pb2_grpc.py | 24 + src/cmvr/api/camera_service_pb2.py | 37 + src/cmvr/api/camera_service_pb2.pyi | 5 + src/cmvr/api/camera_service_pb2_grpc.py | 527 +++++ src/cmvr/api/common_pb2.py | 47 + src/cmvr/api/common_pb2.pyi | 66 + src/cmvr/api/common_pb2_grpc.py | 24 + src/cmvr/api/dexhand_command_pb2.py | 99 + src/cmvr/api/dexhand_command_pb2.pyi | 222 ++ src/cmvr/api/dexhand_command_pb2_grpc.py | 24 + src/cmvr/api/dexhand_service_pb2.py | 37 + src/cmvr/api/dexhand_service_pb2.pyi | 5 + src/cmvr/api/dexhand_service_pb2_grpc.py | 399 ++++ src/cmvr/api/hlc_command_pb2.py | 41 + src/cmvr/api/hlc_command_pb2.pyi | 27 + src/cmvr/api/hlc_command_pb2_grpc.py | 24 + src/cmvr/api/hlc_service_pb2.py | 37 + src/cmvr/api/hlc_service_pb2.pyi | 5 + src/cmvr/api/hlc_service_pb2_grpc.py | 97 + src/cmvr/api/microphone_command_pb2.py | 79 + src/cmvr/api/microphone_command_pb2.pyi | 127 ++ src/cmvr/api/microphone_command_pb2_grpc.py | 24 + src/cmvr/api/microphone_service_pb2.py | 37 + src/cmvr/api/microphone_service_pb2.pyi | 5 + src/cmvr/api/microphone_service_pb2_grpc.py | 357 ++++ src/cmvr/api/speaker_command_pb2.py | 83 + src/cmvr/api/speaker_command_pb2.pyi | 154 ++ src/cmvr/api/speaker_command_pb2_grpc.py | 24 + src/cmvr/api/speaker_service_pb2.py | 37 + src/cmvr/api/speaker_service_pb2.pyi | 5 + src/cmvr/api/speaker_service_pb2_grpc.py | 357 ++++ src/cmvr/api/system_command_pb2.py | 63 + src/cmvr/api/system_command_pb2.pyi | 116 ++ src/cmvr/api/system_command_pb2_grpc.py | 24 + src/cmvr/api/system_service_pb2.py | 37 + src/cmvr/api/system_service_pb2.pyi | 5 + src/cmvr/api/system_service_pb2_grpc.py | 226 ++ src/cmvr/api/test_service_pb2.py | 40 + src/cmvr/api/test_service_pb2.pyi | 17 + src/cmvr/api/test_service_pb2_grpc.py | 97 + .../__pycache__/geometry_pb2.cpython-310.pyc | Bin 0 -> 2670 bytes .../__pycache__/geometry_pb2.cpython-312.pyc | Bin 0 -> 3424 bytes .../geometry_pb2_grpc.cpython-310.pyc | Bin 0 -> 811 bytes .../geometry_pb2_grpc.cpython-312.pyc | Bin 0 -> 1010 bytes src/cmvr/common/geometry_pb2.py | 52 + src/cmvr/common/geometry_pb2.pyi | 140 ++ src/cmvr/common/geometry_pb2_grpc.py | 24 + .../joint_limits_config_pb2.cpython-310.pyc | Bin 0 -> 1492 bytes .../joint_limits_config_pb2.cpython-312.pyc | Bin 0 -> 2000 bytes ...int_limits_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 833 bytes ...int_limits_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1032 bytes .../lawba_ik_config_pb2.cpython-310.pyc | Bin 0 -> 1395 bytes .../lawba_ik_config_pb2.cpython-312.pyc | Bin 0 -> 1819 bytes .../lawba_ik_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 825 bytes .../lawba_ik_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1024 bytes ...inocchio_dls_ik_config_pb2.cpython-310.pyc | Bin 0 -> 1712 bytes ...inocchio_dls_ik_config_pb2.cpython-312.pyc | Bin 0 -> 2180 bytes ...hio_dls_ik_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 841 bytes ...hio_dls_ik_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1040 bytes ...pinocchio_qp_ik_config_pb2.cpython-310.pyc | Bin 0 -> 1293 bytes ...pinocchio_qp_ik_config_pb2.cpython-312.pyc | Bin 0 -> 1713 bytes ...chio_qp_ik_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 839 bytes ...chio_qp_ik_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1038 bytes .../srs_ik_config_pb2.cpython-310.pyc | Bin 0 -> 1225 bytes .../srs_ik_config_pb2.cpython-312.pyc | Bin 0 -> 1643 bytes .../srs_ik_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 821 bytes .../srs_ik_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1020 bytes ...creen_algorithm_config_pb2.cpython-310.pyc | Bin 0 -> 2427 bytes ...creen_algorithm_config_pb2.cpython-312.pyc | Bin 0 -> 2896 bytes ..._algorithm_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 853 bytes ..._algorithm_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1052 bytes ...h_screen_common_config_pb2.cpython-310.pyc | Bin 0 -> 2354 bytes ...h_screen_common_config_pb2.cpython-312.pyc | Bin 0 -> 2994 bytes ...een_common_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 847 bytes ...een_common_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1046 bytes .../agv_config_pb2.cpython-310.pyc | Bin 0 -> 2037 bytes .../agv_config_pb2.cpython-312.pyc | Bin 0 -> 2629 bytes .../agv_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 837 bytes .../agv_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1036 bytes src/cmvr/config/agv_config/agv_config_pb2.py | 44 + src/cmvr/config/agv_config/agv_config_pb2.pyi | 79 + .../config/agv_config/agv_config_pb2_grpc.py | 24 + .../arm_config_pb2.cpython-310.pyc | Bin 0 -> 6594 bytes .../arm_config_pb2.cpython-312.pyc | Bin 0 -> 7810 bytes .../arm_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 837 bytes .../arm_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1036 bytes src/cmvr/config/arm_config/arm_config_pb2.py | 78 + src/cmvr/config/arm_config/arm_config_pb2.pyi | 260 +++ .../config/arm_config/arm_config_pb2_grpc.py | 24 + .../aubo_robot_config_pb2.cpython-310.pyc | Bin 0 -> 1226 bytes .../aubo_robot_config_pb2.cpython-312.pyc | Bin 0 -> 1646 bytes ...aubo_robot_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 865 bytes ...aubo_robot_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1064 bytes .../aubo_robot_config_pb2.py | 36 + .../aubo_robot_config_pb2.pyi | 19 + .../aubo_robot_config_pb2_grpc.py | 24 + .../biohead_config_pb2.cpython-310.pyc | Bin 0 -> 1602 bytes .../biohead_config_pb2.cpython-312.pyc | Bin 0 -> 2110 bytes .../biohead_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 853 bytes .../biohead_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1052 bytes .../biohead_config/biohead_config_pb2.py | 40 + .../biohead_config/biohead_config_pb2.pyi | 43 + .../biohead_config/biohead_config_pb2_grpc.py | 24 + .../camera_config_pb2.cpython-310.pyc | Bin 0 -> 2763 bytes .../camera_config_pb2.cpython-312.pyc | Bin 0 -> 3527 bytes .../camera_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 849 bytes .../camera_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1048 bytes .../config/camera_config/camera_config_pb2.py | 52 + .../camera_config/camera_config_pb2.pyi | 123 ++ .../camera_config/camera_config_pb2_grpc.py | 24 + .../cmvr_es_config_pb2.cpython-310.pyc | Bin 0 -> 1383 bytes .../cmvr_es_config_pb2.cpython-312.pyc | Bin 0 -> 1845 bytes .../cmvr_es_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 853 bytes .../cmvr_es_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1052 bytes .../cmvr_es_config/cmvr_es_config_pb2.py | 38 + .../cmvr_es_config/cmvr_es_config_pb2.pyi | 24 + .../cmvr_es_config/cmvr_es_config_pb2_grpc.py | 24 + .../device_manager_config_pb2.cpython-310.pyc | Bin 0 -> 2231 bytes .../device_manager_config_pb2.cpython-312.pyc | Bin 0 -> 2783 bytes ...ce_manager_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 881 bytes ...ce_manager_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1080 bytes .../device_manager_config_pb2.py | 42 + .../device_manager_config_pb2.pyi | 58 + .../device_manager_config_pb2_grpc.py | 24 + .../dexhand_config_pb2.cpython-310.pyc | Bin 0 -> 2424 bytes .../dexhand_config_pb2.cpython-312.pyc | Bin 0 -> 3062 bytes .../dexhand_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 853 bytes .../dexhand_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1052 bytes .../dexhand_config/dexhand_config_pb2.py | 46 + .../dexhand_config/dexhand_config_pb2.pyi | 89 + .../dexhand_config/dexhand_config_pb2_grpc.py | 24 + .../grpc_server_config_pb2.cpython-310.pyc | Bin 0 -> 1362 bytes .../grpc_server_config_pb2.cpython-312.pyc | Bin 0 -> 1826 bytes ...rpc_server_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 869 bytes ...rpc_server_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1068 bytes .../grpc_server_config_pb2.py | 38 + .../grpc_server_config_pb2.pyi | 24 + .../grpc_server_config_pb2_grpc.py | 24 + src/cmvr/config/joint_limits_config_pb2.py | 40 + src/cmvr/config/joint_limits_config_pb2.pyi | 37 + .../config/joint_limits_config_pb2_grpc.py | 24 + src/cmvr/config/lawba_ik_config_pb2.py | 37 + src/cmvr/config/lawba_ik_config_pb2.pyi | 25 + src/cmvr/config/lawba_ik_config_pb2_grpc.py | 24 + .../logger_config_pb2.cpython-310.pyc | Bin 0 -> 1997 bytes .../logger_config_pb2.cpython-312.pyc | Bin 0 -> 2585 bytes .../logger_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 849 bytes .../logger_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1048 bytes .../config/logger_config/logger_config_pb2.py | 44 + .../logger_config/logger_config_pb2.pyi | 67 + .../logger_config/logger_config_pb2_grpc.py | 24 + .../microphone_config_pb2.cpython-310.pyc | Bin 0 -> 1692 bytes .../microphone_config_pb2.cpython-312.pyc | Bin 0 -> 2244 bytes ...microphone_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 865 bytes ...microphone_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1064 bytes .../microphone_config_pb2.py | 42 + .../microphone_config_pb2.pyi | 41 + .../microphone_config_pb2_grpc.py | 24 + .../motor_config_pb2.cpython-310.pyc | Bin 0 -> 2342 bytes .../motor_config_pb2.cpython-312.pyc | Bin 0 -> 3026 bytes .../motor_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 845 bytes .../motor_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1044 bytes .../config/motor_config/motor_config_pb2.py | 49 + .../config/motor_config/motor_config_pb2.pyi | 84 + .../motor_config/motor_config_pb2_grpc.py | 24 + .../config/pinocchio_dls_ik_config_pb2.py | 39 + .../config/pinocchio_dls_ik_config_pb2.pyi | 43 + .../pinocchio_dls_ik_config_pb2_grpc.py | 24 + src/cmvr/config/pinocchio_qp_ik_config_pb2.py | 36 + .../config/pinocchio_qp_ik_config_pb2.pyi | 26 + .../config/pinocchio_qp_ik_config_pb2_grpc.py | 24 + .../speaker_config_pb2.cpython-310.pyc | Bin 0 -> 1553 bytes .../speaker_config_pb2.cpython-312.pyc | Bin 0 -> 2105 bytes .../speaker_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 853 bytes .../speaker_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1052 bytes .../speaker_config/speaker_config_pb2.py | 42 + .../speaker_config/speaker_config_pb2.pyi | 33 + .../speaker_config/speaker_config_pb2_grpc.py | 24 + src/cmvr/config/srs_ik_config_pb2.py | 36 + src/cmvr/config/srs_ik_config_pb2.pyi | 25 + src/cmvr/config/srs_ik_config_pb2_grpc.py | 24 + .../task_manager_config_pb2.cpython-310.pyc | Bin 0 -> 1964 bytes .../task_manager_config_pb2.cpython-312.pyc | Bin 0 -> 2560 bytes ...sk_manager_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 873 bytes ...sk_manager_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1072 bytes .../task_manager_config_pb2.py | 44 + .../task_manager_config_pb2.pyi | 52 + .../task_manager_config_pb2_grpc.py | 24 + .../touch_screen_algorithm_config_pb2.py | 40 + .../touch_screen_algorithm_config_pb2.pyi | 50 + .../touch_screen_algorithm_config_pb2_grpc.py | 24 + .../config/touch_screen_common_config_pb2.py | 46 + .../config/touch_screen_common_config_pb2.pyi | 61 + .../touch_screen_common_config_pb2_grpc.py | 24 + ...uch_screen_task_config_pb2.cpython-310.pyc | Bin 0 -> 5260 bytes ...uch_screen_task_config_pb2.cpython-312.pyc | Bin 0 -> 6225 bytes ...creen_task_config_pb2_grpc.cpython-310.pyc | Bin 0 -> 893 bytes ...creen_task_config_pb2_grpc.cpython-312.pyc | Bin 0 -> 1092 bytes .../touch_screen_task_config_pb2.py | 63 + .../touch_screen_task_config_pb2.pyi | 158 ++ .../touch_screen_task_config_pb2_grpc.py | 24 + .../msgs/__pycache__/agv_pb2.cpython-310.pyc | Bin 0 -> 5304 bytes .../msgs/__pycache__/agv_pb2.cpython-312.pyc | Bin 0 -> 6730 bytes .../__pycache__/agv_pb2_grpc.cpython-310.pyc | Bin 0 -> 797 bytes .../__pycache__/agv_pb2_grpc.cpython-312.pyc | Bin 0 -> 996 bytes .../can_card_parameter_pb2.cpython-310.pyc | Bin 0 -> 2304 bytes .../can_card_parameter_pb2.cpython-312.pyc | Bin 0 -> 2944 bytes ...an_card_parameter_pb2_grpc.cpython-310.pyc | Bin 0 -> 827 bytes ...an_card_parameter_pb2_grpc.cpython-312.pyc | Bin 0 -> 1026 bytes .../__pycache__/canopen_pb2.cpython-310.pyc | Bin 0 -> 3836 bytes .../__pycache__/canopen_pb2.cpython-312.pyc | Bin 0 -> 4553 bytes .../canopen_pb2_grpc.cpython-310.pyc | Bin 0 -> 805 bytes .../canopen_pb2_grpc.cpython-312.pyc | Bin 0 -> 1004 bytes .../error_code_pb2.cpython-310.pyc | Bin 0 -> 1388 bytes .../error_code_pb2.cpython-312.pyc | Bin 0 -> 1848 bytes .../error_code_pb2_grpc.cpython-310.pyc | Bin 0 -> 811 bytes .../error_code_pb2_grpc.cpython-312.pyc | Bin 0 -> 1010 bytes .../__pycache__/motor_pb2.cpython-310.pyc | Bin 0 -> 2497 bytes .../__pycache__/motor_pb2.cpython-312.pyc | Bin 0 -> 2960 bytes .../motor_pb2_grpc.cpython-310.pyc | Bin 0 -> 801 bytes .../motor_pb2_grpc.cpython-312.pyc | Bin 0 -> 1000 bytes .../robot_detail_pb2.cpython-310.pyc | Bin 0 -> 1460 bytes .../robot_detail_pb2.cpython-312.pyc | Bin 0 -> 1971 bytes .../robot_detail_pb2_grpc.cpython-310.pyc | Bin 0 -> 815 bytes .../robot_detail_pb2_grpc.cpython-312.pyc | Bin 0 -> 1014 bytes src/cmvr/msgs/agv_pb2.py | 82 + src/cmvr/msgs/agv_pb2.pyi | 319 +++ src/cmvr/msgs/agv_pb2_grpc.py | 24 + src/cmvr/msgs/can_card_parameter_pb2.py | 46 + src/cmvr/msgs/can_card_parameter_pb2.pyi | 76 + src/cmvr/msgs/can_card_parameter_pb2_grpc.py | 24 + src/cmvr/msgs/canopen_pb2.py | 50 + src/cmvr/msgs/canopen_pb2.pyi | 225 ++ src/cmvr/msgs/canopen_pb2_grpc.py | 24 + src/cmvr/msgs/error_code_pb2.py | 38 + src/cmvr/msgs/error_code_pb2.pyi | 35 + src/cmvr/msgs/error_code_pb2_grpc.py | 24 + src/cmvr/msgs/motor_pb2.py | 39 + src/cmvr/msgs/motor_pb2.pyi | 123 ++ src/cmvr/msgs/motor_pb2_grpc.py | 24 + src/cmvr/msgs/robot_detail_pb2.py | 42 + src/cmvr/msgs/robot_detail_pb2.pyi | 22 + src/cmvr/msgs/robot_detail_pb2_grpc.py | 24 + src/cmvr_edge_ai.egg-info/PKG-INFO | 405 ++++ src/cmvr_edge_ai.egg-info/SOURCES.txt | 207 ++ .../dependency_links.txt | 1 + src/cmvr_edge_ai.egg-info/entry_points.txt | 2 + src/cmvr_edge_ai.egg-info/requires.txt | 25 + src/cmvr_edge_ai.egg-info/top_level.txt | 2 + src/cmvr_edge_ai/__init__.py | 3 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 230 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 199 bytes .../__pycache__/application.cpython-310.pyc | Bin 0 -> 6782 bytes .../__pycache__/application.cpython-312.pyc | Bin 0 -> 10669 bytes .../__pycache__/cli.cpython-310.pyc | Bin 0 -> 4120 bytes .../__pycache__/cli.cpython-312.pyc | Bin 0 -> 7163 bytes .../__pycache__/compiler.cpython-310.pyc | Bin 0 -> 11614 bytes .../__pycache__/compiler.cpython-312.pyc | Bin 0 -> 17964 bytes src/cmvr_edge_ai/application.py | 199 ++ src/cmvr_edge_ai/cli.py | 122 ++ src/cmvr_edge_ai/compiler.py | 375 ++++ src/cmvr_edge_ai/config/__init__.py | 45 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 815 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 853 bytes .../config/__pycache__/loader.cpython-310.pyc | Bin 0 -> 4684 bytes .../config/__pycache__/loader.cpython-312.pyc | Bin 0 -> 6086 bytes .../config/__pycache__/models.cpython-310.pyc | Bin 0 -> 13624 bytes .../config/__pycache__/models.cpython-312.pyc | Bin 0 -> 18420 bytes src/cmvr_edge_ai/config/loader.py | 145 ++ src/cmvr_edge_ai/config/models.py | 363 ++++ src/cmvr_edge_ai/connectors/__init__.py | 42 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1362 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1787 bytes .../__pycache__/_context.cpython-310.pyc | Bin 0 -> 3311 bytes .../__pycache__/_context.cpython-312.pyc | Bin 0 -> 4839 bytes src/cmvr_edge_ai/connectors/_context.py | 94 + .../connectors/cmvr_es/__init__.py | 13 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 491 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 468 bytes .../cmvr_es/__pycache__/agv.cpython-310.pyc | Bin 0 -> 7476 bytes .../cmvr_es/__pycache__/agv.cpython-312.pyc | Bin 0 -> 11922 bytes .../__pycache__/camera.cpython-310.pyc | Bin 0 -> 15142 bytes .../__pycache__/camera.cpython-312.pyc | Bin 0 -> 28343 bytes .../__pycache__/errors.cpython-310.pyc | Bin 0 -> 658 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 702 bytes src/cmvr_edge_ai/connectors/cmvr_es/agv.py | 230 ++ src/cmvr_edge_ai/connectors/cmvr_es/camera.py | 591 ++++++ src/cmvr_edge_ai/connectors/cmvr_es/errors.py | 13 + .../connectors/platform/__init__.py | 5 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 306 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 279 bytes .../__pycache__/http_json.cpython-310.pyc | Bin 0 -> 7929 bytes .../__pycache__/http_json.cpython-312.pyc | Bin 0 -> 13063 bytes .../connectors/platform/http_json.py | 260 +++ src/cmvr_edge_ai/contracts/__init__.py | 27 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 625 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 694 bytes .../__pycache__/control.cpython-310.pyc | Bin 0 -> 2208 bytes .../__pycache__/control.cpython-312.pyc | Bin 0 -> 2912 bytes .../__pycache__/events.cpython-310.pyc | Bin 0 -> 2841 bytes .../__pycache__/events.cpython-312.pyc | Bin 0 -> 3437 bytes .../__pycache__/media.cpython-310.pyc | Bin 0 -> 2803 bytes .../__pycache__/media.cpython-312.pyc | Bin 0 -> 3793 bytes src/cmvr_edge_ai/contracts/control.py | 47 + src/cmvr_edge_ai/contracts/events.py | 84 + src/cmvr_edge_ai/contracts/media.py | 75 + src/cmvr_edge_ai/core/__init__.py | 62 + .../core/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1066 bytes .../core/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1079 bytes .../__pycache__/component.cpython-310.pyc | Bin 0 -> 6635 bytes .../__pycache__/component.cpython-312.pyc | Bin 0 -> 8034 bytes .../__pycache__/edge_queue.cpython-310.pyc | Bin 0 -> 7665 bytes .../__pycache__/edge_queue.cpython-312.pyc | Bin 0 -> 11981 bytes .../core/__pycache__/envelope.cpython-310.pyc | Bin 0 -> 4839 bytes .../core/__pycache__/envelope.cpython-312.pyc | Bin 0 -> 6623 bytes .../core/__pycache__/runtime.cpython-310.pyc | Bin 0 -> 22174 bytes .../core/__pycache__/runtime.cpython-312.pyc | Bin 0 -> 37994 bytes src/cmvr_edge_ai/core/component.py | 196 ++ src/cmvr_edge_ai/core/edge_queue.py | 232 +++ src/cmvr_edge_ai/core/envelope.py | 139 ++ src/cmvr_edge_ai/core/runtime.py | 667 ++++++ src/cmvr_edge_ai/detection/__init__.py | 38 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 941 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1019 bytes .../__pycache__/annotation.cpython-310.pyc | Bin 0 -> 5413 bytes .../__pycache__/annotation.cpython-312.pyc | Bin 0 -> 9010 bytes .../__pycache__/base.cpython-310.pyc | Bin 0 -> 2004 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 2120 bytes .../__pycache__/operator.cpython-310.pyc | Bin 0 -> 11442 bytes .../__pycache__/operator.cpython-312.pyc | Bin 0 -> 19369 bytes .../__pycache__/plugins.cpython-310.pyc | Bin 0 -> 1895 bytes .../__pycache__/plugins.cpython-312.pyc | Bin 0 -> 2336 bytes .../__pycache__/registry.cpython-310.pyc | Bin 0 -> 5564 bytes .../__pycache__/registry.cpython-312.pyc | Bin 0 -> 8196 bytes .../__pycache__/rules.cpython-310.pyc | Bin 0 -> 18780 bytes .../__pycache__/rules.cpython-312.pyc | Bin 0 -> 31145 bytes .../__pycache__/video.cpython-310.pyc | Bin 0 -> 9999 bytes .../__pycache__/video.cpython-312.pyc | Bin 0 -> 14933 bytes src/cmvr_edge_ai/detection/annotation.py | 176 ++ src/cmvr_edge_ai/detection/base.py | 50 + src/cmvr_edge_ai/detection/models/__init__.py | 17 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 501 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 569 bytes .../__pycache__/ppe_yolo.cpython-310.pyc | Bin 0 -> 1970 bytes .../__pycache__/ppe_yolo.cpython-312.pyc | Bin 0 -> 2304 bytes .../models/__pycache__/yolo.cpython-310.pyc | Bin 0 -> 7502 bytes .../models/__pycache__/yolo.cpython-312.pyc | Bin 0 -> 11384 bytes src/cmvr_edge_ai/detection/models/ppe_yolo.py | 96 + src/cmvr_edge_ai/detection/models/yolo.py | 209 ++ src/cmvr_edge_ai/detection/operator.py | 374 ++++ src/cmvr_edge_ai/detection/plugins.py | 62 + src/cmvr_edge_ai/detection/registry.py | 154 ++ src/cmvr_edge_ai/detection/rules.py | 661 ++++++ src/cmvr_edge_ai/detection/video.py | 319 +++ src/cmvr_edge_ai/observability/__init__.py | 5 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 331 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 307 bytes .../__pycache__/logging.cpython-310.pyc | Bin 0 -> 1569 bytes .../__pycache__/logging.cpython-312.pyc | Bin 0 -> 2193 bytes src/cmvr_edge_ai/observability/logging.py | 36 + src/cmvr_edge_ai/plugins/__init__.py | 23 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 538 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 525 bytes .../__pycache__/builtin.cpython-310.pyc | Bin 0 -> 9686 bytes .../__pycache__/builtin.cpython-312.pyc | Bin 0 -> 15570 bytes .../__pycache__/registry.cpython-310.pyc | Bin 0 -> 6761 bytes .../__pycache__/registry.cpython-312.pyc | Bin 0 -> 9627 bytes src/cmvr_edge_ai/plugins/builtin.py | 296 +++ src/cmvr_edge_ai/plugins/registry.py | 166 ++ src/cmvr_edge_ai/transports/__init__.py | 5 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 319 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 292 bytes .../__pycache__/base.cpython-310.pyc | Bin 0 -> 900 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 891 bytes src/cmvr_edge_ai/transports/base.py | 15 + src/cmvr_edge_ai/transports/grpc/__init__.py | 16 + .../grpc/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 453 bytes .../grpc/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 435 bytes .../grpc/__pycache__/bidi.cpython-310.pyc | Bin 0 -> 2578 bytes .../grpc/__pycache__/bidi.cpython-312.pyc | Bin 0 -> 3476 bytes .../grpc/__pycache__/channel.cpython-310.pyc | Bin 0 -> 3145 bytes .../grpc/__pycache__/channel.cpython-312.pyc | Bin 0 -> 4710 bytes src/cmvr_edge_ai/transports/grpc/bidi.py | 68 + src/cmvr_edge_ai/transports/grpc/channel.py | 79 + src/cmvr_edge_ai/transports/http/__init__.py | 5 + .../http/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 343 bytes .../http/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 316 bytes .../http/__pycache__/client.cpython-310.pyc | Bin 0 -> 2687 bytes .../http/__pycache__/client.cpython-312.pyc | Bin 0 -> 4013 bytes src/cmvr_edge_ai/transports/http/client.py | 62 + src/cmvr_edge_ai/transports/quic/__init__.py | 1 + .../quic/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 213 bytes .../quic/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 220 bytes src/cmvr_edge_ai/transports/udp/__init__.py | 5 + .../udp/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 296 bytes .../udp/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 305 bytes src/cmvr_edge_ai/workers/__init__.py | 11 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 447 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 423 bytes .../__pycache__/process.cpython-310.pyc | Bin 0 -> 6626 bytes .../__pycache__/process.cpython-312.pyc | Bin 0 -> 10517 bytes .../__pycache__/thread.cpython-310.pyc | Bin 0 -> 1213 bytes .../__pycache__/thread.cpython-312.pyc | Bin 0 -> 1448 bytes src/cmvr_edge_ai/workers/process.py | 211 ++ src/cmvr_edge_ai/workers/thread.py | 32 + talk_server/README.md | 15 + talk_server/pipeline.yaml | 42 + tests/__pycache__/conftest.cpython-310.pyc | Bin 0 -> 431 bytes .../conftest.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 799 bytes tests/__pycache__/conftest.cpython-312.pyc | Bin 0 -> 645 bytes tests/conftest.py | 13 + .../test_application_cli.cpython-310.pyc | Bin 0 -> 3643 bytes ...plication_cli.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 18118 bytes .../test_application_cli.cpython-312.pyc | Bin 0 -> 6013 bytes .../test_detection_pipeline.cpython-310.pyc | Bin 0 -> 5478 bytes ...tion_pipeline.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 20895 bytes .../test_detection_pipeline.cpython-312.pyc | Bin 0 -> 9775 bytes ...tion_pipeline.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 31858 bytes ...st_dual_detection_pipeline.cpython-312.pyc | Bin 0 -> 14382 bytes .../test_process_worker.cpython-310.pyc | Bin 0 -> 3408 bytes ...rocess_worker.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 9808 bytes .../test_process_worker.cpython-312.pyc | Bin 0 -> 6121 bytes .../__pycache__/test_runtime.cpython-310.pyc | Bin 0 -> 8766 bytes .../test_runtime.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 26910 bytes .../__pycache__/test_runtime.cpython-312.pyc | Bin 0 -> 13981 bytes tests/integration/test_application_cli.py | 116 ++ tests/integration/test_detection_pipeline.py | 223 ++ .../test_dual_detection_pipeline.py | 349 ++++ tests/integration/test_process_worker.py | 98 + tests/integration/test_runtime.py | 235 +++ .../test_agv_connector.cpython-310.pyc | Bin 0 -> 1394 bytes ...agv_connector.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 1897 bytes .../test_agv_connector.cpython-312.pyc | Bin 0 -> 1743 bytes .../test_bidi_stream.cpython-310.pyc | Bin 0 -> 2129 bytes ...t_bidi_stream.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 6910 bytes .../test_bidi_stream.cpython-312.pyc | Bin 0 -> 3520 bytes .../test_builtin_plugins.cpython-310.pyc | Bin 0 -> 1562 bytes ...iltin_plugins.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 2831 bytes .../test_builtin_plugins.cpython-312.pyc | Bin 0 -> 1747 bytes .../test_camera_connector.cpython-310.pyc | Bin 0 -> 10359 bytes ...era_connector.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 69032 bytes .../test_camera_connector.cpython-312.pyc | Bin 0 -> 30479 bytes .../__pycache__/test_compiler.cpython-310.pyc | Bin 0 -> 8262 bytes ...test_compiler.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 14788 bytes .../__pycache__/test_compiler.cpython-312.pyc | Bin 0 -> 12087 bytes .../__pycache__/test_config.cpython-310.pyc | Bin 0 -> 3578 bytes .../test_config.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 7274 bytes .../__pycache__/test_config.cpython-312.pyc | Bin 0 -> 4766 bytes .../test_detection_annotation.cpython-310.pyc | Bin 0 -> 5692 bytes ...on_annotation.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 18402 bytes .../test_detection_annotation.cpython-312.pyc | Bin 0 -> 9387 bytes .../test_detection_compile.cpython-310.pyc | Bin 0 -> 1789 bytes ...ction_compile.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 5391 bytes .../test_detection_compile.cpython-312.pyc | Bin 0 -> 3352 bytes .../test_detection_operator.cpython-310.pyc | Bin 0 -> 11621 bytes ...tion_operator.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 50763 bytes .../test_detection_operator.cpython-312.pyc | Bin 0 -> 23656 bytes .../test_detection_registry.cpython-310.pyc | Bin 0 -> 6425 bytes ...tion_registry.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 12816 bytes .../test_detection_registry.cpython-312.pyc | Bin 0 -> 8378 bytes .../test_detection_trigger.cpython-310.pyc | Bin 0 -> 9023 bytes ...ction_trigger.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 97862 bytes .../test_detection_trigger.cpython-312.pyc | Bin 0 -> 28935 bytes .../test_edge_queue.cpython-310.pyc | Bin 0 -> 3315 bytes ...st_edge_queue.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 16860 bytes .../test_edge_queue.cpython-312.pyc | Bin 0 -> 6456 bytes .../test_http_json_sink.cpython-310.pyc | Bin 0 -> 9508 bytes ...ttp_json_sink.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 42619 bytes .../test_http_json_sink.cpython-312.pyc | Bin 0 -> 25034 bytes .../test_safety_gate.cpython-310.pyc | Bin 0 -> 2649 bytes ...t_safety_gate.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 15424 bytes .../test_safety_gate.cpython-312.pyc | Bin 0 -> 4440 bytes .../test_video_decoder.cpython-310.pyc | Bin 0 -> 14539 bytes ...video_decoder.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 54721 bytes .../test_video_decoder.cpython-312.pyc | Bin 0 -> 24630 bytes .../test_yolo_model.cpython-310.pyc | Bin 0 -> 11114 bytes ...st_yolo_model.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 36721 bytes .../test_yolo_model.cpython-312.pyc | Bin 0 -> 16708 bytes tests/unit/test_agv_connector.py | 44 + tests/unit/test_bidi_stream.py | 50 + tests/unit/test_builtin_plugins.py | 24 + tests/unit/test_camera_connector.py | 576 +++++ tests/unit/test_compiler.py | 372 ++++ tests/unit/test_config.py | 145 ++ tests/unit/test_detection_annotation.py | 169 ++ tests/unit/test_detection_compile.py | 105 + tests/unit/test_detection_operator.py | 442 ++++ tests/unit/test_detection_registry.py | 177 ++ tests/unit/test_detection_trigger.py | 692 +++++++ tests/unit/test_edge_queue.py | 86 + tests/unit/test_http_json_sink.py | 505 +++++ tests/unit/test_safety_gate.py | 106 + tests/unit/test_video_decoder.py | 445 ++++ tests/unit/test_yolo_model.py | 378 ++++ uv.lock | 1845 +++++++++++++++++ 604 files changed, 29171 insertions(+) create mode 100644 README.md create mode 100644 configs/smoke.yaml create mode 100644 detect_server/README.md create mode 100644 detect_server/pipeline.yaml create mode 100644 docs/architecture.md create mode 100644 pyproject.toml create mode 100644 scripts/__pycache__/generate_cmvr_stubs.cpython-312.pyc create mode 100755 scripts/bootstrap.sh create mode 100644 scripts/generate_cmvr_stubs.py create mode 100644 src/cmvr/api/__pycache__/agv_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/agv_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/agv_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/agv_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/agv_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/agv_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/arm_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/arm_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/arm_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/arm_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/arm_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/arm_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/biohead_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/camera_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/camera_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/camera_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/camera_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/camera_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/common_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/common_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/common_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/common_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/microphone_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/speaker_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/system_command_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/system_command_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/system_command_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/system_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/system_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/system_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/test_service_pb2.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/test_service_pb2.cpython-312.pyc create mode 100644 src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/api/__pycache__/test_service_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/api/agv_command_pb2.py create mode 100644 src/cmvr/api/agv_command_pb2.pyi create mode 100644 src/cmvr/api/agv_command_pb2_grpc.py create mode 100644 src/cmvr/api/agv_service_pb2.py create mode 100644 src/cmvr/api/agv_service_pb2.pyi create mode 100644 src/cmvr/api/agv_service_pb2_grpc.py create mode 100644 src/cmvr/api/arm_command_pb2.py create mode 100644 src/cmvr/api/arm_command_pb2.pyi create mode 100644 src/cmvr/api/arm_command_pb2_grpc.py create mode 100644 src/cmvr/api/arm_service_pb2.py create mode 100644 src/cmvr/api/arm_service_pb2.pyi create mode 100644 src/cmvr/api/arm_service_pb2_grpc.py create mode 100644 src/cmvr/api/biohead_command_pb2.py create mode 100644 src/cmvr/api/biohead_command_pb2.pyi create mode 100644 src/cmvr/api/biohead_command_pb2_grpc.py create mode 100644 src/cmvr/api/biohead_service_pb2.py create mode 100644 src/cmvr/api/biohead_service_pb2.pyi create mode 100644 src/cmvr/api/biohead_service_pb2_grpc.py create mode 100644 src/cmvr/api/camera_command_pb2.py create mode 100644 src/cmvr/api/camera_command_pb2.pyi create mode 100644 src/cmvr/api/camera_command_pb2_grpc.py create mode 100644 src/cmvr/api/camera_service_pb2.py create mode 100644 src/cmvr/api/camera_service_pb2.pyi create mode 100644 src/cmvr/api/camera_service_pb2_grpc.py create mode 100644 src/cmvr/api/common_pb2.py create mode 100644 src/cmvr/api/common_pb2.pyi create mode 100644 src/cmvr/api/common_pb2_grpc.py create mode 100644 src/cmvr/api/dexhand_command_pb2.py create mode 100644 src/cmvr/api/dexhand_command_pb2.pyi create mode 100644 src/cmvr/api/dexhand_command_pb2_grpc.py create mode 100644 src/cmvr/api/dexhand_service_pb2.py create mode 100644 src/cmvr/api/dexhand_service_pb2.pyi create mode 100644 src/cmvr/api/dexhand_service_pb2_grpc.py create mode 100644 src/cmvr/api/hlc_command_pb2.py create mode 100644 src/cmvr/api/hlc_command_pb2.pyi create mode 100644 src/cmvr/api/hlc_command_pb2_grpc.py create mode 100644 src/cmvr/api/hlc_service_pb2.py create mode 100644 src/cmvr/api/hlc_service_pb2.pyi create mode 100644 src/cmvr/api/hlc_service_pb2_grpc.py create mode 100644 src/cmvr/api/microphone_command_pb2.py create mode 100644 src/cmvr/api/microphone_command_pb2.pyi create mode 100644 src/cmvr/api/microphone_command_pb2_grpc.py create mode 100644 src/cmvr/api/microphone_service_pb2.py create mode 100644 src/cmvr/api/microphone_service_pb2.pyi create mode 100644 src/cmvr/api/microphone_service_pb2_grpc.py create mode 100644 src/cmvr/api/speaker_command_pb2.py create mode 100644 src/cmvr/api/speaker_command_pb2.pyi create mode 100644 src/cmvr/api/speaker_command_pb2_grpc.py create mode 100644 src/cmvr/api/speaker_service_pb2.py create mode 100644 src/cmvr/api/speaker_service_pb2.pyi create mode 100644 src/cmvr/api/speaker_service_pb2_grpc.py create mode 100644 src/cmvr/api/system_command_pb2.py create mode 100644 src/cmvr/api/system_command_pb2.pyi create mode 100644 src/cmvr/api/system_command_pb2_grpc.py create mode 100644 src/cmvr/api/system_service_pb2.py create mode 100644 src/cmvr/api/system_service_pb2.pyi create mode 100644 src/cmvr/api/system_service_pb2_grpc.py create mode 100644 src/cmvr/api/test_service_pb2.py create mode 100644 src/cmvr/api/test_service_pb2.pyi create mode 100644 src/cmvr/api/test_service_pb2_grpc.py create mode 100644 src/cmvr/common/__pycache__/geometry_pb2.cpython-310.pyc create mode 100644 src/cmvr/common/__pycache__/geometry_pb2.cpython-312.pyc create mode 100644 src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/common/__pycache__/geometry_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/common/geometry_pb2.py create mode 100644 src/cmvr/common/geometry_pb2.pyi create mode 100644 src/cmvr/common/geometry_pb2_grpc.py create mode 100644 src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/joint_limits_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/joint_limits_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/lawba_ik_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/lawba_ik_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_dls_ik_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/pinocchio_qp_ik_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/srs_ik_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/srs_ik_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_algorithm_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_common_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/__pycache__/touch_screen_common_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/agv_config/__pycache__/agv_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/agv_config/__pycache__/agv_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/agv_config/agv_config_pb2.py create mode 100644 src/cmvr/config/agv_config/agv_config_pb2.pyi create mode 100644 src/cmvr/config/agv_config/agv_config_pb2_grpc.py create mode 100644 src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/arm_config/__pycache__/arm_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/arm_config/__pycache__/arm_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/arm_config/arm_config_pb2.py create mode 100644 src/cmvr/config/arm_config/arm_config_pb2.pyi create mode 100644 src/cmvr/config/arm_config/arm_config_pb2_grpc.py create mode 100644 src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/aubo_robot_config/__pycache__/aubo_robot_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.py create mode 100644 src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2.pyi create mode 100644 src/cmvr/config/aubo_robot_config/aubo_robot_config_pb2_grpc.py create mode 100644 src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/biohead_config/__pycache__/biohead_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/biohead_config/biohead_config_pb2.py create mode 100644 src/cmvr/config/biohead_config/biohead_config_pb2.pyi create mode 100644 src/cmvr/config/biohead_config/biohead_config_pb2_grpc.py create mode 100644 src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/camera_config/__pycache__/camera_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/camera_config/__pycache__/camera_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/camera_config/camera_config_pb2.py create mode 100644 src/cmvr/config/camera_config/camera_config_pb2.pyi create mode 100644 src/cmvr/config/camera_config/camera_config_pb2_grpc.py create mode 100644 src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/cmvr_es_config/__pycache__/cmvr_es_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.py create mode 100644 src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2.pyi create mode 100644 src/cmvr/config/cmvr_es_config/cmvr_es_config_pb2_grpc.py create mode 100644 src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/device_manager_config/__pycache__/device_manager_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/device_manager_config/device_manager_config_pb2.py create mode 100644 src/cmvr/config/device_manager_config/device_manager_config_pb2.pyi create mode 100644 src/cmvr/config/device_manager_config/device_manager_config_pb2_grpc.py create mode 100644 src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/dexhand_config/__pycache__/dexhand_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/dexhand_config/dexhand_config_pb2.py create mode 100644 src/cmvr/config/dexhand_config/dexhand_config_pb2.pyi create mode 100644 src/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py create mode 100644 src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/grpc_server_config/__pycache__/grpc_server_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/grpc_server_config/grpc_server_config_pb2.py create mode 100644 src/cmvr/config/grpc_server_config/grpc_server_config_pb2.pyi create mode 100644 src/cmvr/config/grpc_server_config/grpc_server_config_pb2_grpc.py create mode 100644 src/cmvr/config/joint_limits_config_pb2.py create mode 100644 src/cmvr/config/joint_limits_config_pb2.pyi create mode 100644 src/cmvr/config/joint_limits_config_pb2_grpc.py create mode 100644 src/cmvr/config/lawba_ik_config_pb2.py create mode 100644 src/cmvr/config/lawba_ik_config_pb2.pyi create mode 100644 src/cmvr/config/lawba_ik_config_pb2_grpc.py create mode 100644 src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/logger_config/__pycache__/logger_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/logger_config/__pycache__/logger_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/logger_config/logger_config_pb2.py create mode 100644 src/cmvr/config/logger_config/logger_config_pb2.pyi create mode 100644 src/cmvr/config/logger_config/logger_config_pb2_grpc.py create mode 100644 src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/microphone_config/__pycache__/microphone_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/microphone_config/microphone_config_pb2.py create mode 100644 src/cmvr/config/microphone_config/microphone_config_pb2.pyi create mode 100644 src/cmvr/config/microphone_config/microphone_config_pb2_grpc.py create mode 100644 src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/motor_config/__pycache__/motor_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/motor_config/__pycache__/motor_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/motor_config/motor_config_pb2.py create mode 100644 src/cmvr/config/motor_config/motor_config_pb2.pyi create mode 100644 src/cmvr/config/motor_config/motor_config_pb2_grpc.py create mode 100644 src/cmvr/config/pinocchio_dls_ik_config_pb2.py create mode 100644 src/cmvr/config/pinocchio_dls_ik_config_pb2.pyi create mode 100644 src/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py create mode 100644 src/cmvr/config/pinocchio_qp_ik_config_pb2.py create mode 100644 src/cmvr/config/pinocchio_qp_ik_config_pb2.pyi create mode 100644 src/cmvr/config/pinocchio_qp_ik_config_pb2_grpc.py create mode 100644 src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/speaker_config/__pycache__/speaker_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/speaker_config/speaker_config_pb2.py create mode 100644 src/cmvr/config/speaker_config/speaker_config_pb2.pyi create mode 100644 src/cmvr/config/speaker_config/speaker_config_pb2_grpc.py create mode 100644 src/cmvr/config/srs_ik_config_pb2.py create mode 100644 src/cmvr/config/srs_ik_config_pb2.pyi create mode 100644 src/cmvr/config/srs_ik_config_pb2_grpc.py create mode 100644 src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/task_manager_config/__pycache__/task_manager_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/task_manager_config/task_manager_config_pb2.py create mode 100644 src/cmvr/config/task_manager_config/task_manager_config_pb2.pyi create mode 100644 src/cmvr/config/task_manager_config/task_manager_config_pb2_grpc.py create mode 100644 src/cmvr/config/touch_screen_algorithm_config_pb2.py create mode 100644 src/cmvr/config/touch_screen_algorithm_config_pb2.pyi create mode 100644 src/cmvr/config/touch_screen_algorithm_config_pb2_grpc.py create mode 100644 src/cmvr/config/touch_screen_common_config_pb2.py create mode 100644 src/cmvr/config/touch_screen_common_config_pb2.pyi create mode 100644 src/cmvr/config/touch_screen_common_config_pb2_grpc.py create mode 100644 src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-310.pyc create mode 100644 src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2.cpython-312.pyc create mode 100644 src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/config/touch_screen_task_config/__pycache__/touch_screen_task_config_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.py create mode 100644 src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2.pyi create mode 100644 src/cmvr/config/touch_screen_task_config/touch_screen_task_config_pb2_grpc.py create mode 100644 src/cmvr/msgs/__pycache__/agv_pb2.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/agv_pb2.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/agv_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/can_card_parameter_pb2.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/can_card_parameter_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/canopen_pb2.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/canopen_pb2.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/canopen_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/error_code_pb2.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/error_code_pb2.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/error_code_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/motor_pb2.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/motor_pb2.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/motor_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/robot_detail_pb2.cpython-312.pyc create mode 100644 src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-310.pyc create mode 100644 src/cmvr/msgs/__pycache__/robot_detail_pb2_grpc.cpython-312.pyc create mode 100644 src/cmvr/msgs/agv_pb2.py create mode 100644 src/cmvr/msgs/agv_pb2.pyi create mode 100644 src/cmvr/msgs/agv_pb2_grpc.py create mode 100644 src/cmvr/msgs/can_card_parameter_pb2.py create mode 100644 src/cmvr/msgs/can_card_parameter_pb2.pyi create mode 100644 src/cmvr/msgs/can_card_parameter_pb2_grpc.py create mode 100644 src/cmvr/msgs/canopen_pb2.py create mode 100644 src/cmvr/msgs/canopen_pb2.pyi create mode 100644 src/cmvr/msgs/canopen_pb2_grpc.py create mode 100644 src/cmvr/msgs/error_code_pb2.py create mode 100644 src/cmvr/msgs/error_code_pb2.pyi create mode 100644 src/cmvr/msgs/error_code_pb2_grpc.py create mode 100644 src/cmvr/msgs/motor_pb2.py create mode 100644 src/cmvr/msgs/motor_pb2.pyi create mode 100644 src/cmvr/msgs/motor_pb2_grpc.py create mode 100644 src/cmvr/msgs/robot_detail_pb2.py create mode 100644 src/cmvr/msgs/robot_detail_pb2.pyi create mode 100644 src/cmvr/msgs/robot_detail_pb2_grpc.py create mode 100644 src/cmvr_edge_ai.egg-info/PKG-INFO create mode 100644 src/cmvr_edge_ai.egg-info/SOURCES.txt create mode 100644 src/cmvr_edge_ai.egg-info/dependency_links.txt create mode 100644 src/cmvr_edge_ai.egg-info/entry_points.txt create mode 100644 src/cmvr_edge_ai.egg-info/requires.txt create mode 100644 src/cmvr_edge_ai.egg-info/top_level.txt create mode 100644 src/cmvr_edge_ai/__init__.py create mode 100644 src/cmvr_edge_ai/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/__pycache__/application.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/__pycache__/application.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/__pycache__/cli.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/__pycache__/cli.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/__pycache__/compiler.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/__pycache__/compiler.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/application.py create mode 100644 src/cmvr_edge_ai/cli.py create mode 100644 src/cmvr_edge_ai/compiler.py create mode 100644 src/cmvr_edge_ai/config/__init__.py create mode 100644 src/cmvr_edge_ai/config/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/config/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/config/__pycache__/loader.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/config/__pycache__/loader.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/config/__pycache__/models.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/config/__pycache__/models.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/config/loader.py create mode 100644 src/cmvr_edge_ai/config/models.py create mode 100644 src/cmvr_edge_ai/connectors/__init__.py create mode 100644 src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/__pycache__/_context.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/_context.py create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__init__.py create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/agv.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/camera.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/__pycache__/errors.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/agv.py create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/camera.py create mode 100644 src/cmvr_edge_ai/connectors/cmvr_es/errors.py create mode 100644 src/cmvr_edge_ai/connectors/platform/__init__.py create mode 100644 src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/platform/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/connectors/platform/__pycache__/http_json.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/connectors/platform/http_json.py create mode 100644 src/cmvr_edge_ai/contracts/__init__.py create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/control.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/control.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/events.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/events.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/media.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/contracts/__pycache__/media.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/contracts/control.py create mode 100644 src/cmvr_edge_ai/contracts/events.py create mode 100644 src/cmvr_edge_ai/contracts/media.py create mode 100644 src/cmvr_edge_ai/core/__init__.py create mode 100644 src/cmvr_edge_ai/core/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/component.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/component.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/edge_queue.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/envelope.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/envelope.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/runtime.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/core/__pycache__/runtime.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/core/component.py create mode 100644 src/cmvr_edge_ai/core/edge_queue.py create mode 100644 src/cmvr_edge_ai/core/envelope.py create mode 100644 src/cmvr_edge_ai/core/runtime.py create mode 100644 src/cmvr_edge_ai/detection/__init__.py create mode 100644 src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/annotation.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/base.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/base.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/operator.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/operator.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/plugins.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/registry.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/registry.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/rules.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/rules.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/video.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/__pycache__/video.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/annotation.py create mode 100644 src/cmvr_edge_ai/detection/base.py create mode 100644 src/cmvr_edge_ai/detection/models/__init__.py create mode 100644 src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/models/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/models/__pycache__/ppe_yolo.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/detection/models/__pycache__/yolo.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/detection/models/ppe_yolo.py create mode 100644 src/cmvr_edge_ai/detection/models/yolo.py create mode 100644 src/cmvr_edge_ai/detection/operator.py create mode 100644 src/cmvr_edge_ai/detection/plugins.py create mode 100644 src/cmvr_edge_ai/detection/registry.py create mode 100644 src/cmvr_edge_ai/detection/rules.py create mode 100644 src/cmvr_edge_ai/detection/video.py create mode 100644 src/cmvr_edge_ai/observability/__init__.py create mode 100644 src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/observability/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/observability/__pycache__/logging.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/observability/__pycache__/logging.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/observability/logging.py create mode 100644 src/cmvr_edge_ai/plugins/__init__.py create mode 100644 src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/plugins/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/plugins/__pycache__/builtin.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/plugins/__pycache__/registry.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/plugins/builtin.py create mode 100644 src/cmvr_edge_ai/plugins/registry.py create mode 100644 src/cmvr_edge_ai/transports/__init__.py create mode 100644 src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/__pycache__/base.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/__pycache__/base.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/base.py create mode 100644 src/cmvr_edge_ai/transports/grpc/__init__.py create mode 100644 src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/grpc/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/grpc/__pycache__/bidi.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/grpc/__pycache__/channel.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/grpc/bidi.py create mode 100644 src/cmvr_edge_ai/transports/grpc/channel.py create mode 100644 src/cmvr_edge_ai/transports/http/__init__.py create mode 100644 src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/http/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/http/__pycache__/client.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/http/client.py create mode 100644 src/cmvr_edge_ai/transports/quic/__init__.py create mode 100644 src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/quic/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/transports/udp/__init__.py create mode 100644 src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/transports/udp/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/workers/__init__.py create mode 100644 src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/workers/__pycache__/__init__.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/workers/__pycache__/process.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/workers/__pycache__/process.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/workers/__pycache__/thread.cpython-310.pyc create mode 100644 src/cmvr_edge_ai/workers/__pycache__/thread.cpython-312.pyc create mode 100644 src/cmvr_edge_ai/workers/process.py create mode 100644 src/cmvr_edge_ai/workers/thread.py create mode 100644 talk_server/README.md create mode 100644 talk_server/pipeline.yaml create mode 100644 tests/__pycache__/conftest.cpython-310.pyc create mode 100644 tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/__pycache__/conftest.cpython-312.pyc create mode 100644 tests/conftest.py create mode 100644 tests/integration/__pycache__/test_application_cli.cpython-310.pyc create mode 100644 tests/integration/__pycache__/test_application_cli.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/integration/__pycache__/test_application_cli.cpython-312.pyc create mode 100644 tests/integration/__pycache__/test_detection_pipeline.cpython-310.pyc create mode 100644 tests/integration/__pycache__/test_detection_pipeline.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/integration/__pycache__/test_detection_pipeline.cpython-312.pyc create mode 100644 tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/integration/__pycache__/test_dual_detection_pipeline.cpython-312.pyc create mode 100644 tests/integration/__pycache__/test_process_worker.cpython-310.pyc create mode 100644 tests/integration/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/integration/__pycache__/test_process_worker.cpython-312.pyc create mode 100644 tests/integration/__pycache__/test_runtime.cpython-310.pyc create mode 100644 tests/integration/__pycache__/test_runtime.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/integration/__pycache__/test_runtime.cpython-312.pyc create mode 100644 tests/integration/test_application_cli.py create mode 100644 tests/integration/test_detection_pipeline.py create mode 100644 tests/integration/test_dual_detection_pipeline.py create mode 100644 tests/integration/test_process_worker.py create mode 100644 tests/integration/test_runtime.py create mode 100644 tests/unit/__pycache__/test_agv_connector.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_agv_connector.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_agv_connector.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_bidi_stream.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_bidi_stream.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_bidi_stream.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_builtin_plugins.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_builtin_plugins.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_builtin_plugins.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_camera_connector.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_camera_connector.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_camera_connector.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_compiler.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_compiler.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_compiler.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_config.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_config.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_config.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_detection_annotation.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_detection_annotation.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_detection_annotation.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_detection_compile.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_detection_compile.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_detection_compile.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_detection_operator.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_detection_operator.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_detection_operator.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_detection_registry.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_detection_registry.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_detection_registry.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_detection_trigger.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_detection_trigger.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_detection_trigger.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_edge_queue.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_edge_queue.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_edge_queue.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_http_json_sink.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_http_json_sink.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_http_json_sink.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_safety_gate.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_safety_gate.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_safety_gate.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_video_decoder.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_video_decoder.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_video_decoder.cpython-312.pyc create mode 100644 tests/unit/__pycache__/test_yolo_model.cpython-310.pyc create mode 100644 tests/unit/__pycache__/test_yolo_model.cpython-312-pytest-7.4.4.pyc create mode 100644 tests/unit/__pycache__/test_yolo_model.cpython-312.pyc create mode 100644 tests/unit/test_agv_connector.py create mode 100644 tests/unit/test_bidi_stream.py create mode 100644 tests/unit/test_builtin_plugins.py create mode 100644 tests/unit/test_camera_connector.py create mode 100644 tests/unit/test_compiler.py create mode 100644 tests/unit/test_config.py create mode 100644 tests/unit/test_detection_annotation.py create mode 100644 tests/unit/test_detection_compile.py create mode 100644 tests/unit/test_detection_operator.py create mode 100644 tests/unit/test_detection_registry.py create mode 100644 tests/unit/test_detection_trigger.py create mode 100644 tests/unit/test_edge_queue.py create mode 100644 tests/unit/test_http_json_sink.py create mode 100644 tests/unit/test_safety_gate.py create mode 100644 tests/unit/test_video_decoder.py create mode 100644 tests/unit/test_yolo_model.py create mode 100644 uv.lock 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 0000000000000000000000000000000000000000..79eabaa5d8b5d018b4088c689d4ba72d59e2dc55 GIT binary patch literal 5353 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 BES3NO literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c1432990d69691cce6874b6d400e05d21ab4182c GIT binary patch literal 5641 zcmd5=%WoUU8Q-NSK1S4wdYGbYNz25s)h1Lb_nEqBXo-@Qh$1agO2fw8E;(a*?d2}J zyHsl*dMQw#0owEr$U#Dj06|UxdMTP5d?^YPD9}@Z4bV%97WxYc*eKBNo829WjO0UU zASI#IkKgzEo-;c;v)1S+k>Eof-M;ts3zGC1{y6nA9Sa5|!!_m1)F~+mZT^ zjCcrev_8yn6mYB_=Xe-!qMqb9W{~zsJta#lemKhF_r~sx(>P7gBqem@CNp5GfS=(=3xr{D#Y|FKpma)|6-nql< zrKUw${upn@W!WQ?IZa#dxRwpS#!r5!9m_IMWOq$hZ!`6P*^X|RC~_XO8n~!oTmzcb%Q$;m#e5hnAFyw7bv`i2wn$=wG1}0Y_tP);884k@EG(AE zu!EUoB0O5wH^9vEeYxiTD3dJ6cC-V%rMWN|SVGr%7Bv_H)sQDKtfA`!MJ)6d(VuYc zy$H#i6vAp&#d5?@Jd*$Su}J4gv;)3^Jf?f|z$mLU{UV859c-VI8R9*k@1>P2RF-{8 z`WhM2sMc|rt#&k9Yde|fSw;n)Q{j~C^+DR1dpAPnd)kBJ3@L>J{m~#DN)v)(1@2Ux z@;)X1|2y(~nM^}RHY~%ieq7PqyJ0Ju1I(2xFjv48^k4{X^SRzhfI+IP)n=wU;As9q zCNq$GmAU)Ou$sF2AWWVPko;AWI0)PYlYO?gfH2sA*}o@~G-SW6J8ntqJd?HN^+9fq zbc#gVkO&{OWDc5(OFE_(+$7H=MK(~1S4bkb#r!l!R%d$0CP)<@NTZNFG&Y)NtNE0 zPBStwF1$d-+uBdm=H0G&Uu`^anUjfwVmLeS{-E1|?PB;`?%N;infe`_Q5e%b&T7Q? z{_xtvjM#Z;C!0syA?_US;r8-^TG-s*-P@^cm5S9`p;lZgmr8}5bv-S^4%9-*;yDy` zw|Hw0jE+CR&FY6pT{@4tQ7o?CD6GAq&wyKR9%(y;{jJSHZL7S4ardfW;7^h9+*24~ zT-`$E^G{=nuzyGD+$n0YR<4w*qT0t1Nz$JnNjaA!;(iQdFLypKjC%Yp=!yOxWMAxM zW5rN-SaK=~yi0J5@2Lh-?B$6B{({uYPjjh;Iq2iy+nYh|MtOU?{AQ(4yBWs)6UM!Q zgiB`YCUdb605vz>UTX6BqIg2WS{uXJ|K{?ghZn01bBR)xjn~zb?n-BT)=ta0+ zBJW+e_|x)pyfD^s&Wo!mwVJBxjkheTWibDk3deLC?mEdEQBRKJHrzClH*reB59l_` zNfh@T(ruU{DDFF`dzrpCc^x(fwy7C543^|2)ZiHJE$nNCjth1LUmie4!I^pAe+l5l z)~&Xtn_jH4cVl~N&5N(IJ6hLpp(Kc@(uOBa==KP@zKAP1RO_m6Xy^Z%{>dL#b1%u) zc|7ylDP;4bKWCxLH-iTN=S~(Vyi2ilNbn(}8@j*BSCpX~zYl!%%11IgZj>iFgHqGLB#u|h mB|4mv!*5iM$r165K2DI-P)Z&fibdcmCqPTS@b8!${p7zUB42(0 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1cc046110caf361f0723516fcddded46b425864c GIT binary patch literal 7477 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b040e24f6da7f191f3510e943c805f5e758ed5db GIT binary patch literal 1010 zcmah{&rcIU6rSmBx7%%NLkLZ*K}PUG0`q! zB#HDo(ZlHqQP_yM)o_X=p98FJOE}#WL97u;_?c~~>t$naJJGdsUF1>ZHS8%phFggd zK+@*{OG1DZU0DTB%MI{QS_KU_da7BOb8O=J$aFE{gl!UrEXU-WaAZ-&2=}OqZMTMe ziabVrx)!XDW3q0$q(`tV!b{)dY>Q20qI6}VG;w_tMul~oao-Sn+@`K!bHkwx!d9B2 zEB6ViG0#GtX+1D&1lcb4O~(;>HbaQ;18L=?j!aaZEy1eUp_< z&n}y_O~az~y6D01)-D>cddb^Dn0Um+98tHK?xwWq%ya}x%`vp(5R(%Wcr|7UwJjR3 zUQu<51Bb-j#wszPc^OfLFm1ShWB;ut;PHrhjB%e*hfht9W}=i~U}_mgB*&phZJ5jz zKpqw5t4njnd}Xmxon5J1GwxKXw-;_LMj7K|Vn&qhf{sIllZZ=wg9n~RnV3eD?{;eh zzU|n)O*j+36-1eZdK|AXMp=}t2Ci?{N!NP?4&%rAb)GY;{J#-K>QrwaGpt{b;!nlj zimfd{2)}|G2Ox6*@@)kaheJ?k<@-P7hu-Ih9%Wmn&x(t-EW_c?^5y-(OFK8it9L&R zu7+~4qXN0OuN7LFxZRoA?F-epmR9XZFnt?-$qs~tv2bAg<#MQ%JIO@)f{@QmhLg9# zD@&oa+(}94Dr~DDHwSm8c1z)s09HF`S&$DUC8M-~l2qD~$nmqC0&4#c0O4?J@Z1Nv b7?(Z#i}#5RUgT%{z+PW!_KdVwgkt{oE>FIt+ z)BaT#)1N$Ee2+i*Hx5keXiR4*M|V@5w65l9oM$=$5ke?j>#-({sM zy;FrdziFKs6ffU{D%7r0=r3O0g(@rK6;=i5)AuE?3RKNiwhT39+|kT6wi3rSHLPnS*XX9N&cC`4SWTHkyq$-M6cbB^Nb)zDQdHeBkYmy z{y!x`wt9p1s3zltlzR}gM(wr(nkw2NR{*r`=@HppFLQc5j@Utcjo*5s{tlo_s$t<6|CN73QBxRrWB~0#|dF;s% zsR)pLcmA*z^(=7aTkX%JAR#yghg)`VZp?Dmo_LZZ&pzij1ja1?L{=m|KjRb)?gfH7 z^I-Z%l6Pzw>{DMFAI@z0qsW}B`lDAy#U)#$sDPu}QYKz%3ViHJCYXJx-4q!oIUtMj z%nqz)SpPH|_}HQ)2|V?3nGKl6>-!F;Y)*JhQegbdnEzT`NBY6_6Qv6Z=GMdG_D<_~ud!qQfv5b&y)b8*j9aD|mV2D{oJb^~Xc4K=l8HDG zX=SOH6U$Jph|I{GkSt;-MJi@OwuIG0p1SP?AUx^_rDDRo8Ht*3^_V&~svT%5DTNh9 zO5k`TiNfqQcd6}#+2+xMM|+Jhw+$!iP{funr^&iutW!imLElyMV7GyZ(q;H>jdYNr zNEB7b2QP#uCS!G^0>kAk#!$W3SltD%UE6_>Z11!it-a>q<5rmKIlN09iJgpRs}Gb; z+XpFW54JqEKK*bvXXgG zQReU&!I&u%ch$Sf4~@i(Jq b^;G=LJkOJ2x~P}Z*%XfG%jyLGGynY$mTMi# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ecdfaef9db6a065820f331ef3e2f1529c6786684 GIT binary patch literal 3513 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 A*8l(j literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..10f86492c1fac8300203d1d1b06a8461e700d413 GIT binary patch literal 7922 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 B>T3W1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d0522d2582de27a95c07512c582c0ae7b75bba1f GIT binary patch literal 811 zcmah`!EV$r5Vf6bnr3&~+j8}R%S!E5?E!IUD^S_4dMVYm0uc#WX*^wA5<9Y;u9_p| z3%H{F06v4?$dwbnPyxlsLRC)K@?^&I-gxFk_4^TWEBgBO@}q~)wyWzLTxwVH{!vA#}jIA#v~;$*zxJ^?Ha=vw08) zZ;-@u1c9DF#8;s68S!x8@zqC&N8q`ryYQUfYy1UqUcbe(w>S8M>*Ev+*Q)VBCO}%6 zNKOstE6_B}5@W#7RA~*SP?8HdqgK&EE33}Si%kv}Lc)?DQZS?MZaj2c1>2*i+oSEn zA+84(LL19mJtLH4!mv!uL7!E_vv)vex=3k}r0t?{#->#&GrN1k zZoR@7S1Dt)-=ykro@nWSrXEbDCkJdYJ|0h>pN(IzSL5mF(aYny%kC9pb*}~eO%3id zQdwroqEOnYQLnetveJr7SOG>8_bck|QQqXoS}R@mrlqtZhc^0x^twZ|=Jv7C>DK>r zti#=0Q;Db9$v#e7n3{RBRImuc@ZJMLXZ56eDcG;^Y4BGqIUL4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a8cc4323b0dd44508bbaac0eb1266d15af6c8679 GIT binary patch literal 1010 zcmah{&rcIU6rSmBx7%%NLkLZ*K}PgK0UL){vrDBX zV?28IpxiZF_yaV7vzJv5mL(<{Pu^O&7*5W%;E$7Yn0asB_kHij%)4Ap17d0W_Z~cz z0QeR=r%O38j@}64Ilw>%7~+Hjb=Wx*dIC$jMBoGUl=A1)lUUZ3bj(OPshX;*5F`Lm zNs>rAi7rl8h{7(4TMeg3@&&-^wuI9S5yTpigrC`#I$k#Rwi6vY*Fl~{c3@ZO3EWCt z1SEY4up|Un(Un#3tXv0=q*YLdLuVS5S;r=>k4zUMPS_@4$Z|~12}c%XjBt;-*mgIN zPm#x{PuGI=5lq%?mvjlXMR@6ZoNck8jF!enOQUl`Fe8S{o8pCMWAtom%@HUt!)V65A zx<%C}4jdA98>_^G#u%auVOn?p#{OGPz~d2h86!TW4xgMF%0wx{z|=B~NRC61S~r<1 zfIKS9SC?mv`O0FYIj~A_rdDlBFnt?-$@YbX;jnMy)vZt~x08wVWg(xN2q%`p z@#Rpv)lNz2Dr~7BHw*VB_e$Zi09M;+S&)w;C8M-}l2lrf$nmq40&4FM0O4S>|H22k b7?(YG%zH!!FY_}!V816db57bXLb3k>CC@Sv literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b89e72025ca2fe782ba7b03ee17d82b27689fd88 GIT binary patch literal 2199 zcmb_dOK;>v5Vq&xnRF&E?`twFd2q`jO~k?l1Sr`_vLs|SVP=zq#j53TS0V@7ZMr>y z%@uw~a^z?52e@(Zl~ex0N=Q`Oj_phc;;^>duCB*dRo(8-d}fAdc#+@Voc)T@Uusgm znmF0ONB)5W(=3hYtm5f@#j5H`uA*GC>WN%K*{~XkTt~TSO(nA75r5ih=^8XHXP|L5 zdp5@!tjVSrVbfOz>ZdN}uQY1`@mikgKd>GqaWg1CvL2(ouI!gj*zA>VwPEoW&FVnw z@&jl?=c{Lrpzv4=! zfUSgrN8I7w%4mFY0%FDC47%6Yw6>GD9S z1Ho)6^e&=z9_4vb$TUUgc5sHB+MfT}AY@bc!?Y&z z6`8_S_;CyeCnx4|_XfG!OAGo2Y;3SQ0Q8ViXXQSJ9RN16ogd6TX=ZrA`+)m`)O~&q zd*+iOb6+X)lG=9-GUR~-GmG7yjNQL4RvEI`uZST->}k&|;SUo{9=%v(&Mgz+`**^N zQjP?d*x>U$fs>vg`+h$cTr19^t3^K6YV)%$!2B$K4C>)4(lDe;=b($v2l3xVt=<6H@4u!9(xhCggGf2#W`7uX$AdM zEf01ZQRL6$oiZm?OC(y#<)b?;Y9(`uR*vz)CSypUE>?E|Y{&H=CR^LX-eC9e^}!%+ zoO*m@dlEajk(OSlpG6;}v`=9(VB5ji-^zbOGEVKUd00}STNWT=lO7GXX^$3JhVdNr zIPx>2JXA+r#tZ6SlNCi}WQz!WN-7B0Z<<)cE|cNivJ}p`YIpKqrJC%9Z{y~dK4)VO pzSc2DeuhJ>-qLUGnr`To{I0!gl2)~)&sL2Jj%YJ8@k7){prb+q;d11F5@W5zf-E>4cUb$Y*=xw-f?J$MLikm+;hJ3opbJ$KINB8 zMndqDe*UcfHCVs%Mes}e#?zk!guX)2vLrE*brm0Rbft5$m~tJi3E-B3cDj{d{0WcP!#G?!yx9-%oj7m$k2+k1&C??;Dj7 z=yO1qjWd-|x1S8yXDefFX7BSd>&1X8y5qrp*bDxvWMQ81uu2YA1NC>jSo}(|B)6aN zJZ{Z_`n7o6vq=^Y0j?*&k)HbX( zwHhh7JwcvSqlQ6ErBS`gi8+LOR15&gXZ0ZBS6`PT%chN0YQv-ZB2M9^l-VNoGSy7G zY{Tm%k42-q-r)I&os#eOTe>NUz_3B#SYmVE@m%3dxrjCj`3w)F!J4XTRsMQc2%{g# z@rUt5B;>mg@hx;CeA<#b8gFb>wVC!+e7pP}yg*1AE87k5IV~v*?_8s4liQSisWSYb zW)efSwVEYgj(u%LF&iRLMZXmg2gX=kTRPu!Rp{L7EYLObMW;&^j2e(%4EjZ#L z*;Jdl4I@EK1y`L=EZG33p5WU9)7lE?r+fF5Q{h|0G8{5>3c`0XlxPlgM&>aF3Duyx zU4n0GI&q}MrSd}Q-p2j)k|S>Ew5sYBOcGen;b+Kly!d_E zh&ch~Cs>ZGJSZM}&sS*dIO+EcikmulpS=!v{!fAR2V9PO1VQ+70120#ptql(4}!mI9aKC( z!);V-p<*XF+D@KpCC{~!Q?2AwC)wLcrh;q!uZ$=pjz>}d#RDnVmZnjba{} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..67bbc9a2cfb14097d3a4bacd5668f3ba4cb37599 GIT binary patch literal 8870 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d2dd5e7071b23dd203e1067bb2b09cdbeb2b28eb GIT binary patch literal 6585 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 SpbRM~Xe0l3ze7s=2mb`!0XvES literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..29d2a604a0cae9fdce530374eb2a1d7aa6c30525 GIT binary patch literal 8938 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 BFV_G7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5e9fc2b55da188cb8c44d54500365e0768b93347 GIT binary patch literal 819 zcma)3!A{&T5Vf6b2-#h@ZLdCdC0GQ9ss})==oZlfMGI=xNR^e1hsBWCk?pV|$8zbp z*NPA5XY@C6?WwOlCaqjc1-!zaJsDqOb4IKY9p#Yl4R>bip(CnNQAe z6eEr~$#9I@KZyzVVh`}SA9uJP2UXvlce15Cj6;kdgbsMuBo5vp+4T`DUkx#8HV@+9 z9g=vCAkg~|@g?YdMm)Un`0^vfBk)|*z44siYy2DHync&mPp|O@*T*p$u2kc_On|gB zk(?UPm!N5yCB}fEsnQxup(Gb_My;ZSR#r{R({&D~Lc)R|QZS?MZaj2Y1)HO%o1@K_ zLtGC|g*KMCdPXS8gkhPQgFdN-Cucxsx=3k}q!-BysF21cS?1~qLlaB?aOf>wt6tG* zX{l`}AVDYAp;E;g5>4|W;$^duT-fQjpd4$88j0&RT@I|gepzBn#+gSe>*BoBh!MhZk5W+ z_Rg?dFEPed%2@3;sXCk|S~{Sq2m6zwJ+?nS98aE~j9;)f%+Rs?p0%TuLb>0 z4em2iS!T+jP}(U{ueQ^&(uz!20Y($|9Ch~~Z}MZUm9Be}Qd*Hi8+}1~)gfAQ``GAo zN(oh=mXPK8uEpT^tUH^~ zOo)krodLRoy7UKt)Xpph27)Uj5EENM7u1P!(iYgb;qHC!d7k&{?p;2w1F`h2yZ4_; z0DMoJ{iW;~J8y*X9AIDo3~|bZ2JD_GBZVbHBJjStFZpvCX)GIxmMGG0rmh+)1SvpN zk|xr6s>d@FqOcKhtK$qwzW`WWld#qlAl8W_{Op?4^>T@~mg?I1E^`lAhrLKo;A(0F z5bZc%X$Y`lD2w1(r2!sEi=Y8V4>hYZu0y;4Ssq55u!n>p+qF0+9NCmH!hPyt$6G-G zMLweeT?&`SFknDQ z@}0_(L+=p_n>MZ2MIWZWbjnOJl>JqNiBCMt5%ro{H?2db&d0Fa97A(1u{c4YzrrjL zag~OwS6H3m&?QO7Nt8s;JcB4hm^Qq>x&Kxa@?=as#dtue%O}r|X5)-$VrrXaEGJN` zHZ0}|B9DugYjZQ^-v4#nzS}gx|omZIImtg|-4p!x1R93jLo7L+=Yik8`cVN5w^3mf`Rh`ON3R(;L^L z3wJ&aE=F>xqXN0~SueJ9al0|K*%zrZEq$RQLG3pDnj46UW6{9)tDBKt>7-NINfADO zE;@H3Iy)EXH#-?gtHHJk@-uLAa4V1LfmPC$U?F>-+e*y@H gTZ6|w$fcz0;a%P*I(S)_?gLwWndu|aRtbvzH>$-oJ^%m! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..259d4d32044f52087db0f0ed6cf27469610248fa GIT binary patch literal 2208 zcmah~+fv*{5S8}Muon;s#ux*(6UV$PDe~${h09{7#5T3UxT31mcvjPF1d>J>X-SsX z{6ane9+SUFK0~j0;ul<%s`O}gB^d*jN~4}WJ*T^MIU^bkqTxaQ{^{f|wEk6-{87Wn zcX);W;J`IY<2o<6x>vA@y0VLCm#lJPm(Z?Q)x<8NU9+YVyW$dW+N$dsR4*D(J!zh_ zc$L@q6eoQ8vV#7pi?!WQmo zZ*Y7Ja@7_b+E>`9wx-8~LugAUh=jyhE1S_k2v@n}z>gdc=o!e+5x#PTL$4=X%2~vE zECh5_djrSiAg?M&d-X^SWgKGdeA)KSiiZ+2vuXom+c8% z8-ZPBJIu}Cb{BpbKo~7Py*q3dfcKbv`dBkg$&49EU|z;zJj<<4uv_!2eKK;A>Y4tv zZoDF`7Z91lFapnvSTqQY2N{Ec5@*`R(oNum5sN=0jgAMh557G#BM}%Y*@6#%lyUX8 zfE?@hgqQ)aQ!GYCMy`g;oAN!(_;P5DY>!Nh0W2@qZ0by@K29Wpc8=*eDeti$7#jDo zoQWf4Shx=4R(D9v9LPXAAsCBU+{Bl1%GqAj@juR?QeG+h7z^5 z5?3kZ!lpEC^@ZrWV_ie7l?AnInr50Ri^?;T^CT)Okx03(DXmNtbcSA2+j!wxp0?vh zAbsXaCBAWuj2f&KDBPl-`DS2Gk5gv2=xN`7j>xcdAxVi(!Y~V)N z66U157pDR%rxo>6^_Z~Rh@$2u_sQd;6YW+HES}xuN@h_%opa#3jteo_>6qKy{e#2T z-MHF!MUS~5mO8PPx77ae62BbQhfSY%{DHTV?o44k!Vg7|b7I(dK^PnKjoG2wG|OV* z3v&hIz^Mo#)b}u+Ra?)vC>kmS!kC;3eEz2UtBAicaue`tN@N8Qkvx literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5b266406c0aca91c41a864ec31fcc0b197120712 GIT binary patch literal 2632 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 BkBk5S literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c53da818db9b781aef8b6e790327ee5bf2962e29 GIT binary patch literal 6303 zcmd5=U2G#)6`t|Gubsr1{M2#&X5B69(&BUzD5YIW9oxyK+x)EU1ZpV5*!Mb_tnG1U zCd+2U8;GY;@d9s1*@sqC>@9AnCHPcJ{m9 z{mwb}o_puc%+ZI2hyZ`&w{Pz6JSPYrVx#{r28~zYpZW8kAW)$uP>}{&VmnX^iim@N zL$xr+A;6JZl;beqSS`+Rq($0^T2d5P^k|4h_lNgKXq3iioD!ON7y&+hlzJ%CM!{dQ zCAG(D!XX)sa0BvcYJx9l&Jq|tdbize41;6wupFf zpLLOAo8uhDMda8na1Lz|$@F%r%FKqLbuHb1)=00VbalOj zlyT6pv^G=jF~ijK4pQb}yRNqss#%s7sK+v6*E3B$z}9?Uk{_lyO# zt1UFtHjGGV=}eG~Z3YvGiyQDTWSn;4m}MbIn~iF0+3YNqohC zU_}_8lq4Y>XIhlSYe{K{gukUxYcCyuW>^{~kv*pE?pf&|Z$C>y)?t^WL(m#toXvTm za=t$YRY8QlFqMsT7}RlToFp|cYFq2<@_-&|u!e3>WHFyHDMeyn&J060c+O(d1R3H@rOixJ-F1={ zml7n{?3(E)ScQ8_7URv{lEtx|{e?&-R+-f|{DQN=GB1&6nZ0$uOiNn8byi`2@`)rC z=X)#bs&Db`oNJJ&lh_Jlw5~SpO3$A#md`WxEv}NViHYPnN-R$F8Sv%fShNNpG0SBpWxVJbS zB8kPZ-tzj#gp?yh^KVjqS0olQJ>kjQIQx-E#$m2S*0uJYpxII!&*jhdhXFoYSlJ?< zta%x0`8kGY zFP^#Bc>y<5rgvVP8tg^R--`|G?Ep#l9UK+@bnjjfV41O1wTmjk%iNjE~Jny;3$9C+1kz{Q?aR!dbU zIPWTKuCM1eirU{$@fs@T&Q>Vp(w*&6rK)`wmES|<{5i^%QmMF{FWlBXLe-c8fy!=7 zFWeziPVDJ3db}|9%vqhj=ilKVzKu>K&~0uxGxEI0L(q{q-QhdO$R~^x-**zN$8c`= zX0@JQEqQ+bjy=<7^mvXn1r;-ADSY?;K*>VjVSnkxtq7 z=I~b(@gV#r#z|DXS^N{eL7gC{`urd-qlO1Lt?_5_FBA=|QyEfKF0O5?da>S*!aB{u z#lG6Vo+ovF+`A}!=8;lA{7+Hazmg}tYRgcmv!Cu;>yrvEE+3t@Ax??>h$Zwl)xWxx zYPpnO_hx%14zpEJbmd%;7gGBP=(G<|oIPLchpvKaXY2J=eK2?(ik2|hfqgZg@FV{k zeO-N&(htkfskh^ce6LmpoN7OYl7thsw^Ifbe%L>tFCO+hr5E=46+5aZRBtGXJ+iCo zyDjF_E}%kVz@0DH38hzwG2j>w>{K7aYcmGiXGou@<S!l2T68XzcWfI63${VMa%7 z8CbHhV~ShBvFCQ+n;%LhYzyl~FruSo!F z?zZ&0+A?8)9-3RmTGP5yG01mlsdLaS`c*8`cKWM&w~qvwYhc2Ox?L$L1*I3IVvi^- z9qI!ViW?RbxlDVwsj(I{mHMI5fd?F%m!>~RUo_fJ!WX|`fulYh>(KWFV-u3VFCX7t z*wfo=;l6eEK<(@+ZJi#p*emcj s6wKelB@|AI-Y+CZ#DM<`eH=BTw9KSPcE_-)&j#6951J literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b2b22bcc92cb181b7cccd9dc5f0a06b0bf819d9f GIT binary patch literal 8375 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 WXzriE=busH$*V~4pEGX5j`&~kt)A=v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a029067331c7a2e2d2ef858ecba358de161dbd59 GIT binary patch literal 817 zcma)3!EV$r5Vf6bnr3&~TeuQ zcdlqZfY0DJa^=J?R6udEP?a0DGMVRjZ#*+r{eFbpioU+R`sg9_tpN{L=)j@-%qM3! ziV?@0WH`p{pTvZFu?P6dk2~CtgR1Y&JK0Je#vw)!LI=DX5(lr5?D_~+=L3wI&4W02 zgCw3I2=p;Td<8n65f2w0Uwwpl1fE0Ph3EWU<1dKw`YoqDy}=(`9cO5;R*jA_0n*Y$ za%w0LcwivRZ9#73yHW z#=~lpo@S63pk*=DiAy+FrC!#RD_&;MG~8rq1l1F&H05e0A9DXKs5H%#mWnN_RA%<} z2i!@Z|s2h AqyPW_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9ad96021d4076f9edf97410f0112111f76da9477 GIT binary patch literal 1016 zcma)4&ubGw7@f&(HoKd|P>KN~wPO_|&?Z(v6l$T?Sc=ltSc_edWxF%2tDBu=X4lx9 z#ETd29<+DGi+_MZd-k&G!E}qDc=DFmi+FN2X{+9x!_4>1d++--^Ddv)fmr(1?Yoa9 z0KO&8{!;dg-8aH`3NSDLhB)Oy1NP38k;0N85qMYKm;5=6G?ooTOB880(@+f+f)pSs zNfYT+s?ReNqOcRv8Veg?3*Az`g0K&%r<_}LAq=j9S_Bh|C>J?0+r3icyCg6pXh zfM_QHOGAJaLsY}ewPaAZ@)2=}Rn9d8W< z6#0w>bR}FJ!(`R*NMGRCgjc>N+2&fxc;)G{K@xT=ExI;bD;igNQge|tp zi+2cGW4?`i%f4r=5#)G0uv}Nfb2zdBw5LHa9Bs{_l`ud-A|W1ORv=h5#fb>njr*)> z`A*fg8UiwH+GvP2On>E!nIx$A>j)E{c$g#VwX|MVhfZFMVWl;S7F=R+f$On`SI0P^>mB z<_RK?i!=3wX>+DFSF2Ag)-Ib%wfc?O`MEf29t_Qlb3M{NR6IaD8kjuveagf@;zF-m zGYlNp2^_+i_^=?(&NdRf#u#OBt{!@U(;z+XCD={g>(?pH?CO6^F;*w~Gnr(ALYI6k z{#I-q2}1Z4T-^cL9Z={fpfnPJV!JT-sWAM$F#IsrK5|T4bYvNhe3s8`51oB|ExL5; zm%1vDOWS&}t&7{&lbZvPI^EW9b|t7S!7sVPQE@anJoe&xq*uG?ly+K#&tHfx z%tz-JBK>+dBWZQmQ9*thZcc1gq6I;$cQskN0e2-Ot8{>pRyvZ%@w1Zw>fjFm;YfSv e_y@U^ls)o`4~Pz)7p4Zl)<9W^)zz; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3deb11d53ec67dfda4112ad3ea3756c572640ecb GIT binary patch literal 2335 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6cd552b603e82ca3212faac4033717589eb89efd GIT binary patch literal 19034 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..98a58ab6a255f8224dcf0f434a08bbeec087c874 GIT binary patch literal 1965 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4acbbd19b70e222fd3bd85487248dedfdbc8d8f1 GIT binary patch literal 1000 zcmaJ<&rcIU6rSmBx4YZch7g-rgN)#X1X|IAV1guwl@Jx75+j?K&9XD#>UL){vumXn zjc4y3l)J_Ye}E=%_Oj~1vc^Q?$y+NI!^zneP%qA5=Dm5}_q`u8@A7#ah^6n`x%Wr{ z;Ctd6F6F@3dn1fz00RSHh*K^!VE0TJDJ&Thf%nuy%AeCnW7$x&gpqbLb=6QINCBdf zG?8AXdN@NN3L6%;I?j;vbAZ)N32RLe#5$3LpWT$YUM}%AQ(ZgXMIJ<6!=BO;xRDwL zL^}gm8Um~s$_jW|X@G~)3TVKQW6kQU>kuzMmWL51>;YlOb}h~cM>b`QaG!eE@zzm5 zk=B;Y&=KPI?IBOma%Zzhf&|#=}6!Bf;c;0PvTX^D2sEo&3XlgUh+=+gPhrw|4c7dr+TxPX8i)6d?o%?Y;6fb z_zhhB46>g=p{;<@Py~vtLjR}2;QPYh<6P^+DRI%3WjORjp4c5YzjZCTbo=ALN+g#$ zDv(RNdaDN0F)E3~^-0`S58XX^dxfJP@PCBKX74rED(S;k)wVQBHQnE@LC~2iFi5$P$8KCz61P~6j22OvFOG(*7 X`@Bzd@S-r&2X^{0Gbg2;5)}Jy4{k0G literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..255cc73492c4d9f5218753b294d6ba51494d2cbc GIT binary patch literal 5196 zcmdT{O>g7I87B4Zu_Vh}f5`UQ=4vBP*?1QT451!7;vN& zV)2vo$qbFt1f8OUPCtzSKXsOQDzs+7U$QIp zZnWlriz0V6Pg76D)&g7nrO?W<;oNsUB-8H!`hK88B-1=_ zo1A;qLo$60&=%)j_mFG*2It-c4v|cM0O*IDd&@(v?c0GZBG>jE&Y>+Lnf?gSJ;4s8+1^gTcY&TV_hwcX(y+9Gmoi=0DSL^4$Xl{mNSA(`$0Ds%3A z54pDY16xEg-G|-^=MFsNj&qfBIL?S@m(9k>SPnjUg(4>^anh+Nx;oWmH1 zWIEFj@VD{7>H03~GgGq}l?SF_8y%x7w}(eZ%#=F@W%+YlcpIWaD6=}IKClfFS~H`T zIxvhbQs%I4>piAEVy2}VeWa`>y|&R+sb*_!&0;{s+e5uenRy-qx97L;5`~K+oUV0x zkIYSNpl?$4$;VossvV=(ga16w3v3AzMqeGYx2%7_9zgz!iIhN>d2qQrA2@e?xIS|m zBv~>Uqekzc)*Uj5kdRKZqAcExN+}Y3gk&}Z%~0+$G0-+_7%nMIV$?WN$MstOxa;b< zxitXS*|6{J$S^xB8{y3iNem22w_%*JQO?YecxPyutZ!#yoVY=zZPpv$vl*H!8~5EX z(=^O%f;Z<$iZ|6Bvn=hHWvAru-KpHCVL}?LZy9D0MqYY@Odsj}V`i%MlYyH-dTTxJ z4^Dn0&6l{Y2{P$bGBwal+qJT+SEYBEg)lyM^*b;kMY}>l3pPZeEcw|;k(mY zH!t;2Hy-mulO$pqk1ampQ3+Zw87+4Jmsd&Lmq{eXpNz$wMz=#5G1nwyEcYoAt(6b% zf0z-Z1c}tk2fG98 zb3<(?2aRgISZEg1di4YRcf9wa6@nvGMm*oalT}JB)fJ^!tvoF3A1dcvJaQQm9gSw8 zspwfz5cCzeIMZXU_0PeV{x9sCMwEh!voI#`UZ%|tYl^O+qJxULaYe0AZ}R@%U{^|o z7w%r+py-BuhBn_|$D+EcG{^gPs+9`p^e>>w(--05tY0Bf>&k;erP0(MptONf`6_7% z24lOhb6@`wWh;=VAGB6&l$+)1LBkLHI0D}Oj%Ac*pN^fU3)>Iw`|VW;Uw`X~-#`&X1rJhcllKcv16 z^2w)mQ5nSk8A|7IVvW|GA9Vo=5@+?&z97^e(0=W5O%P-W9j#1?8qK;=sQ97&hKk$I zQTPGjwkC3&a1%VZjayD!RjJWYRcGedFyJ`jmPb$qny@f&Ll~8NCd^#H$&4|)05oAP zBYmL=G+|C4eW4I^vKQjy^}fx_zScFdE_4#AS0*}ZaA4D+fM7K&P9dYZTV+0Ui%ch0 zGNpmLR4|+fGmfn$zf-Z>w;(whDD2|L?d}C2p(EY=P1S zGrco;N$)a;6k$o$%e7{;?!=F~MqBGzkf4v&wy~hCxb;_|PnG_#R}4yJ%W?ag#$b#D znP*@*l6u%s)E(Fq{U{A*M(rAKY^SO*zzWfF(nmV$QcG<=;iVO9{#KA=AetQ~;UI3> z;Al+8`t&owd>;mZU!L8z%+6*|7hm#{$q$}5iF-YR4!i7KcmoL5Z{ZS&CPn`n5o2N~ e_(q;3NHUxb$3jUlEheBZ17Grg`=-UnSKk7bL9y=u literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..18085db0fc7ee2e0196fb8f5c57f5c6374995c12 GIT binary patch literal 7039 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 GWBvuwZ8seN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..778f4ccc021625872bf86be3eef83a41e888fec9 GIT binary patch literal 819 zcma)3!EV$r5Vf6bnr3&~Tej z%oX(m_zZp{S5Evw1uRY$s&d0t#xtJx#xpOf-;a=6(bu;ZA3cP=HNpKAy5NcX%qM3! ziV?@0WH`p{Nn*mi*aLjw#~tp+LDhHnoop!&;}9bVp#$DEiG$Zjc6|iPR|AZi&4W02 zgCw3I2=oC&dK^_E7fQ}6Cf>3 zB&P=SC1{#vi7{YksS!PfBc)^O|j z0M~10LL19mJtLH4!mv!uK%Z2DlXIX`U8J-~(s#)esF21cS?1~qLlaB?aOf>wuU^p8 z(o)+{K!Q%JL#2v0B%0=zdNV1+CWrUuu78%QJa5k8belCV42vtup#aGZRkB)cpA~9v zx5mS2ogQV77@%b_)rqTkrAobMELXhDpqaR-(h#agRB6i9Oy0--+fiv6nHG!AE*5r4>1}(HEpw9ilb24~BIypPS`(#m4h(hSMS`njfV=@4W9(Mgsl zFERt1r4fxC6kZeONcIW9`kI1`wg_UAD8kOKDSfLTt+h;FFZPk!kr!}~=>c5L90bHT z1XvaVtXbM3cvNeFd&(kc!LeQK`mFDfAVN-n5hv^}VaW9z&Iv~@WsLBU2G|Q$P()G4 zXhfId-b)o>MI5|RLOM1!``FYD3iQz@*r$I-k`98ORit}sVrT%|D^ zG}fdz_KBQWW|4&UaYPxyv=#h~{kNl-%QX!c6A`68pE@;`Pjj}7scYM*DnqH>a#$dM zJT0GV%+K2A>T~tR%tHN)eXZWOdj9fUnzw&%%}xt_&`zlQI}*^y=5ZKOCN`3m`qSER znhgu0{9OFC zxVj32@GH3Z3FJS4Qda|&(FBw`rQr{y{clVA?-x3I_6bKS+v@>$x dom!D?k8bfHF~QT)%n;ZZ%FXOmHY!luzX0ENHC_M! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f07eeea145095801e5c9027e623393b9eb56d4c0 GIT binary patch literal 2036 zcma)7&2HO95GM6QNwj`!Dsde*bzB5U;-ZxU_gWNHBsnqAq=J|j1%Y5gamNl_lB?`e zZsKcSL_YQbdhDTn3|@PwUm-w&&Qc;JF=`zWT+YtS&Ns8m`N(QDUBOHLLxd7?NYTi;geq!YYVEATd)aR zmpQaoC)=<|>o~(qAb<9$AhZFSRT_?6j{?8aFznUmawy^uDHl7Af6n*qk-Jaf?Q1)rL;yc`9caaQ;o4?m zaG38)htcpr{GP|-x@N6Wma$tHd;3GrS+FU}!QYx*@50+B7~Y)18?W@rV+hUA4#&cH z&`LtJP?>n54LZ}LS_6191`%%WUF&%WK!>*Tli}$#GY!^f!uWE&cFEv|xUnLO^wmds zr)aPB#yn^{IQ76-$Teg70PKrZU~!bxHAOmEL^@>LfyMosBgLj{RV#B^@?4E$j&NI7 zHY=5z;73M73LRJ6conSstAF4ww?(45FZl@iU^!KdsWq&`rdMfFm zLAt#kpR}KK&EDYnsMB+Q!?xeM6O{;|%poLdoHB-A^0bSQzj#hATO)Ix;OcQs5S30nHncr~sV36SBgx56K?wU4+5znaW51iSy@;mv z=WMi~M7P-j5gFu#*&`i1q6Edn1M`?off6=~IV;+bbUsUph|uOC`Yfm*puZ~oK2{nJ z@9zrk?8}l@{SsuMtv=6#RqJTc KR2|=ifBpsbyr>rd literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0cf115b6f29668a84e7e10e59bebcc33ae0cc750 GIT binary patch literal 2460 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 G)Bgj!b$ZqS literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a07fe54300bcaacb81283c8f8060680e98aca297 GIT binary patch literal 15003 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 QxnGU9ob&mA&GP{N2i}X|LI3~& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..88d2571bca454b77f857f6d46e9f287437498bb9 GIT binary patch literal 1377 zcmaJ=%Wm676eaZ_B}$eoC3YA!mDR55Mdc#h1woKRRvH6s>`HPL1cCv{xwh$&9Cn5@ z5^wV(^0GzIf9P+_wkvhnU9;*PN~Q$?Ee9N)Id|@3?i{kMElb14`s3Z@k*R5as>S+K z!^Lxa#a}p4&C#e%OQ9Z>oU*QP8F9s_=D32`aLgQ65!al0j*ZZY8ctK!z?^S^dD*&b zQ9%|%s+FhYOy@%+2&h4yFpJF8XpxCESpSGu(diU+? zz3(6f?xsKo31=zuSvc^rvoqiWpHUbtup!#ME-ebe=RuM(j;r?FiX@DMisD(E1`&`e z;38nLqQr;DVUypKq)aahLJE9gVA#bMYO3NSwdQ;5eGXJ9ITjm6Q&OoHy1IxGo z7i+Wif&NwF@!s$@%y6`<=5=l!-a6DRJ+n)=x7tN`WtVf)6@ z6A%fD1$4jJ$TIk^EUy5l=lVZ+M&5&OukE6owFaq%yjeU~Ei}mN_;mDop(MOGQcP>f zkjePR)A4k+_)S@USC)2RnVuZKnU44Oq)7;6J|VJw&e(Yf%T}Qjcu9FQ$laCmvSPN^ zm|O(BvZObJ!1D{&o3itQ(kbu& zlU@UT6@(zI!|`-9IXamgPo#MsGS3YK7I=&LFO{f&03qCScoEZaoJEJl?;_;VK4Zxm ziQ85VLUzgNbWBF%F3S{eC}b!`N?4NO-y~#*h@vPWQkSQ=XN`iG{-W`(@qGF8X-{zf zppgHDWQNHHS$i5WnuYL8M<4M7r%JV{-;RoI=%r#*KGm#dxm7kwO}&K{L$`|2(kuV` E3+Cpbwg3PC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a0161e1b7d0b3d1a34079fa142146bcd88ef3e46 GIT binary patch literal 1887 zcmaJ>O>g5w7#=%LoH|LxeueJ#3)rOFUC>r6u)A%RR)V%9ZGlK=m zQ_-GsMB)eV7l0zfU*OCoVS{2>+5_SgPKAXFCuZ#036WSUnt9%5o_Xi>JCE~IA|V0# zNI!m3+ZF-%jRpP_y#mh#9)Pa^0%d?W#H*afmqQ%GA&P}^*u?_Hk+SIGFvZdGh>Iht zq{Yf{4)6erwTz#!bdRL=;6MNJ6oTh{w8FM{3!=#zz-dpgd0NjH@Z_ zT6w}%hhLkpjo~yJe~B)_8DEW30T*-)U8CH@knVcG^>wsXbYn=z z@_AgKJ4>D=SJLNm?_wPrvV~E$ZV-zoM9o$jM@QJmDg@zNkJYroITFIAVpQuEF=&+R zN3c$a$|$3uTU8CiQ*4-3qBF`o(<(%TNVepPY+_1@l}1%X*yyqQ?!C;0FdZClo>8<@ zBQMvh`4d%v3ehxKM>)49x=&&R(P6!^X8uVxNH375qAXR8rpjCol6w2SeDmLOQh1*< zl#`4iiADTb1DjSVFGWtUOlwPXS#CMDHkb3$@jCu?#7CtyAl=geyv6b)HN-|g}?d|TA3M+Gt2q7X0gii99 zkYg2lt)f;in4z5*>|ePdFlKCkaZO;jH#r7}j9{>Lb>h9hp`Be|hk^p)tD5LKMO z5JsG(o@Sk-Z)-v75qG|F7we`o{s^HGHmdZ{bBvx;RqRNch0;cGdvAZY=!nNEsmQ8H z3-rqnZdPCa`J{QyR*7r$$fTXgk4W^TV2c-hsfc;ZS^oCSI&4?hW zTl8x(opEReMM2Y&4U1|9D6FGzz;pU~;Ga3uQ1XHEhuo5@pE=P78bJ*ezir&1diG~% z&d~8P#BtmoalkD;1Ft^=@A$v_HpsWYSQq5mAa4s3U16#%Om&6%wlHrCVOxm!WA?9v z$nlpGAa=7QWxCRQTbh3=-D-+|M*#PbyNrO*#a3dbn^=Sx{396V*L_;HxETx z`{;*aQ~EWQ`f|M)wZT#|+67B(u=KU`!&V2p^&h5nZ>P2QQ3rf%i>a=-@UysJ&)sak z(&4A=rIqIL7x&xzM2BCr7nXzImYu)TTyDjt+Wd5f&)K(E`_XIm>RlF1xA~b4pSN?a Sl10Dpw=a_*x@hESoBjoCA^I@@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9ceacee16c1ae77c092b9a4e89918241e9b22f2d GIT binary patch literal 811 zcmah`!A{&T5Vf6b2-#h@(W?)=tOSeTQ1t+aiY_ZI6fLM#BUM(2hs89pBims`j-_AF zYqcNH&**RD+Eaf4wWUtBMf6l#p3HdO8_&F`em_EPMPJ`tee@9e)&!4N=z@KB%qM3! ziV?@0WH`p{m&Am7u?P6dk2~CtgR1ZDJK0hm#vw)!LI=EC5(jUP?D`0nZw44On+I|5 z7D+rq5a=;PdBrFIb1vC8a#zV(dur++XHQYKJ z;Ck&sXk(eHXM~bW7?!CS=(B2Yb_sNKRpKMHQ+Qce|g|<**Dn)5yEMgaA+3rm1>Skw|*==l2 zA|Aba(B2g<{s9W@*~_X2(-lGSB-ro^~cFM%)B@6`@Z*M=4~#g0kO25+jk#H z0DOy`!=)S;d#{D@1Yn>83~|DNIvkt{J%J@%BJi$yNcnT>Ni6G1I%XuDR7=%W2oiv( zBuS)~i6Kr`h{8^bTMeg3@+rXTmW0zC5yTpigrC`x23|Jywh{w7H$WalUc#Z$L%5kZ z4M_S7U`YtDqAM%lakUNZODmua%SSr(nqw2!N2ZGrCv1Z-WH~11gd>YGMz}{^Y`g2o zr^sW}r>nu*1SV^?ONIp7BE0fF&bHW5CMy>wE0gnO7!}rR#(hKRahtk^%?*dP30v-z zm+ug?&O8fwrghI;C&+fWZ#s_9vpF(-bihFo9PiAd)xbx7Od&2|rY}$y#W4w4tqoQ+ zJ-d3xu?&m0TA~NTTRmsQ>J@JjVd4=Nb41-vdXUnlvojH_bjDG`Atom%@Yb0r)HZ3r zhDEg~4jdA98>_^G&ILpn!nE!Fjs3TpfX5>mGA4XV9X>r%&O|B0z|=B~NRC61+BTUh zfIKQ(X*O!cmHJ}6Ik#NDY}~9jZ!9b=Mj7Mx#Ed9A03C)3zauX74IX$NWnvmpe$cHE z__kyFHsMVCRuE+tT5-J27-dnm8MwaPA_MOQ*oz-){|skV^?xIb)al_sX4$A9#h;45 z5nE4!5Pk($KY`3Aknbs=SPDU*n;-p{Klv_y@uSxu1k*R*m+aB7FdiPAcz!+9s{LdleOAclroySE z@M0s>uJ=6GfSr-l+;M5A2*v&j2!t{K literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b060da1c8674ff1f8f33afb274cdada37b0a22c8 GIT binary patch literal 1203 zcmaJfHW5~yttEYYi5)g)X6n znLxV#ND^9t74n#tp++lf26;lOQ)z>iI)?tYmxr70AOzvYK(`{nWA1Ub)g7Fjf!Ojm zh58r^HMUJFJUT?q)~x&xn4 z5MvA7j-Toc`)nvWg7*W;s`fPWi*QsVgmzgQG^L)jmCJw8q~M+1^w+LMZnLQKcT|JvJ>HX%MN2$z8ie zS|eq0ha?1u%mltif=HTMCijR?wR>^uei(xYT_!XyDo>^~sf|;Y`N$s}>*2tR+7p&f zCnPJY$phC9m391S|LCxxa!ojM2Q0>tFeag0HRC}PwCiWOJy>l_kecMwZJJT58>K3H zlp-ZQh8_Za>oZVxv(;&|506hiwpFgjc-LhT)4Y{7_Vrl32Pxei?1i)y4ua-vV5OSe zpYUi7#Aj0jsjB2tr$rj%E=ot02;(jVN_Z6GJWEy4Q#nnF$k-JzzL|p{q(2Pt9IH&W zn@uUa9bNAIsn$i-qf3>46>vIW@Y+NhxrL2nyH6rQ!$UT5u%6E{j-H%*L75|S2M3I!3Mpw>1dqLj!^Bd~-<>zzru*6xm( zSw~7vJ|e*piN65Kg#&*8u3TaoWNW1!5aQsQQQ^XgnYGt;Q>iO!=9_Qcd-LA=p8Tt( zsepg#&tEiti2(4YI0Rp81fKmQ0q`9_pazhHLY8EQYGFy>FvoH&;$xZPXif2PgyUFk z#>Y`hwd1vf1VR8SjZ6@HvU7H6atcsmIY5&gDJX_!kc#4G(IM+cooQqzHUT;tf!aLJ zGhwCdE42mRI`T5>#;w4AvLo$`*Dt=>BCp}b~xO4>qRTJgpzm>jyr_ra2m~DpH`nQtB@iNa_je-m#Z|$h+j%8Gbuch8mBQ9wfk5gCkfp>mi z5cvxJvW1zONxyi(n#5ta9`#dw<1{_?oT@{l{IFjVmB!)DgJQK@IoK_h``>V8W5rV- zM8ts5OCAw&WR1$^GEgBtcbT-C%7U4jVtfIp-^Dq2Ndzj|%&jj8;iWIVlQ$g~Q%AR` zA{=6HB$Ky%pj#%_5ATWL;2BkhDjy6*_M#!#yH_a5DUuv_$$5kFV zr0Mc`X5PG*%285~xjJ>Z%@l;QFBk!SKG?JQ6POR4WeLn?OeOj)mA&&>Jx3ft8I6*?EwJq z7G<9MY)Njbf5cN?Z?|JTu-=Y!!FmU*f3NHBDB=Ioo~PP)!k0$+C}JAZ}auh;rGzSKmoBey2&g39~l$E*8l(j literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1578e7390e7b53a4b4d3c888571b4e6e6e99fe46 GIT binary patch literal 3047 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 muKPJ+m@#rQx9}}Md literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ddc10a1c267062a926fcc7790343022b827a3b3f GIT binary patch literal 3682 zcmd5;O>Y}j6rJ(6CytZ2O=7$0#BrfC1)P%dSxQT4J84Rt7(2!T6!jnt*yKEu)`nod+(8+`%Lr zJrrA0Z2FPV%Cgi!j!m)bVF<=k2YEI{({Kl!u<+i6&qaHYj_0sICs%UGL-?%a<1SbFL#b}Mc@$0^b(*K zIQOEDJl)HjLtR8(?n|7*TtqUx0%)FdSAFE=zRWqyMdana!a2-EB-2*`l{mNHBQN(F z=P(zMm-`y$Fc*gAxp#f!>E7fV>LQZq zdw^~QafoC())eqJe|vv+g&E9J97YRW%XG|+sTJD2?QLciIwobMBiy9(qAO8mcPzE* zm=@fP9p90zX=+GWJ;PCTMjkNBR!svb`=Q=8H9{3fX)87ZD&Fp?8fDf|49XtO;Ux+e zSJLJYR59WyQaZN$JBMjpe5cKq)SMc2I;mJ?L|nG{yi&`z``XcoSzIN zyfR+dqAewAYNzSIB4nndv}zO8P#sm#)O|*?VnMu;$Ydl5c$lK08avq#=gvzhOkumStK*XSS{Euxu3QWbu1Z%B(P_FI@H}n!h2%8|;%F zvz^R>6x(GAtQBuzWO26CpGe7%zRQP8enOr|iDkxUTj_k9xppeKFf2L9n3JM5E&-q1 z$YSO=q2FWi8&S%_GFjMlqd)C_=e7Udd1gXNZmSw2UB%hu`pNvwvrMM=g(yuo9kV;A z(X%Y^*}D1?)~~MgZ1#M$^_3{)8q9_*`+W8FHEuWliq!Ei*y3+hf4ki~!*)A9oU?eYt{pAoft)0@)pB!VX`_1d z1D?`9B6*IiR5$v3dA(L!TC1qK2*W?)@Pz@xq*48Fv)bHH!IlVW8ZPe4ps?j?wQ^^v z{DIoVkQ@Q&21+%m<@H8ob#0|DHx2$s{S1TVh6V+qzhYc*cwC@*4+7OZly^k5+ZFhp$K~n^rjpabh35pr? zTfw>w^W&GNmu;)F5E#V4XJe`R&`sRbP1@7gEjTd*`!QT1(UjQ#M#PvH3ciu22`Lp$ Uhhw3Xm=+T-CjBqpv>5s2Z|D>wfB*mh literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..82612d60a2d4ef242cf50639b1c4d982acfbb431 GIT binary patch literal 5029 zcmd5<-ESLN6~8m_S0-`NxM||JY2!GXIB9q7HvP(1n>ep1q~uYtFU`Bap)=^&AGP z|6ramoe>_v;n%!vS(^O^x1Blm#v{@I@Om5`?m4*2W}VTGIPJI6ARZBEjid`WEDenT zLA>Iyq!7hu-K2|v4ZC=kD8_gYtu*T5g(=2(V}M0mJmSyB81FJ*<1XG6iZR|*z$RS0 zD8(3$=uWzLF^VzX6kzYTc+(VPJd*E>i%0UcF~%dhaTky1+8E=_0ygL3U85M|5#8%9 z9?`Wi#v{7#x_Cs_#u)ECz!ENAl46WUbZ@wLMAya`kLccX@rbUCG2T32w_Lp26l1(Q zfZcWR?oo{Ki0*=mM|5qB@fHDFa`Bca#&{&(6&H`>Yh#ReAFu~59`R>ml=pJ*<>1`v ztBFlq!)mdCrD$DM8cJEIL`%(Q&#)RTD-uqekh3_)nSzA1vMSdbiVD^sv&6chR0yOt zYYn-I#TQuBWTi$R?Pay3R79!RD3*#E2FPD($`uK#C*-C(nIsP{JWTXzvRr+kCX01B zS(VGGQh%=0uvk{A)nZLbSfRmkfdEQPte57sc~~g?_f??)ahw3hkxNvg0EZN7a&14%TiCGBPcU3oREeNH7CIpK zUMQ7j6^A`wdqbCmAXZgH6{}d&iu*Y11-Li%D^3V);)WxfwiAsk3H}`Zb`xujP*U(c z$3>`>6+++qM8ZiV(PlrirW5T25m5+iU@Vo2k+eob!a z-w^PkxS8E`@bvc9R_aMcu0k7m@&__~xx=)W%YJn)o8Oh8RYvjvJk0Swvz|(S zMXnRdm=i{N7eau|E?+ ztUH0KO`XA^a-R}!@cFELvh#5JN%raXV^DTJ-*@1jF8N<}e%(lpqMQXX`FvfRv4Vb& z5Xaj4s)On^=oO%#_U`@SJQe)J7VOG%(wv(x{h}x-Wl=N-_Z4Nog6(DkS|=5fLuQ}I zS|k;`BJ;vqh}ATyR_AkpbQNzmPAX}QfbD9Y%&E6fbLT>heVAA zB|>MTHh}XMglp0-&}Wby`sa+MmXowM?0hXI>MzZ}N>!1X75qR=!!o!rkTwq=`?QDS zxWDxyZsucj=VP?${4N+Md4w(;qoj_KhG+QLb6NLXKK8_PPt5Rm4Uf-x#{VAlbNuNr z>YF+eBF92Z7h-ROcMkpk@F8xEJN2P~nWNy9mS)zfk=`IyQs-~9 z_;n+dVb+?lxI(OHou6s(3&y=AX5DQw-_!X-i(fNV*U6l1JvdX(&glOer(KD-&d;{^ zrFPV|wQy#BnN1S)4V}N);?wP@Z7b!jr&S4BPpf9Mo>t9l>+E_iSznCi{&}BgeIqMQ W`i(3mvHr-foqnA|ff+RgXXig25G zV#F~g8IE!LCo$n(>;b;;;|}-Zpz6ExPPUYXaflIw&;jp;#KBu6yFP;D>j6g1=0P01 zLlVyr1bPe+UxLnO#KXDAmmeV>f#;&`+;e`f@#n;O{T9=n-rx_ek7G1gsYd&m0BLC= zIW?fKK+`l!i~&Par8SsBNiO7+T15-3tU4{vHaMIK2@8Tq!3@8<@z7xvYz?1o4Yytn zaJ_aWw6V<9GeSuw49nCE^hq^1IR`q`MM{e#y-cP+g)}zFGFML+nppaWLvQhV^@^UB zmfD5_5_Do6DpkB8(KNr(n@J%ybCGIQoGS?|ReA14Va4e-Yc3fU*OWs6k{haIwcOq; z)ZT85ht)bg${;a7%VMe%SM*wydeK|1c$q;nbW^1vR8OeVl&hJ1i2b*z(lkmf7#mip z%kaW7JL5Ar5I(pu@dHz}nRIkeFiq*ooHHMftA zPB;Hk&N|##h_iERXJb1}K|J%S@loEH)DEA+_E$R|JiJOA!C D=A!&Y literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4b9d77d664a4c0d4611d7ea4f9d324ece5266e9a GIT binary patch literal 1024 zcma)4J#W)M7`}@g$958;s%j*H3P;OQ(Iyn63Z%ADDFGp%gis-B30bc1+PXMC>&|Xy zCd3Lm19S(m@B=_VabpPTvrbye@-KfWkb;tMcU0YR6~Uz1&B)0 zM0%0x^9+S3Y*gIpI78A;09LmothEG)bs`BryCwC!T;gq|dUn3Y+(%x(exwI*Gc^i` zb^@?81XwYYCGe=y1oxyR(1c@$TGe^iAzpwi4uxZ*G#m?9o`sAoFdbZL{Y#n#y3 z8bPbfw~=qzcdb=|9FGT<>xy^|M^=FLH7JJTt!rpG3{a3rh)0+e2$oH8B0_d!gHFVsU~(f0bDx z=q3$WzqSU&p-YmclPHOxHH9ccm^Qt?x&Iaw@?=td#Y8}<%cp0?vT?>VF||!ImJ=ve zn-=o~k;lbL_1e67sk%_D&n;Fjn769+o0qRI#98zA;LJGJBOO4+--t&8lZU=fnHWl3 z=yhv`f#W)XLpT$k7R1@hjRdbUMp>MzhhE?`NY8r?_L4XId6F}`@}HB8)#?6RX4#<7 zCSQ!d7F$Px5Pktyc0qO*6gmnhjYOc>E)0Gs9D7?hc0boXd{kU?WEqZplBYfnpLuyT zI)D59@KPj~x+;)MAN68e7q>5Gw+AA1zO65HC8!zjb8aXqjz>ci&u&C|rJGJ^r$qSt zx#-;W=xi<0Z*((~R)-xGa-N-VaYD*$}Xc_v8$O~LEErvnO#M@ZZ|U92+Xi)w{#6^(+#Ly zY+kf!jn-*{nzVUsAl{g6U2FC>=4%C=@SgnvVpWArKcpMiy4{73e$(t7Xie`!7j~{o zINzG?LYHph7ghr0i;o4NZRnCu=oakI?fV+}ly+v;9&KBidV2S+_Mbuo+)02Q#GEC} zW5I!&o}U9h@EC>u4K}LvbZJr$9{1yfar|m8zevnjpdg+`i5~*F1TK6QDM-8yT^11P zB#!F{K&a-Xen5fW7?}3vff}kfNbTx@7hdv1C-x6R-{UO)nMFVZ@Jru=epVWWcMEeK z3qvQOBzBL)Q5hHQ|1)&zilmPuyYF(A5m5kyyo!yvD#KZajZ5XtW6YrUQTP}lK(>71KQ4Cl!hJjQ9~;W8?NF?&AaN0ZwO zU%iL#g2yvWD`D}BrdJGQ@j8~bDtfo;f&`lxIjMlV&1CR8nnE)xeHT2w*&2gL!#PnQ zILFs9eZA~`wey0Cnhgt*GZrA*TdR|;e6~sQ$#tu<%D2dZTj?%KzQ5j&Wu@<&#Wk|? z^v%`&V*iGB&YU_|&u%PrIJd~@$zb%u^JhbAI6gZW4E=|A-g^78MhImdA+kMTY!b}Q zDar>gskkOt$^^#E^yUxnQ! z1l1#Z7k(L;2?tR>O>RG;6!pT#3a)@Y@dJ?N@z5HKPew1Ek7aEVFxLqLRy()$9xGKm z1tFXXJc{TrO2gxY-U^w|zhH3zM7LRhkR9^U8j=B7vRJZB0_ISlgvANUv5?zJO=m?B zp`-?V3J^r}ca49E&1S=!F9i1vRo5#-S@qhFU(5QpA){#k-{}}Cj&P_{Tl)O2=!RZe Y+?6+VvsKN=Mi8F?7;=A(if8I-frvLx| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5bc63ba6cf5494877276cfd7ea5f1702f13c6467 GIT binary patch literal 2337 zcma)7O>Y}T7@l=(ud}w}#En8o^VPITaEfe53nJo6t>dH%>;xdjz2oS515?m9_hy0T{8dFPq;dEfbX{7X75 z0rp8hf6?eA0QiFs{Ev;@@5N670N()wY5)l+q6vDWHX?9)gxS$r%(bJ;j@Lxjjxjq? z8+GltCh5sqN&pdn#m0;uJnWfwHXQj@W8SyI9*HP{Mv;V)$8pc=MjdS|h8W*E?i;lU zmS;-K>ep(ME;=?0-MHkLMybZSH#gYgDLj1~@jNbPs&RL4 z-m~!(8e_N-ABRU8?_Ne(vnYe7V6a1I98aT(*BKDb`Dm04e8H|AW)3sCYex$Yv4N>< zW3*^eViSdEi}mK-9;S;5K{$WPW8?%!LfBHMYTAUdQ6?NglMs!&Xw$G&9m4}mEtMGD zWgY5uqCq6va$UACbBXn)sv%5I`T3mQ;7yD*4mi4@=m)eQn`%K<6-vynh=HMn>4B=? zyqg+ZJ~c3>5M7rI1kL)owazw4`u_;o1SiQel9`u+`yoBL^=?ecY^zF{XHxYN$$ln{ zKE!s_mhGmMy_NUF<(ZQ~xgQt3yz&+NO%q%8%<@pw1B_8!R=&>eNlCVx+7+x2in43r zD%Z`Tdo!xBf05z#D_{d2MUtvEG5a*R!KK6;#&RyBmqP;*R!muLTKIA?u6r2GKrvTh z10GdMRj}36eWHQIo_`qAn_=x&g99ogjaIR}OEkuI?sVLfcQ1)IIqY<qt8|&g zyI-%yawXPhsmI;-bmwRxxWD?PH^<-A%RXZiw9UNvk? z4OyciUyy)(d2!}1%O}r;JHJg>h zI3cP#fmF~G51r#%#j3R-QxCTv*fQ7`4#+pnnYf1#(+iblaD>qVRl|<7S*jK*Tji%u zDvr3Xk-Dr|EL-22zt2^18(WsVkM9|%WHj~7KyNL_n}15o0Vjj?7g$af?o>;#2t$^t zlYttM5k`=hHj`t^nc!;LPYPOGY8Ym~g$?urc)@A~|E^e6De!`?&|_X^-aK>?ALs-% zHT)6fe~bK2(E5`#XCs0j{FMU2%5(6}bMUeMd#?uyZ7|*ig$^k6qLba|^-lD9H@eh` zF7=|ZUNr8vtAD3OA#yeek~iDZOjla!NK4P8TP^XQI1oM;&f;KfrJbJZrk6YEGx#z+l^MD2Uc5&E?Dh=)$gSrADw^?&w1K+ciNxA6YynE w%yz})U&ZC#!p+v&No2P7_MO%n?c`i1GJg_zt5;a}N54h#XCg?f&>XAvKPQXF=>Px# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..684f6cc9c8201edcecd7d4b589a2b2da78acd5cd GIT binary patch literal 6287 zcmc&&O^n;d6(%W4qJCEU>)o}JI&^FYRby-8peRzqHf(Iyu2ZjS>~7OmXu;B)*)>&3 zREAU>3(BQ2&|cE^-UM+@!4|z0=%KeBdhdBo{YwB}<3oV0Q}=sANh`_H?xHDB67%Ng zJ@P%idGpO122)d64OjM0Kd%1hlBWHGI)j&m&Q+A~FCa{-XiR5DO|R&xHYx_{L@iM_ zD@k2rW+lb-YPw>vWF^y{0xwm|)^nAduJIhVc>rC`JvX( z?`b?E*7+2(xb+*2Wj;u-?2^WEJcVBVg9O@B)c(LgtDt1m=)3yQ^cxzRW-|{oHUkX> z^k&%{dUNPavw61gK&#AP^cY*j=psgEnX#!Am)dKqHJ>*k+ifsAaxJ`d$z#95E*him zZ!_NH4Hnu#qn%Nj{ou-Fsh8U4?AtZ&hTLv7cZ7?V-Va(LwyhqpR*jQ8=q>a>`x|y3 zY!=*W49X50)e6an@e}8xAgG0xE_>v(!OKABDoVHv!ZpO0M)6fiFoPv9gC;XEXGuA~ z?t8b%#hdXrZAPs-d#|ngp;Cg$3p`)>g9Tozj@fl1cpBcRQ->T>Qd4Pf`!!zjNNw+; zb_!(`CHyByN3SB}BfV-=6ID~y$!dxWde7`=PFnG4-0JeJ>L5R(_(|}yU4C|mpHuu4 z`1vkBKg6F>{51H5F269upH_Se{FyF)W{5wl_!;o$y8O8z{yfXVrMdRa)c~<3T3!^0 zQZ{RMuWMo_yx8y2pm*18Gb$1 za!&9!TO2C^k?FfNzs-d_7B9GvJB$wu&pCqc_+i8aXHK0*cLTO}cCs4i5YYz5`el1( z4iXERU8w_JcH3=*yesz7 zhXs)X|EhXmiT4qQ7P*{*w=n_aaK0KtkxXyIK9{ptIo_rTqn&r0H(PG4 z%Sk(kBhPWf*I=gl)&WwaF#^QrQ9^21nyKdvH8vT*zYW3h0M3#C@+3>KH0l(ySO#^P zWmyij#qw+lb%qt#H0msy0XogfrP1$+34n?DN3}T;r_{UG*|J|p*{P>e!{9zx;mev*4*o^?YM!i|%w104RYBwdV zkVjZ=j7yVtHHqLRU|Xe;sqd9(@d#Uk;+1`>$FPA`rU? z@e*v1=`JE7_5$d6I~tvcgNMsRD?NbwcQ|d3=i4GF4;5EC3@}jtaws@7u zH;GWp$3%s|bLdhS$C?eL`Sc{s188utZgH8kOjP<6a4RHn1*HArWOY4Q`BK_wss5y| zZQ{obz|)sfcM7Vu`|8e2`nW-qJ6K;gk{^Be`$s?j)kq{CGP|c?M~!U32|$}1QGFym z*pNeJ^(=IRq)g4~LE+om19ReKl0Qe}JdqL+S`32XQc#=-nm$2J74$WNvt(PYdD?rGzF1awr5T;b$O`R@FON)rgP~QZl9Lbk$<|dxmGPgsqr9 z!tAt{C*_I7W$|_RS)Lrod>nvmH}!LnaY(L&y#cxGGS(j?`?`jO@+5Jd$yxjS zqDlLQI*$pNq84)B`Dec{l+znS)L_D6R^XH0-6)NyA9EB1H?2C@3_q`%;bOH zO8w0|YwG6HyfLGTn|Q5A_hclrtWFMhuNu_(Q?Hmf$jn({&S2K>svk;hu}+cRLVR11`HS(-BMxT9LG{q+I4g z+em&vVE$W_$2LeRXCP1R<;Zb+pQb)*A0Kh{K&~^%)#5vNF>p-eQE!P`)Qg=aD5Dzi zl|l=JCBwcFWs)1QoABk&H2wpS_H(0l4Q4`zS~i7O9B7$BucKNGPTGmgR z4mGTBm|+S`k7^ig)c@)m_KeF0^Q%KGn?B4kn-m?@GTIIn^`6T{4_Bq3RP0dGXpJ8@ zn`*Z?s%f-8UFe(kO#JU>W{27}dzf9c_Z`_~v|HYqY}fv=+EOd0T)tk$Rm_je=wv>m zKXqQb?H(b67+8#IS*rZ*by)NANf2K322cyoJGM`$55HyXaVW#T=momUuDB;i&vJ9 zbGWoF#XlCVAPlsSwv#xs$Q!1S(#ijMy`bmm-^WuEGm#~1f0{Lne_ILTi>zrpF`pJP Lg_8Bk-1+|iHRIYT literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7e397789fa262eedd6c7ef3c7797a3d48caa2270 GIT binary patch literal 13216 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 SGsn4K9}m3h@%{_X1N=X=fRbnc literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ab48a5933392fe6c84d562385c40edafdb2e8dc5 GIT binary patch literal 3899 zcmd5;OH3PA6rI6eo(-4*jB#LaNYjRXOhWoi(MsCj){&g}OjRI$(&cfqFq}xj@DxwM*zp` zgB(W<(n{2mqQGK%LoBvEygfo=G)@O8p^5z{@Pm7)eW5-I{*s2&8mo^37eQ`sf)4GA z^)#D&B-Ar3xi`hqEVDlV^5kBYrRgw?AQKkaKKDelm+1>J5^aGIVrGP-m#*Bbkl? z8V@)`GMxY}&ACY*$ut9KigQ^X$@CncY0k~~NTxEN*+37GOmo1U=iHo+WO@P63!J;? zBhU6l&bx7ek!SlV=g=0BOkV?(=iGviJlo5hLt8|i?G?_U zEh3pN0=mk%*L~#KzQH-PMdaDO$vLz|B-6J5z0J9Id?eFrfZpZYbsu@Q?*+DqWO@Un zH#v99M_ycoa~KzqOqT!^0zE`B9jOZVn=kIp-ezrPX%3^gj%7M#(=>98?&c=5a!r%6 z`~hy}dC?`5*-cCDIHm=ok;9SNF--$0tJ`+;7E^baW$R`eDf?loVHzsc9Ic_*45(P6 zs~ePA2T^c)a2_8Kc(}r@+-&Vw3tC5Cusck9z$~?Cwpv=7=6POVOOPd$ zUreMpy3B*i>_p()^WpiN`(7l2N|)+p5i*pLB#fD6#GH63DUFeYt+hG^Q?WXk0U#q% zio~1uwRW2sb|%D`1rpsft(N9w!Z03QoXz)w<^#_jTIOof5Q$)6o0$mc56)d739jc$ zVmf6p50 zt>JEhN;>y>@WJf$v&>}fu}GwO-{229??t?SzufsFRZ=*BrEzI)p=pN1TEE{a)EIYpM-r;j>MEUK1b$2?r zR_&v&2YemeJ1F8ygs94T@y=RNsVN741(iGrYi1`l^qy)K8aqq-!R+;3a6eC27$gsdG=X(;5~z0{zR=YwOU&%`xEO0 zu|ps^cY>rB!JR%y7`Wd-`*F3btXE6NTt~tnnm$Ps_?|gY8aR)mhY8TGuCE^V{uRn* zPLu`i<&(vM|F6+UMy&?Ry|K0ek$Sbc0iA~a1M1{X)9HoM1<-ZRpQ;vwR8TL|wmf-NDq3FrrU&7B2H zDY&UVhBtT?6aeXCO`ZiQAbqUSb2G={_8Y=(<=Ln@`B7 z;&qW5UK8m?i)KsH+itYHzO-_u;KqtIx6&i{J-gS zjd)MEqY9-}X8CQ>vTiV!6id}YB-oGP5soCq-Zv~p#ev`(ei|pq&~PX^ SkQ9f-ILOHV`W+U-zx)Fy=TSTW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1673f4c570bc88194148a66f277657c7b866406d GIT binary patch literal 5332 zcmd5-g@xCv#BxllzW7V=<)aZbvMaH2=5>-+T;G7H; zC|VS4=hgvw8K6D1$Yp>53iQ}ZFTI$E3KK-<&<&`89$ZX|0(0tnq(s`ZlLGZ+gh2V< z_rCvq@4f#g_5Ax_kfGqi{Pu&RAGK1{U(l$00_DXQ&%6}%Q%a!rDS;M9md=s=DjL^S zu=ebG&9w*CzJ0&B_QE=_Uu~{^SteJrUrSSj68%S@;r;;H?#3wUsN*Ik_=SK_EigjO zYoFycqgEev-Q>P5L;H=8XKgl=Yuax%+1_`tk4A1r5`wR33*B#l8t&VX!(i1OP2Aie zwus@^#KKL@+M}u4SVA4}swzCO>S*RB>myqIyHGE-aCVP`1~Du&J^(8Bu)-HYHfHxt zXacPH7M_JUynDcFaq;fkn8OPL_Q1t!wK0d+23Wg`_t3^1UI$>EE*{3SFo)L#ShtH8 zu`!3&16Z$%*Joo64|V%pya5|?c!Piqxp>1i=J2rI5f=~ZwJ?W=x}z>0>ROn?8v`ur z;>B#t;i2v$7Y}tU%;BN#xQmCn7Uu9K0Go92rfkgNq3*Pchq@N#@KATg#Y0^Sb9l3W z&AE8BTA0II2Q2R5VLS`7 zc_;NJ^+SnQosY%5sPIKm=qf03QO?NOu7l&}&qbvxBMV~m0-x<6+F%4x%_vf#C@Wyq zI~G@vz(8LX5X2$^p+=?}zyg1)!d}cP5UOK*FXilUP7M3N_J-~;K~Yg;h0BSm z${&hhznT7TXr|?{SiG7}+&Z?_1;)QEe&<+Jiy>&z3z3IoHxsM5+N3i3J<}a6Z)nXh zfeld<4*1N6p+~OGT~3FuZ?<>zFIxByt;MiE|o-nOf#W$Q7)A8t|WPv*4{0lpU_NG3h&8RCZmsNrg>Xb;ii6N zQu;Z~+)vvN#h&>XRgUg!cer29Gikn!?s&H_OXtzOp5a~LhFv2}x zr*}8q(LYAt!&`lJ_>SA%cKn}Vj4+qBk9%+HDJWHXvjUTb^lRjFy^T|mk|^LB-EXDY zk}UGVSMJ1h$-koe>Xo_Xw$(ni)SP>IomNpB>NNZuC&(F&GwKgz`7kS5;}48^3jWz= z)WAGY8SWIQlrkEwAm)IlfEL28Ay2_E4q(@yXM|lkQ7K;(l{}wS{J0^&mB&D%V-F@4 zn7YIi4xf+L!MHLq0RRM zR9oa=?lb?-SW_duCyYi`5YnPj9+NhttY|QaWIDe6B(<}(ZTJtf@&TV!A;HS3V;u+9 zO>5|3^FlIzoJ-i5sG*3kLcy*7j^4P{*p$*B{HlJjoQYbKD%%_cvjP+k=_!S(WmDeYf6uf194R&Z6z|MkH&0c zM4wwkUav;_N@P}_nRl#deJzgGs7A(0WK~~b9cx)<6KIWTBw8YC`s%u4t>_ceXmx0$ zvqZ-AN0W{f(-)S}>et9XiOiSPEo)X^TtVM}Mg~h{L7!i8tT}yZ7OgIgbeG6vS;4Z# z^`%wx4QgblL>9{mmNl<0uc2>9BhbC2vVvtTlzY^zkw}S5>64~{Wlg+upVf(Bjf|AY za#`K77WEnP6e1ewDUoS?$~*H~8A}eKe%UaUs&7SpXq`ySw^jY)7 zENez*lUSd94(5qj){4Gv`l1?%l?YotP0Lz+cg%O3?fM4l*)dHfT7Mv`ml>K0^eb_A G;{FQ^uzimJ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d9b4e34d91f9b3990b2d3915bfb27088e242025e GIT binary patch literal 819 zcma)3!A{&T5Vf6b$YyupMz210C0GQ9ss}(+bc^VLq6M{Tq{<5Mur(xhWIL?Lv0QrY zwb~EpXY@C6?WwOlCaqjc1bitnc%qM3! ziV?@0WH`p{pTvZFu?P6Vk2~CtgKFT;JK0hm#vw)!LI=DX5(jUP?D+_~*F%h&&4W02 zizJ>Q2=oC&d<620?$Rgx##?TV+F+j^=suNf7T9taySgv@PK{Ihvr6E*LsM3_HnS6--x1-WDGA$VER;kQv z?+knO5@TGYjMaXVs>4~Lr30FJ^MqiL#afsI3J~let z_#Z9naC@Q5E_P(I<&2L*w|y_bJ|?7(mvP_4N(oh=mXPK8uEpT^tUH^~ zOo)krodLRoy7UKt)Xpph27)Uj5EENM7u1P!(iYgb;qHC!d7k&{?p;2w1F`h2yZ4_; z0DMoJ{iW;~J8y*X9AIDo3~|bZ2JD_GBZVbHBJjStFZpvCX)GIxmMGG0rmh+)1SvpN zk|xr6s>d@FqOcKhtK$qwzW`WWld#qlAl8W_{Op?4^>T@~mg?I1E^`lAhrLKo;A(0F z5bZc%X$Y`lD2w1(r2!sEi=Y8V4>hYZu0y;4Ssq55u!n>p+qF0+9NCmH!hPyt$6G-G zMLweeT?&`SFknDQ z@|_CziS>Xm)28*h=)?4vPMJxDvcHNj@rj2yqFz($rgiAl`52a)V`$DL7AGk5SC}Ot zuF{b83ae8bx+Lj1iINDKXAorw(}wpq_uq;_o{Xud7!N3Q`Q-W0Y@9JoOl{MQ&2EXZa1bj`yzFwr7v_OsNIHNa|2OvEE*Vpbu-c{opee&DZ=N^ zMdxlrXXhgQW+x+QHP}``eg3qD?F*Ur*zkfNOHZ|>EHCepS zr+tTy{09f7*&5SX#n*$1UDcIcMZ0F#GrNYiVK*|nj&{>-Wwzm)!HV71HE2vb&^Yg& z_gI59S&Nx$<=Q}hYr1-^*=vZ`_WQxQ{Q>&w$~XOxb*^=L13vmgvp1nVeE=JT2~^KNmW1_SgFIxbu*udQXyg;t&#i6Nvo!VWu3tTR0U-z)1KWuNkGaeJ z9VaisJ5%8CIjgTFN(RqS+AUth;v`LL=whc0OSIM^mwRT@-lF^PZ*7< zLnWZA;Uu2VK-?Gzd-JIp>Nup@)wUa4h&>v4dolw08U&FbE<6_o*}AZHeI9Um5YUj3 z$k~_mDt@keYWq!vJwWWOcQbRa8O#1(b+c>D_^laz|2MPs0%9wsaU%O)4(3n;<;*iN z2E*B+1{3%_0U2-Y-kbRxfH~BC-9I*4W1n6gB+TRe&&s;Oxs3hzJvjyH&t}_-d89xx zme0!~<vkLHE&CZx65(@N%x-4)9{F4&?wzLADn%xP=Bq#|MDrb>97@n34 zx8@ZTj@hyxKH)yH^cTJsI&+8)+5$+V&uqy*{a63@U3V-O+rg%XCeNphV?w@;|=p@6_x zsS~bfGLxB;naZTbArGh*rpEZyvmcIzY2y%1Y2wGYCFCT`NsHzeD+>CFIwst0Oi&N9 z_x!kso9l`?uypN!F_Zug>92r2_k2jr!;v+d9F32Enxu_0pF7l-xYW6|{Y+`wOOTSD z!9mDIVGvOhKP6nB~pZyrnG?x`)`Car3tgXl7CeiLw(`0%Zcb#fnvTD`5$@0xDtmEx|w VX`1b7w`x?{dKV*xZsNQ4_CIqgXR-hQ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ff7ceaad73021c71a166cebfe7a8844c00b8bd16 GIT binary patch literal 2276 zcmah~O>g5w7#`=#i4!-iTb0u77yaBWB7w3XD!$Zh)1`uiDDL(USOzCEt(>*vFt#hT zr`-7gTseYz*#m!oGna%dN@QsdD-Ni)qJ;|*5;JyeXVWh$ntA7)XP#$f?Dxs9q9_3R z2){h4{g4FU4>s^0I(DBIKXU+l4-hB=#G#1B>5+1jV{w$mv2xsvV>C{bc{h&JI9X1) zaY7UHbUDL;2*7-8(kBmlnw<(qepH+Bqp(LjN}?1JQ2Hd{WnI=(Z7#(4(Mc~TkI;Q) zG)W&VkGbghb?Dmb{`#;-;t4%Jd&=YV46g*t6LgfRt$B0(Egr`cClN2>YG!J8`{%tF zkE0=qi~2Ymt-X5@Wlo_i9*4mVp#7 z9_bZBgGjNIieh4#;w!eQAxzHL`#F1!HF4TF;CQC0ACkP%Q1fO3D~~aOCMJh!73bW2 z(Y2%gMAguBrH){uvS!BV^$0htm6D7*N4Y02B70<>%G#5-FeH2WvL`QnBc$$QOSTlt zHl^D+pDM?aJtUpOo8G2!CH$?8O>1)XM&vfesG?LKOS?jScCK^t2YFe&F{0b-`-SSJ;I)BO3@&3s9;2F-F^}h8uxTm>c%zOAbz9#GT+?*C`CX&Ym!z=X0@IP;L%9GqVW=Y07p55s z!U!4-i<+wGj4)g5Z3>!}LM)2uOW`{D3A~^O1^+IXq?%`^z06hF8FP&zC;5SHAX~#9 z5%$l<{>RKeX>%UsIPR|u;Fh0(cbAbz8f3s#BQ}?w>q);c5J>I zi+5uQzg_%Wj=0_y z*UtfXzmTQ-AKK0~h39GMyY*(W3s#!R4p?b}l^=yCpPYgZU&^%ZmRh@Coq|VQUh42m tzwt}m*@fnv)5ui!&9&z3R(h%(nLdr=yScag(eIHr&UuhrCad(U{{!EjuZI8t literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8378014a44ddeffc41c56df81abb5fee9099591e GIT binary patch literal 6243 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 F{9n?=q#OVM literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8e0a20efc91446c265f4a33632adfa00cd5fd266 GIT binary patch literal 2865 zcmbtV&2Jk;6yNp7+8#TOv-xuK;iRF3rWB`yTU8+(+f4&W6S7XET0*0>XOeERyR+=B z3yH*u8v^k!BnMP+16AURxbO!caoHQ{DJQO|C*I6{G^8nq)|S`5`Ms}s``*lBLqkY{ z5Blx1gI`ridd3&$KY3Vu1V8V0S(1pSO@X|0GDRmy8CI|pnm(BJ{vyM40?&{Nt&))|5}qY$glc3I(3s%H zBUB^ciA)Nv8lf7w0%%Hb(-EqX89=j%9!E7&18z=m^AQSduO_w})yM)!-%9j2s*!8J zT^HQj5vq}Q0NoJWVuZrDHwE`Da2(ahEkN%Ht{$P#c1duzf#WE&eP3|gmZKUO)+GL0 zTt1##r8aeSpOSgUWj^aObG|p&+oSG$pAlL=<1Mf#2Z&Iw?;4KJTv!diSYd}TlT+@% z_6>{T$JF%9rvU<$KMECWc?ops}y@6p8>Ygd!_UtNuWZ@A=$FqIwvAd)@#*%mB z`P9OFW?8yT>S9}9ia5b+?DUqsTN!B7|F=-{++iIYPK_t7Ll>dTg^v+x(Z@!gt{a}O zj-Ub&D{PReJTKoUsEeo=QT~xbt0^GM%VYJ}Z#}f`fNbGU8I7${UlUtjv-emt-2GGm z<+kbP1M2xxKg%e8k5bap`-kdnq&%WJya^GjxIF*DZDe|1i0g^PC@My8#@4NHHwASV z<)ZgpO^ZE)R$Ln>LNy~ugi_3_W(A%=qv2gGiW9rdHlSAy=Ux;P*Zo`q)`^Wkx^OmnbR=MxK?*wf6s5W znVVn5hm0Q+HHXrT)!mAu7Ex|R_kHRfRb&+*r3o$V>Xj51wCI=j;ow)&e6b35jzir_ zMlB<>Vf0<*JYqJjWYq%7ZnGZqD+<@s9I8WKS8{x@V;|b=3%fGbQ^f7@!SQr*X#ab= z@Z02p@gxmbP!YGzsYdFRbr5J&sq~9qpmkX>Y7O zxVPEd+}LP5Xc+`%rP2Jv*aw%vD()%aWvyQ?qmK{T1luLcQ%H1wpxu&d#kbC*hu13;T0K2Tt0J|Jc$%= zmDx*G;xK1_L)ykPf9Jzk>uhc{*4N{7e&Fon1vYX1lsm6pl*WD+=Ykx@g!M5FhW8oU zH)(j&!#s1rlGif68=31uBq^wzW5gtLq41o(FwI;sr7HNsL<_1H;?xZrn%dS)mk+og zkE3}P)OK~#5SIy`vaqA=a9r+&(=t$6%+d`zP_}kf*4LT=oPs@lVET{}#Kfc*B=gKI za@RAwqaj-#!-y0A{ct|=mJUW5glNktLlj5Rpu8r78AmX4e3(XF zFoL~gQ`qx$*9V<*6t&5Zl6wd86OZQ$p4(qaruWN7x9%JT`MVY)1CxFTJuZ2B@W^CK ha{SH6ikwQmndf;_N|)10sw9^|qR8mAzGXS{>|aoJG?M@T literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0bd9c0bb0d3e43776cbfc6710f5568aa26a61271 GIT binary patch literal 3859 zcmbtW&2QVt6(?y~l1G*;Gq$7nBX;EY+m^D)`Ake`Zf+;t5|!l28< zyoY(~hOYqD9pLqNn9qaR;;R8((!+e-HNg4;ynYY!c>{nA26)t;gZaGcfDHwB!ye}I zXxtkC9*yf@J}(8>NPtKEIhfD83D{_WH|AkJZyd0R0B_R6d>+krD!`leFrPOA*ld88 z_AsAE<7NW9TOQ`~XxxtiJQ~-*Jl;{~QRn2`>!CHG5KS})9X#MDnhk1r_Y4o za(e#=K1v;?!C_yI=h|`MxJ-Qqp&WT7mC1&r8+;dP!?q?!ILQvP6K(t?iZvnQs7jI? zfE{z)Y1ePs8C?Wh>Pr@NuMwkQPkmk4S67|c_qCzO4*6k?=tke~SrmOj2rh}`m;5ah zenCW72|HG7ZrFF*9QxEYr@awZP^`H!idb=G)5dqANOQfDE%uy&O zY_;WH)bWyN3X$E7N``H`8?}pC`GsVVvQg7WGD6YRe^}J(cds7)|8xGZ$xQvkJ!JHl@DbFKU3;Ej z_!x?;h=xJ5qXf$%6kdgr*2Dxy4LS1Tr*QBSEmT>9JEuytM2PP|Xj3X{YW0PxkVGrr zhFW*jl4>Nvlvk({5$ZY-p~|lEQc-`TB)Uss`}Vl~y5Bpr-@IMuZSqX|47#lq6LR_I z>#OMXv@>&-oYBc`|5q{Z0;95kbIeSrDww164hciF4CKKhwZ3 z-AngwR5cBDn6VNU5WACUb~{Q4fo`VJeg*76N3)WFIB7|NqE(515 zvM&?bWno4;PP=^WbW1CoQ!ApRSmEv6m5udP3r@klSd$G%31SMkWO@BeYp1RUXhlP| zh5#+j{+6BoNK4x4%3@p~ngl^#6Z}MyiG_0c!s^cYc5!RRiX6ylNtAV%petQpp`Ce? z==2uODmbsyDmky`>y~3Ls?`f1(3%5uix+kad0`a}ksGA|+bgTk zn~9B_puSU-s-x6fbn`g!?=ZtYX3xV+$Jj}H@H9SJkB`2MkDtcp>+$(>hFK7YVEP@U zp=0#782|J6%VyQYe(%|lzckcVzZF4zGwW&Hc z-QbqY#YeujU_M%*+H{?pX>iNt(qms+G#@Nb?Pi@DZE*KZCxWBhH6O21+f1FCZE%m8 z5gcvF%;u5`^2p#aQNgTXJvg;#QT?{a4HV@+9 z4U%|{Akbro_zHABBOcy(eDx9H5qK`@-gwUMHU158Ucbe(r`Py{>*Ev+)~eA_CO}%6 zNKOstE6_B}5@W#7RA~*SP?8HdqgK&EE33}Si!BZpLc)?DQZU2sZag%pf}P>ho#D=l z0j@VLgf^DBdPXS8gkhPQgFdSUXYYW{bdk~`N#7?kph6m(WSOfc3{5Qk!=bl$vpS~d zrKPr^fCQabhe{Q1NHoo_^mbB+ZL=^Ia+az*cVn>Pe3vyB42uQjP=MryDp{?zw+eNz zU*lo5Nl!9J4A8Qe>BJRWs8TQc$`vm&Xa;VoG=%C2Rhn`&mk+W3HdLBMr6ps_DwUbN z{Xw^0VT`MkvD$A^bvRG7bU;&YjHf3@Y&@Ebrq9ks&)KWd^z``Ur0%kN(OBJUL4Q*l z_Zg`yGi6aI?F6aU+i6*8MJB8OqltTsx_g{A`H|L2*S%>et;nH`z97Bs5Usg=Y;?N) zA5zxg-cpzY?6_vv86Ssk`(A*3Oh^x};hu}ho$nDGVdA`qkYAxkenvj|;otdpzX4_W B`5XWM literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dd69cb7eec2c950099ab52a6d94446970751ac29 GIT binary patch literal 1016 zcma)4&ubGw7@f&(HoKd|P>KN~wPO_|&?Z(v6l$T?Sc=ltSc_edWxF%2tDBu=X4lx9 z#ETd29<+DGi+_MZd-k&G!E}qDc=DFmi+FN2X{+9x!_4>1d++--^Ddv)fmr(1?Yoa9 z0KO&8{!;dg-8aH`3NSDLhB)Oy1NP38k;0N85qMYKm;5=6G?ooTOB880(@+f+f)pSs zNfYT+s?ReNqOcRv8Veg?3*Az`g0K&%r<_}LAq=j9S_Bh|C>J?0+r3icyCg6pXh zfM_QHOGAJaLsY}ewPaAZ@)2=}Rn9d8W< z6#0w>bR}FJ!(`R*NMGRCgjc>N+2&fxc;)G{K@xT=ExI;bD;igNQge|tp zi+2cGW4?`i%f4r=5#)G0uv}Nfb2zdBw5LHa9Bs{_l`ud-A|W1ORv=h5#fb>njr*)> z`A(Iu^MEu=n>HGv4bxvaV&oS@KOW0nZG zPD9qOt3h$-lBD4zN+M{TLzE#*o8I5te+vqEGNry^ETGio6BoHO<6Mul4;2p(j|L_WeV;Nhkhsw6 z)(iv3bpnTQCO#~Pv$Kr^uQ5hhoU4ak;510jdkJ=v_xg2;GrRg9Q;gM#{!Av>pwJ~> zi@z0HM}iQ31y^@Kb_WzX3Mh?4px7=9eku&VFAP7-wT~PV7aduKBcJ7S+e2qxUyCl? z`Z%;4$)&Cen_UTNOYlqXa8w+P4v)RK9_iI?I;EW!;qw=w z3-i(Wg-E~N%}81uc2tm`hMNs?LOZopkh$toS7q?L{&a{TOMfI9dCKseGK fI{rZ}C1sEN;sc_C=Y^>Our-jGIx20IpxA!{%>y*+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a4800f5572cc1c1546094d259b0bc1c3372547e8 GIT binary patch literal 1556 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6a6acdd9a364390e937c5c64fcf795c3b44bd5a5 GIT binary patch literal 1750 zcmaJ>&u6rNoc7B4K=U{c4p4k5*k9kWDfBh_k~C;`%1YGVhmr6N_Mg&A9mmR-8L zSh7zXEA6!s(x4Ih#aW$uIwjru)5^R z0aa**s$mAO0P~IYY4F*Xpc^x7Dm6Bz((H?P6huo%K%pyAD?M)1rAEqCu3c9Xt-3<; z`rpN9WUe{73JPD5H`7y%6aQ5&T7qcd#@^UAf8#h#T(M)stt#A*=Ivt{$B{^Qz9|p; z8u$OpG7Bhz31<$%j%y?Jir>H73Q}T}p2V;jz(d>6(eJ zTOSn0uH|v=*h7KK%c>enNYmibT;%l|i|{yRE#TJt;pI)wYyL&BReSJ!xI-TuhLvKu zQYyYE9+xY_0&T`Kii;Dxe02D{TpZS@0nHrB#nQ<^zBqhIpq&kehY;y?2%YGeuAixs zmXR46bQ7HroXyJMD6%|JWsJ89SRN zvZ|2g<0HCRq@vPzMNY;W;sgqMQ&u!5aCH3i%Y(eb7x1avRxR=*k`tnuv+@KXG7W{q zxy0z1qGCrV6wCS2!BOR~!mBU(;InH;_oKt^ zi?92jYV)xHpZS%~*thOQ4riRvZOix0&P;$LjV8( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b21212d47e8ab47910b107377b5450bfce32034b GIT binary patch literal 3035 zcmaJ@&2Jn<7VoNV_w;;tJh6k5W!cbZ#bKl{-f%!D0+h%*4w2Tgk!?VXv?_JF%1+Pr zbWN(NF&+&U$d$hUMGD9LGxorpb6xku6%H#!u-^BoXU5|Ys8+px_3Goje((2QwOn6s zc+eXE{>SLMbZ5Z*j zVSTy|b79^XH-}B)iKd7|_!S-2KkX;@eXKc!{`d%gwZC9Hux65`otS{ zK>7;b2I)3PuW)+kb+1kPyLl!G6DI|awNRf46{mTkwa{@Yl@fX)3!W7xv61maNh2SY z$8T|QoE4(lkflQRegNlqgD$ZhBeY?75Xjl&;oH;jR`1sS`ky3?7-58k(=H@z7@$VH+>Tux3@0>_=n zQdL%sB`TC9p&?%fGmnLpDbxGA5~8EZ)JWB9 zG~#)?t4{Ro_X^|y1w01;&C|# zRF>q~R4DsOg-I`} zC0r4&wuy!uh{vMT#$ExBR~nAwp-~V|+p6X;L7c zZSd>!H?>OL*ZwX%ZNyksWQMWVF(NG>K}bmm4<)Uir`K29gfQqWmJa|Uu(wFYf-&1* ztimktEyf;~Nj{&cG5GM5F@<<@maUL$D60W^4f;=ri^vN|i#pkFLRaBJP`|ZmTZL4m zuzsdKkVQit`!4hfarBv2;q6;OycvBKaDtp!;{;1vm%2ly*Y;gh<3a+Y$CF$jxCMWs z8`?+|O{0MpGj4j~0fy^E6dj|TVmAN;Mq-yaMnrz}>{rpOE0_Qr-q2Smx~>3?OH{5t(@-YJALEpy8;->DPWnzM#f?7IA+@ zzolRJGxDrH^N*d6fp1iDxQ09^G{L}`1Sr0`$h}KxwzbT@i`<)DeOVHBn<@so?2VNS zWDuNrax$m|JQu3+KpR%74J4aLt{~|Ev9DLDyjZQ7EBRTv5ZLPp=Sv|TKR;L78#wc; zqOhFr1_&E>PbnKB2gPOU*9L04@q*5kcwwmQ8>r|d*k?w%(jwG5uuNlYLmIeT4t@#F zgIYj>^A_z8bql6a=$9IX_zra41G0KEh@ejEK0JY=!0{Si4?U?Fuh- z*mu+=3Tst2s)F7UpHHB2y1OV>@L%Prix8v6D#RGq&n1LZ96F>(8cQ&x)W1-VT<5zYlDaE&s^ivT%zQQ`^fKD7s9SlZjMNav>uiPkO90S)Q3p zXoUscHldWlysM(Nbx=ac#=?!<{o#t3gSxYP#r?||*yz3L+WLUNf%-O(0Lp3vS5QDh of_`X`Hfe!`o(JHO1_tT8LFxY@pZ?Ma=+D7AUP zs#0&{Gxwfzzwht-&dHy;x}pe5^ot*E{jm$7f6zv&gqoE`ltbttGEf#7oDs-zS&n@J z*#La`9G@4mK@J&0HiWsY?$+@AZ&oyd*+_(r4&|cxST@F?0KzdW;?U!O-!B`%5t0IJ zmmy;D7s!Y_;*Dq-j7AKHpe;S(S*we-9tBvvoAqp39>Xqa`Irl!C1k`OAfu-}dpFbd zg08QviyQq9P_~Cz5@1QTExp@;6vEOZBJ^D0Y~&{TWaTcp$KOPEIc1={xR5h(!I8Cs zA={XIh>5J{G~33utXqU&yJ!^*v#=>UmRuy3W8ErkoHFo+S-}1PQ^$7tUv#$_h4XqG zY&$BQ2=r?4)?4M!>Ga&`^y$lB9M~`kSYZv@vLI;#* zWsAs$b+^#j_uzI)HoZ50#3{$Ja`yZMonBO{G+!At20-%=VFYMI6p2~h2*7&^+q@!p z@pZV;HUCD}oYJk=bIYd9riN#-bd#Omu=G+M-cfVlX5lvSv_UT<%^pqOFmpJqHv!7l zr!K+6ukk5cyuxj@w!_T6FgT|@W=rTWG^<-J$LcV3j1W82tr5&g59*D;XZ+*l>=mT8 z=iC;QVEM{|1y~>@-LXhI8kL*nBH6UhwOdrF-_{BRoI599WOtrk@Z0Hjr`IDh6dq}S zN4n!qXBGfx>k9An5qziAgs-XP%rYik()R!xs|E&Cx72+q!JDS-V1f-bkDc3=kx{~4 ztW?m*w#uZJqKm19<*2YA`!YLf%?6m*Y89Y%{&4THuYI&&RMkvbCEz3Ae zXnz;PeY7i~nQ*BgM1zXojrRB-+^oOz0(@^HBtqw+UeYSG&b;Oms60fKz+qm~ z;Q`;!XIg~M(HZHYZxn;u@8QH+#(nL_oDyK0Ee|d;G|B}F+&^E;VM+wpm{3BVXk$mE zMCl2Xl)UH~&_&5!pqj1^GFO&Yt}ZSwX4V(q^3;JLZRCM zoeA2cVj6y(&=G2IbLhKkJ?a$t- z9hR4T7I@r*smXm5=-6dXYt;VN;46Vve((_w#NWSfYt<_JMhE1e553SgO6NQr+D3|y zVfPNClxR`u0oLqqlj%N{mJ^3@vJxYcu$p(YgHTn#Y?EtMpmrmNiT}*>h@_W_1Qql& zqz{BQ?lW_%x2>{#!PHv{Z%8wYwvtZYoY(e_Qp?vkzHNuF4Bo_&9O8v&4Pr(A*EeW; z@i*4ADIGF

Ni>Qh^_sjZh`A=$mlhektHVen-)8XT<;oU09-bERX| z$ukX}8$5ge+H)aV7g9AL^=q{{Ir~&N<5GoE6O`(=zE{2U!C!>y%tLh8Lc25phZ=8z z>R|eUg$O$MX9x!WfP35=oDFUVAQZ6Z2SJY}i;TW9dS`k-AC@lC_&r;|X%0r{FyGq> zw$1HI1yvxw@9?JooBve;6}}Rz2$fJJToEe~meMr;e^_IlL0NlU;RsbKq7QEg-z!4M zXNg;m#KA)L!Av3SgPFowi4p}qUdYkNCU(5ARivpNuyZr=t2*Q&!1a(o*j|qg^m;%e zLPm}C62V>!vcn>HBF(|Q?zN|1j>d?R0@g1byx!A+ zTgK<#hYi{Wi2LZ}0P0E9<8m!7*W=T*__QnayHcMkP3=fCb?JOfI$xKTYtk}n&ex>* zy0lc2mRPrOS4z0jm@5ss(g@Qe-0{@z5!7?4fx3H+@1pL=afU~@NJ{dV%;gLeC3ILU z@Fe(G!ZCH93=WM9b1(;?ViA7!X%H-_cpmKC4WZe6CnarGjE zUG@6n>e`iS884=`|4D+(h;?e22HF;g@Ty%Z7A*qt9e4)VC#{woGiN#`wuwNOk3rl9 zI}6#Kv|0jq;w zMnU`eJ5PG6;({Ap+vB;&I`>>kRtKi5$y2|5zZ#v{3kD+Jh8O5MTRnTNI=51dzP}gd zBdgr5h`JWIC-YCz)fMQtx)%{5Yg~g5#X`F%6b!xU>*AC>1mcw(<}U0Z5HAuj?g!kf yqhs9h7YM|^ck>()`(GfAI|i@&XJLe%>)30XMv~veFV3Ma=E9c({2w?D 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 0000000000000000000000000000000000000000..4ec4a99b7518a466f204ea65b7cfe914c65a4a65 GIT binary patch literal 2670 zcmbuA&u`l{6vri7k{$YIoTgRlEwQc zsGDo$Wr=2v3N&+2JSdS2$&x98NbXnzK6O++mRi$PW%W^oq-JZB3+Rmmc#8fUkJs*-m>-RA5)p{nG4PSXf_z7QD19r-tUc6I zye&g}ML{Wi$jf2vA-AzEvti#ydYX@snizwRG^%|k^P%ql*P$vUl=2UCg^!(P>t$f5 zs~jk&V5DmtsBfKpMMY`sAI8-%u12*lk@9q4hipYJCIWJp2nhQ=OgI-id$u6%;EQC~ zgv(Em+8nsl7cL}19=mWBbKc{P0KRp>FOat31=J#yTPWN20w;t6#aEs)r|XHmuER!K zmD>n?4gOKT$GWlECcAO>7m-TYGP#R6v+~A3+=1F=Jh4eG-MtD@!>?#)C*t)+WU@%Z zyhH6DI1uZ0XjRH6-EB0I8^DQ?+yYL_#Gu^DXe7@C>th(ngTaZJI2qi^Z0H$&Ry1~a zSRd2Svz%nc#MGxa$+GjLAqB${MQw=MjB7)1LvTZIQ*cvo)2#iJ<}Tm+A1*hnb1pZ| zxok|jY?zZS8`e3O8wr79;0^Kp{078!ruc4&4YI@rSz_ZXtF7_-)f(x^|J3Q_i~DW!^OFzR z^{nFE=Bjz}jNR9G46Ku%8Te%k?mHhru67 z25&ssYFcX!Ndek}LLhU^`JJIZ7*g^2^0wvtng;M26fwBxY(r95x(ODTcpZ$R(!S^I zyEIN2kivX0mZBU!OI1EtOHnyNc=Ga<7*8ZHpC_g=o=9SmJ~2<->9MQFc72x9qAV6E zE}Gl5T?cX>-C&6oRY+BkCu zQT%<|>n9*|69z$4!8=<`yo%3e*@{Zo^=v{3_WJA|4x(usj2DH2(Dp;lI3umOdl=H=5@ku^CrewEi*R8+N8UM>RH MWOV6Ul$BTi0%tN^^8f$< literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..780fb0d31e1cb93ccdb35d1fbda0a354f1deb0a9 GIT binary patch literal 3424 zcmbuBOKclO7{_PV&#Yr7aT3@jX_7jzowUfYcSC5PfSSb6N`0hx2`tLW_DsTBw%6=B zZJbk%mAG(2LL3pVNWc*ZE}XfVb2q~zXI59K3-ra0U3c`wJ=KIZm9^W^U z`Syo!m;?C7eSf#~@c;n75To-8nvKW5vH*MlAjkp8K(@?O*qnzUZ4YjHb3WSk;-Hz=L8t2AzF zI<0-Pk?X_j`p#06;+-*e4ZHUl!^-vJ@dMUugV^dWJ!dOBE!2+&_i>!akM63wlrk)5 ziTYs}>pTWy0P>X5&VHk2+42}t$cLNoLXoO>ScxIfL zVy=M)3`6V&&r+Mg_JWsVr5#!}V%O*x@o8g7|1R&LbFi*C=uEq@_Zd3X+<%=a+QYHx z4)3LNd&qtXXz)G?ydAL1`zi1}arFfs=aJMMqiPt{ZtQK&duKaeCp)_D3doKlAf&(H zaL#bn-Vk;0zB6p5%eOgScDsyJ(*;M!4HxW~Q^kn@K5@hMIsdYvArVF=xL{4u*Kksp9>F6ds1zkh@7Yq6tuiuF22Nor`S8#~lAWqlFCo1SLX($DGf=PON?m-N=_^ocqu+NvwEBD@KOgFcPHhUs5W0xvKk@ z*B5SFU(^FjXfwZEu49+*ni4GNeODn|MXHSF*+uX*xr}sfDZ9G3a(#L2_KF_ZDl3J2 zS;K2~TB8djzurWemfu2Gt1w&Lt}I!ZThopHHKpc&SY{5;^r*DHnw1tMdzDqaM=C3M zh@hm@$UCg*eUfHvO490iwT?9ogsSj!@E8XJ{57Vj#hE8%c5=B=wT2$NQc)m&m0wY3 zu^!(FpnZkQV-LeHe{=&T@d!Ns2)x?)yW9pd2cWkFW}0B8?Hy=&hnwEvmN(Y)#@b$A z+w1R?<9~(&40}8PLK6qvP>YK-x!6N)axZYqfk@&Ye7+TqH^cFV;bbd3-we+m1MpID z4(~bw=k~atL(%u<_kwMZ+zYlqvI&x(abI6M1TUW;2eY>hmftu8Z?*%`Rv`XkAl@FE z*t>YhjY`i_0u#5>;Ptw^lZ5epXzw1d}#A7Zu*?~hg(TxCytXKm{0|LaDM}X?CV4T literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..062f5c0eb2db35225e47ac77b1ed1fe9dce84ae6 GIT binary patch literal 811 zcmah`!EV$r5Vf6bn$7OEw{rD?%S!E5?E!IUD^S^1y)4zX0uc#WX*^wA5<9Y;t~N)? z7jQ-W06v4?$dwbnPyxlsLRC)K@?@Upz46RM4F(Z%EBgBO@}q~)w+1|1p##s{F`t~_ zC`KG}lHnM)UlJ4U#U9{GKkjfp4yu8>?_?`^7>5`^2p#aQNgTXJvgae{E`}I2n+I|5 z21z_e5a=U__zHABBOcy(y!#082t0>+H=gtRjekR&H)uKS?KS@3>NrKi)oOf@36Pd1 zl2Zfv3N%f##27F%Ra%27l;lFrs8zJk%Bu77Vw1y#kgybp6wK(mYY$DTV0-j*d$fHx z#P#}x(8e;C&j=-%Ff3DZ&}Y@~>>bdVE>c<~>HA~`R7hi!EOYsUp^2q`H1rm4REPAu zwA3~dkf0OmSgGQTh^G0K-bz)TE4eiTm4np_R-Es!Ci|#ZP!0u1Zm5z~w|!Em{kb+1(rV z>J`SgN*SyDCRB&>L`z3B_4?8D0Q;DbK3>IrhsmAq5gcLSyoiurp+|m3KKbF_`FFno8!z_5 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..861a77d32e974e20b1e139fc230c39a7e2c2fae2 GIT binary patch literal 1010 zcmah{&rcIU6rSmBx7%%NLkLZ*K}X<10`q! zB#HDo(ZlHqQP{A!)o_X=p98FJOE}#WL97u;_?c~~>t$naJJGdsUF1>ZHS8%phFghY zK++?CB_Y6yuB?Km9WV#q}!Zry*mSb{GII<{XgnQJ*wp&9! zMINI*T?^L7Fuo>f^3)jrsD`bnrVaOR?7!6nJRVVxG4509@Tuw1Oq4PVOfAER*(;Q5I#Zf$Q6K()C_}!}zg&jc{g_|2M)&o$3u_hV=_l{Hgd` zv9%=#;a70;0Avn8zO8`bPzVaGeE+BXx%c^VkFu@PXT?QZmf_H6d2)a7($1~$>fMin ztD#)%s6a06YlW63Zg*yO`$BcDrByo;OfSPP*@3Vy77mQRTne>vCz(iJ5c0Vz;g#Fr z<;752>ZGJ}6}DB7n}fSkyQOeZ0IQv}EXaqFl2O_~Nh)ngaT2 bjLRPS#rs4DFY>c}V6QJVdq&zTLb3k_>|`>G literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b6ef8aeffeb8470afc8b7bf5fb7efd406103f443 GIT binary patch literal 1492 zcma)5&2HO95a#NKl&L?8ieou$TsK95I6!3}1&SVuqKRY&N^Qx|j|&he78G|aP$0SN zF6F?!_FD8AbC`E9U0hyLe&8@d+bcK(p~B&A7%>i3ePiK}F;7x_5k#&}@fGF5 zWr3u`wLfJZCU#^`Z9#$3rVH02l&@4syZS^fMOb3x<}=5?;zte(F5TJDoVh`SJ=b?5 zfm5!Xv?tymxVjIEHOn$(JKg@|LN{K^tfw+*Cn;l^x}H`FY@fE!0eZS+R`sIC-ca7k zfr+HK2tzRQiD~GiE9x=Fjjme-$vpW{Z*204b>h!;Rj>tj(xkQLkK376+MD=M5@bSn z(uaVe`KRUo3{SH^3$mZGPO+~)dOhg%$N02&)*Iu|U^46+<4OO!{^0w*p_qI6&inks z?umiS$NKJjoSn&NJUBCQQ#CpIFK#}~enfh3|GFX^PM`N(1UD2naFQ@-Chf&#z^}fP zmz{NX2K|%XtJTj^3BO3Enccbe{tb@9k75mD!W@j_`i!xeM^j0FT6s?HEaQzTd0sL` zhDmhF3n_h1t31)5f$!-xZr!WX2K`VT*dCYijZ1i~=6L_Y_FRxTJ(n{C9SzfAxJYLq zR=do%-5^%aCofNXomlJAOMBr(@JUb;lWE*|LCA=57xLUta^iY4*1N}}&aihr9t>k` z=CP^m38?uF?Z1?xbVh}+XY@rtj)R5Yy&F;yr~WYuGbDtqHwZZB$>`;06+`V9y} l5nsVlC|1zBw}4cXyL$_7OM0!S<|?QLyo&U@w}uLT{sYD~#Kiyr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a2b79a5190cc06d86fc995c1faf96c124f0b08a8 GIT binary patch literal 2000 zcma)6OK&1a5bhZsgByb}wzGbl;NaJ0!E5iXV&$b>us3XMu#D{}L{V#?+t#3An#_P# z_KACg%fNt}4C{1R!7^ z{O;4z9})mRbEW(FI>L+J1OUDQ7~}yKu&4@UG4B$%>|(Mz@3CYzlf8M#l08iJ<%cZU ztHQEBKP&(d5UDiXb?)p8oprRXRGRNf&W}^Z?|PKh69Y}>hg%I zwElONk-|YThNij}JVGXLh^wgget5Kp&GdL-hK&vI#(S8$1H4;3tj9YKo(ESa zFBTpXjp%Zf;7CQMRa&HKq)8?B_1y_VMU33_amqce&G~6($^p(2k_ZWily5gq5Li z0D4sVl<1QpQw6TpDix->EHwmuXGEn%rFuT;=1BB2IO_N{I*#u^uR&{i(RLc#n2YzN z5_kAHixg&bD-VMOb*=dSk#2im)B3N{aWN04pJdaSL$sUTOCO?K_UItFjgB%OX0jh; zLLfW?Z@tQXklG0e;W;?<3S;spcbMG^i3P9q^qUup{g<#G?xhl;N?{|T2&_WpD6$+{ z$=JCll;PJ8c(MD9$!umP{rLI|-U(lFW4Qlh$NDETtj?MeLYNj2GDl7+JynU_60BQw zK69oYIbEr{ITGqatUlGPW(%Q#Ue$RwGWI~fnv(-@;;es&uF0y-{!W4EL!E9m=Z|Go zVQo(K`3$j)4(wq#wr9cgrf6AKG}F6(^k_GoG^G?dk!xy|U5Uj+xL}Sx#5hNEh55N4 zct=r*2~*p-FneH1rz$PTs=;D*rTIsEl=g^W$fx9?hPSm^In^6d!?f*(w9*GM zTIYbl9{MP^jgrWTk~2q;N@YwiqLnI}VS|;*=p}`Ws;pNTrw<|;{sz2Yj{$yPGxTEO zKZclhNW5}x`rav1TvN$=`W<$ZH3l%I*m~m<1mTxqAS^ux_n(9JyT7;EAkhG!7DzNf zqU|1Uxo4X0nU*`+bVu86PuuP7uD5>;NP>7X4*ZJ^INgHLCX7CVZ`7q52!czEz+5X3 zYX)M^0?VzyRx_}51HjwckX<0S1MBc7fB5sQx~~nE>%JCPZi3~n;kO@Lfp=d*8uvbK zd{VpuxGjZSQtSsQ)?QewuU(0=?T!2Om4<)5DK1=zciZcmj+SU|+;g-++vWpqTWE@r YD{-TJ*J5(*M{)gT1o)Qp+w6({0v2Z;I{*Lx literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8208bb9ff5e2a310e48be5b5966cdbef67b0da64 GIT binary patch literal 833 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 Ab^rhX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..07d91168992ef12c5540e757fc357a3932614cda GIT binary patch literal 2180 zcma)7&2Jk;6rbJLUVHqNxM{jg+a$OoX$rDYo0f!@qSQ`Xmo$l*vje2r~$+4>B$x$Z9 zs|inzX;4pAQvwhHky?{M@Zp=G7l*q*YE1>w@Qo!L#|aE^@*w7;UerWuW@r;g2YsPB z#_~*QIsHs^+_R3p&ARno;6Hrh^Z~1%Kh+6V!?{6Zne9@oYrZbLk_jRoh(6+-nQASD z^}b9daE9591a@Skb@Sg=_6ImiCeVN(JW6CdcAgocOMx}c4X^=g_zc6wPuZOvU<13| z>~3~pVsH9BF^Hu&1kacjb!eSxGqrZ3L9CfN#iVe|&sq>%h>2adRMVjr+hvD4WKybe z$Z8vosuT2_Shh+H4%xeUjcN!hj#5)>!XT;ERy9nlV}6gv@9-tc78mR-)b;09u}+PK z+ANx?LF@G$l_IR!Nc{}?c)?4Ng*n%vs^K6_)m6vtE0I~dW=9$O@c)O(CHbiZKl!9PC#y$HuQg9OW(UHP=+Wt|&?iME9s$W)viS(p*7By#6qmRF7%mcpnp*dmLb z;yfH{Xo}H%O*P`FCSb;?n_-uThqLgy@;Oo+Mz9=ZP&Ai?l1XhuOk0jIK;QtoMGuey z04qACvt+Rlh@`7Aw~)hIn8UF<(0s$U+W@Fg~He}^t`9u z;BEU}c?F(-bI1&g5kskI#N#fWOTkD}Q4NnUAvX?Fx?(j|1F>#Zsm!?&a-%TLIWgPz zj?7sSYB8@zuJ6r<>v%&y@s?RXuJHyui#99k8>P~N%0_u@i(To)`n}41^(*!TP`_r2 zJBc1{RMxlGDvv7LD^E8n<(2i)YTxQ_X0R~rN(fr7N2!DiH1{S zML04VLcBS$ykQtcr)J6G)55Gz&66viI;X&Hi&%!DSrT`MqyDtHQ%@C*U9&?r`HZuS z9{a=k)Sp#1R;D_m+Kp{Kxw}>=xl)-ll(y!ulGvFD*WB=+SScReyJ}Xj6U-kM{hkO0 zGth9J-LVynw}_>(O!f%Ar)tE7<<+gypR$P4n5ggMI|&mWy{SXjVhR6L`~L3hV70adyo{e9mR5( zO$Z_e{uaDq1%tl|wpA~NzY%X~tAe@f#&7Esw>5IddY`$o9sxG{`vfN=f*|~!0>a!& zaN{NTB={}$K=BZac0sWNioM8qH*%p9xzLT|JCS@Z675A|!CL$yEeYbuI7nVTgp*yE z@4);E`0l>+XAB56;Uoq!bBF0m-SliHJ^La(-%T%f(#t0R+#(rPN~V$7hrcFsUo7v( zdtiP)-Uag=F#iqw?!giG_%-Bk@!2729)X>nlC5|9kHm|;n@jsw4wLzg qc=<@Y(Odj*xVzrFb%*cfJK~ii@uS}I?O^w_xO9>P@j2@%BjX=CT4Fc= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1830a58ce67819f8259a35a787215806cd708c04 GIT binary patch literal 841 zcmb7B!EVz)5ZzrnuInW2tz7pSsZG=(Ar5T?DxvBnRA~WW39Vf3B;D58wRYEy;v@Y7 zIHG<4pTTeV%86g7fU6lbCQX_5fe_af|zLPHYfj)$YuR!ZF;^B?QS05oBf#*>B#&dqB_HT&ux(%m^UgHn0j}z2eD~AV}0BLC= zIW?fKK+`l!i~&Par8SsbNiO7+T19iMtU4(cv z+H@e){$7Rq~W@ekwHCtU8NS3PpHzAtGRs0{kOTo)EiqW zwyaW_+1=~4s};t$N*SyCI#q@9L`z3B)yCoY_<$V_N5k>+)8PyDW;i}MdNr!r?0$i) z>NKRksg3)LRF;_{&y{w|sn(lmSz$#ctN^2l`!iMhXjbQkS}R?3#)Y(E1`Yax^txj- z=Jv7C>EJ)tT7|pICGBHZ$Lu)cEJcoahTls9>EbN&Wi~76?)`n*Nw*!x1NnY@-}fFL`Lza579Qkg0ku8@A`byE9Ag)J*X)6>wO!DIb076v>pr7Fgxr82NyTF-kmtKnQi(h+ z1eGBV2n#wW;wTh6;(PJ_0-^gXpc4Y~sBHX^Romzri;d;Q#^TL6nAY~0kP%jTlJNjD ziGALqVy8d1^MIm`2t5?K-Xpg|5esDG`o7X*61fpNz{-dxAGS{tqQ+D_{R{;Jj9zI}HiE#eOY#A#^^I#1O; zWC9*x8HXVkYB*_Sd|Mnx%x4j!QmD@h(&FuImTw8cMOtddLBzUr?7al1+1tIpDuvhl zpA${ZmC1Zo#gx*>zMTACZ9@%0_#61*1Qbs|WoUrITm%YgKSuEv$W_9oM1S{{};7KBWKv literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..62f39550066c4a3854cd8e6553d623bb2ee68e4e GIT binary patch literal 1293 zcma)5-EP}96qfuW+4R5Nw8}PgX;Bo-MV(^18-`)6lXk|o#Eshm6;KeANV`laQ6uGU z&Fx;qx!eovz7Npr0leK+UZKE%9m@Iff&rTZ9i8(Xl85}xN7-s22OjkP)zKfGng_;Pqwq#0h(>YX8eVZJxN{fL;652{%}Cd7TZ|#z_grHJ3NofVLol z>a2Ve=$Ew326&Tp*aq!xI`|XXJM%uJ?WtqG?f&uSPgu$n$r;_sl+0x;#a6VK&6wJX zC1t}?IGnTX8bq0n70+_1AnII3IFnLXNG;NwCk(%3N^_Z7NWV)WDKI5DiHK$Z`O$(4 z%G4<^>YU!PrV7S5$M@pot=frYI^*-5jHfb=Uvr6n$}oR}SMaddq3*cQmH$1w->?f0 zS1a89;G)6geDSB*`1?^&u-`-Vg`zW@k^FVwZn^jB{TrxbH;reCz^PI&L8(Y=qTWo9 zbpDT2xkz0{?L5xbr&J2OjXXh;h?1ZRvD@#W+Rs3o2F3%gKwtw2`33VF*b97ss{IBk z=TZcyR`3-?sG`$d zd_@%|xKd}xQ(z$_LRpcR8a~sEx%q+!4q{>xt5n$0;YxBZR*Ey;u1p9|jW>Dz=!fyh z_`7UI79xiuK}}2}(|btilqnAD95ecu3ue&n-gI<0o;*7^H2z%3hzJce&%K+EtYCj+ zT9Y|@n9{v;k?dYv1#MROXEIv@Vc6LOZTk56bPtd4WtFMvU?B-*6w54!o1#q*>x)fc zofDPAm^Fx{^fyP{0X7Qr@xE5^&VSz*+wo!c&eXn6BwYyh4K&!%55QEaP51n*xSm_O Wcq<=ks9p6+O}7oW=OXx5{`wo|s){WD literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9d00d9adbafbfdf0a3c26b40330ca1b08bc10c51 GIT binary patch literal 1713 zcma)6&2Jk;6rZ)%-t{Q<(&Irk-LAF-vq2iQVP&xI~nRPaH0s=60O zf7$BC0r*20;ZjeO=Z_@-egGI$0hVyYl&nZKDhWNxb-5Y~beZe%suJiiuB+8VpvO&U zC94w>hybFrE{4TNUyja=tWayshuY|i6|CX}hB$e^dlf6FnrJNq#u;&NVpTJI-`KxJ zv=+}K1_quu;Aa-AI>qy+Ppi#vYohh$&^Ip06qz}Q43VI#iB@6UKh((-)_7brj3d$3 z+kcO8=WvQlp$nl0r^yVS6ec?54`)uX*)VP|wU=7W?q9k?Y{GPp;01?KkJhQVP-{0D zge}x5CWRvrzbg3<6SvL`$D@o}>5+vTN=*T=w(S`fL5~P?4QdO>-Lq=cL|FIqn(h(~ zDYdp?V#1EZog7^jBF2Re_E+lGBUY?ayJ0koj$zY!eb=Dq8wVL*qaj=fI&{;VZTP># zvq@37FszXORf1EMu=qWv{AnrJFq?siHp30%=-zHlT98%}*;ip&G>sZe=clr{$VCFj z1(<1=y4^hEiUzJ%V9KjI<0*20r(oRFts2&IF>XuQNvM9yiE}yQxp;u^1}yzMWO$st zoWh}4HVLC1HFIjoB?ds;y`-1IhR4H$6 zl}dNZTlXrQgRRZmjQra}hkaa#2OiZGmM5DWBvOp%skYE~E zaHW&z+(a;O=REq{RQS1b`xFeDvta8cQ$&J_PG-iRf2f-VA0|=}lgc~V9**3@;l%mz zb!zE`?Z+z*ZhclR`N}$J=xx*EN8)=TT=O$GG2S7};C1#1zHOMqhwB?VrR{R%{?@jy zG)-F5O_%Q(YV)_mz*ZSIGPg{JVE%&vxp0;)jzz0p0 zpCPrYcHpyQ_WQMtIsnTZwGWniVEHlp>Fy!;=nT@m{zdnz#vy19lx$yF{7qRLT)NVE l<1jKexbbdhrJKCmi{uX@*9X_$39a8FSC18-F0mzk%9k!q(NX{a literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1fc36a549c11ccddd0d34952ba20d89ab2e62961 GIT binary patch literal 839 zcmb7B!A{#i5Zzrn#yAPw=yk7=AP5ds4}hp>8gWTch+5TBwKCpGSa5dT-8B(DmOtQH z@d5pe{)VqT^%qcEx^`MbkKL8W^Jd-~&x}>K8zFb1uOBXMJ%qm1;OPk+ICPJ>b%vuD zam-1EW8Az+Ot=?&fG_;G#r-%ayDr|!R_0+GVgwhf#fb5C`v( z#0vz09zw)dp!FH?@Xq6_&k&EmbEtjiIloi;cf@(!hSNlE@F&;DDeA42gQHA)%zz4MY?5WJo-j1A^pA$#Ol2b9;&V^KIdM*_Em@{$7mSL8k?Xvn?{rsA8$U$;Ll`L1A z2824;uW-NIq$e39256DbbmD4Xt3oeZoGV^rP|sdhsRiY8sx;+lA)j*p?X58N!j_6H zt5jz8_IvGWg)y#D#wx!~RpBDh(h*IyF&v*9vEg7e7{8nhUa_}>@#*oKQPpOT`(st7 zA^lBlJZ7Y_%oKU9wA)R!-b~91D>7jP7){)dsoKZ$IzP}_>8dj>q!n{$&=;iF9iuV# zyGEzm|F_jD+*|JG0J}0~*BKv&?)qMUeN0FPui=iv1nswyH8)Wuu4RBDqgDo=KPRDLAOvZyf zLugKjGpD`mVYhb>7ybZ{+Vfs!;XoLKv@1^B5_&>GPemGLJazySo3oDUs1I&)5rXpTnVP4j^A<#h5ycML0I- zSm{38${h#PIsrr;0%ADEI(X3Ng1g!}=)##pz2>sdXb>ScAV^YihYIBRu9Q?Fj|)L% z$OFQHHi|e31&{bfyg5hcCJX48z&tAJ@3LwuJ!8IpdcHn?eg>x1O(tZ7m7Zigz)WJF zcd1zG&8*#^s4YSdg|2tYZBxVo8M(f%^q53$g!VZog|odiv=K)r$|y9T!i^Nl<0K;? zuX9Hxi1&a&J@!E z#)Nwqr+Su3&8{l~1(a#^LThyyUudp0TT5%rU+}eN>*~eJD`^pbo*zz2BhW#r`Z*Ku z2+KGOxlpr7E2G=uIAT7F7?nc(U62+pcCvg^2rkl6D-I&op(F1R*vp>o<4GyJ#`g_0 zH5bO?Srn5>BKvUinc4;#gz$fG=`|?629<#UYSRg*_A8SwE64t=9J^QQANfHA2D%QX z-{_}aP5u1j*W}#w7gOtrUK^S~uf4LXeOpDJEbdGs=5pWueW*dph5wZfC)L^H@Z7^I ziQO3HbJkBvzI-M*b2&M^n%Gx{1KBnNTIA|M|@DkfqBsZUWTIH5rrgnH^|%+l2IxP zWB3BCGNWsbT%cArFXU-s*&yS9OT)A-lM~|gjW+>jI1#XG#tmPzH=dg}33Cz}>iMUXc0eMF>OY6x5Zq2a>GGpU&;f_XHXc;>P!x)u dJ^2c@W9Lr4!tb(MEjl^Rt|IQ(F8&LD{sP`~c=Z4P literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..04db0262d7836da38fd58e80561ace7b20833125 GIT binary patch literal 1643 zcmah}O>Y}T7@l3Pz0TTeCw3xCk|xHj6! zwC^9+>LCDr6^ehtBjM#U34pHw0wsVXBwJElE(IhZ2e_=1f}X5!IaE?TImqR3Y0i^F zmR66Hq7sk+R%^?C^2wK@s}s$aYHPkU`63mC(HzoHwKh@7I@v@ ze|glhGl8ChqUU^OaY{)Zzc|je#ImxqYgRkW6$whv{TL^}5i_>4*v8awFiJNlaY%(&>2mYv z2-9?hAe`?D|4qr&5M~u>HXK5^7N2OaL5L+F+O!?Bj^R^GnMrH`vD12)SP&VGQ8pOn zkXmk<7Q(bIIO)G8t{}f$aJF8lKc$5Vv5(AZfl&s{Cvd3cy~fz@+)w*IuS7(o$qxfF z-$`1eNZH<}+g{p4Qj3_j>6nHE8_Y~eY3aI@ctwk|W6~5|jS8k@ZU`n8wJ5gASi?tR-s1v>j-X6Q12N>!!m}ky%@_(U{nx!d4D> z6TfqIWonrG%$vYQ`R)7tx5U#$Vez2IM|^Pq?%uurZyefObyWxvsX*w)tAtc7JbXh= zEEVhIM&N`%st9C$3h^kV9xwCo60KAa$GtC6>!vQ%$@8%>wGE4^A|MP$#Jg*c49nyj zf%nC_@s19Ld-G_xHg0H#)D6>iL;Ht!KHJ-N)g63fG%bgZ#A`xSb{B3VRK%3CXr5to z*R-&!?d%q}5BBySJ~(jIszu6%#du9$TDv2*?sLqTQN_1yv}-r(JL7$3?$G{_G^RlA zHVR;F0v;B3;WnI9DY|h^s(~8}O1p2Keh5qm{zH z?z`YN-#B%{AJz$KTKE>- zve;EtI?75{$#j%VPYL#vkbh_Y)Ky6yEP}|@wzk~WG94}RT)Wm%2O5ZNwDl`pJ=@W< z&-Gka-|FaF0{}iaR(b0&Q*CL#L=sl)_?Y&WGUH|e{M_#^=Z}wi#kCdO}8v_-DH)xK}_&3hczdrx~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b420eae5fb556f268af0153e48fed1018532ea67 GIT binary patch literal 821 zcma)3L2uJA6t`-EM^3ioU+P_~;?@tp*QQ=)hC=nNQAe z6eEr~$#9IDKZyzVVh`|zAGf$42W8itx3aZ)7>5`^2rck#NF2ODvh5>SzwTjFZyvy_$ z;c9Ctw6VT7nad{(O)UMRp?7$@d`TyT zrM8xU1f5vNN)@j~G@W1R-Bih`nC%*En7Cvs#PZ1=tFP$i*OWsJk{haIx!&9>)Zsye z`{g!0%OEj8i+rXNm-1Q_dR5t6@gjq|&?DcSTe)8(HYP0*Xv8vOM{)V>h zBT`vriab}^jilOax@CnGnXm$kChk3|_Q|}C54Bdh>Wm6$#T**+1?f%4Xv|&D=ydnL zhFOLCtI-@{r#5@e_&9Xi_X6x=LOOT@cN`{5-y=A}#CZ`Rze11vjC}ILU;4}6hW`4L literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e818646de7e7eea32d2718fa0eecefeda5e58f7a GIT binary patch literal 1020 zcma)4&u`O67@dh7$99s9s)|Sibvu;3L~0WXQUy|})Gb+rK&6CgX)Ucru4fud9gj6* z6Pgp+8xnU8(EF}>;ST_*J##T|AQ&Nm_O!QzURFIZPTKD7jXBJG-@Ny}Uo)?ZMH9$n z?*DfGu>!zfsdK!vBjezuG@b$iEI=U1c+i5wGh=0lVks2f*N-Lto>rEqmS&`ite0!+ zmJUG%P@QI}vYQ$4ERAaXoV+zjj%J?$qVFig=t_{7RFQsuM;UsB)Z57n?c$JmgzUmm zq~GCo<{Y5LPk?73AeyDEgD3S4c&My{4y^vrZ7g{%^+RO)1PRI?P>vkW7J>@oFwUt6 zm`_~4g+hh`&O){kZO#z7>H2gea2+aYf2Y}&yV`8+(rj(^mnuxkn=Th2mhptkeC!JB zu@2>{-RkN+idsByP+&V-c8em{7oqKWGM+1t9ik%*O5k+&2HJ>16s8jDQ*MWnkjib-Bw+2f!!@U@^G+m0jDWy!8Rf^pgs|Z`CVg}*JTS=5?Jd_qZNrGf4!aJ4bB-)3eCt5-8LP1e}aSb#SW{2JN56HW1`QGrn11t zr850q;+5R`3WV?rxbYF>KY~(U1C^;5lzXM|_oc}YGt4UwerC%_e^>FVqtGA)|Yzb-GKs)HTb!3DlSjQr)K`R9h>z*He;NZ z;ft5!%eUf7E3tWdkW-8%?CYSo1o!6mYVnFBHV1}k+<^y*me=|~%W8c^=J=uzVgV@I$|SbH0>Ult0B} z^p}Rom+<%hfPpA&g{UOvsBWwsSA`r0Inho^IRUcPPDwcla=M+7vgRN++s>&9Ob!7XBq@?68G=amMgx83x^$zoC%|9MDZ7*HDQTB?rrnwLEUYKR`n669H)?y1&i|ma z7ijMK8J(jGH!)bxT`$r(QiK^~MdRm7J|iVMhnL9&T_EK-1y{)AKw2SFAWci%%s^Tp zrKTeOYqM9&yVRrH3@EAeI18A?oJyyEdP;f4VuV(2p%KcO8X`h{i`%_`ahMhFXSm0h zBUHTa1-47^1?9fYJfZS0-41gwF$1$>`V>^DPTzJ2<+mERyxqtNs7gDi#X^r~Is{<<}@jXfHT}wmY_UsmEZcu<^V)l4Nzn?Y6)+By>*|_FtjN zz~o&TfCJkLF!b+?5j`&5tc`Qik|qsdu`dnwV&Kfkqb$Bp(QQ?zFb4<5GOj)uTRZ~PUw=a`dWk%&`l>Uw| zD9e1OCuG;+qUs7tUVv&-Pl7JHF)^%DY-9C=TAKo4N`y8Jv5d;W_qHG4Q`jiV;aK+0 zOg$xoBt(!k^o)9D$D#OKw$`y-JHX}zvx(_hR8Ipmy_XcwkJ0>-7~C~&PtQo7ngFh& z)u(|jwAJ=7++3UKNE)Ps!ArC>&fp#H_5CwFE5YdqT;h1p?||V8+wbW)Sbp50eKU9k zo*#hwoSf?Uao`0s2{$I-aQmZlJlIuVFI3_$GDZRAhEc^X8yS?s(cT(K6vuAg$f67$ z?xQh{Ch>=zH@wDMXv8tV<2)u}WZ_y4*_+3OrZl3F49>xkP(NaEF4?(+A2E5T zKZ;WYW}w_N9WIDHOydF1hf61>1Lr@W4e`1_Mn};zaT2`}p|;IjIP;;_I5ytaYhh}e zo|=6pfRZ34CY^A|zv8a)tnj`f`V0jJK<<*`g*YT*EP8uqBwiDq6GodOMnWs?qfHt< zG(&g8H$!@w*dnA!xeaApQS!!iXo$9VV7uy#*5Of@>N>1rIzE&{HpMmP`Mo5f97-RzdwDV@@nrgOn>1r(s$@EQD5Z7Wb!kVTc`4!Z}-~Rv>Xazd} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..73cd09b5b64f12161172b5cc01920122f6ab0a91 GIT binary patch literal 2896 zcmb7GOHUhD6u#rPuWcUY5n~8ACIo03yUl}!Bu#~Z1ll|h5EgEutFiCJj5`l^W=sPs z7L{5lKcIi16`THruG(Dqp1fD!v=?Y!-fz=Bpac1!P5Xfk<-;}| zkg*cUM+M|TELcvu#@k3vou} zxYC#Jx8sS)1Il3joUQ49lVtyeUBrt zeMZYSJJ%hW4bg7Ed0iaymT$j`GTx;14xE3}sl)A$guB-N7jwPY7svVM>aoWc#Z zWG&oI&rZ+ux!tjQOl7)gGMcLBnyD2vIaR1`ZZbVp)F{gwal6xkg()+Nx>PYW9k}i` zM=F{obCF(EO-W&7o9Tw6sa$02CR5ZncXfO~33sp&`Oh4j1J>tO}`Qc@u-E2|W z*0V)T-IPjMQ>zxY2smU+C8Au?bjjROh_jlp0|jHz64MlB>N})TxB~p>3ALi8D4N>T z;RxdsA8h{r>n9%Jfn^-m$z(H1ZaV9_cLGYjb7lr7LUGXPzXd$B&eL48vz)8wl5C2l zdAk7da~K0d3~84UB_X7QDPakHEeP>DI7(T?+=8;pQgJ8Yfu*i1q2XN?fszF-f(}d5inwMvt!v1X#%_RIFi(Kh-^W<&JY_Q@CM~F5ea28?=$IUM! z!z7!~D5k@Jy3zNN-BYqD`^RAD+Ia1+j#UWg{P!aHMSBF8ys@LJvwRD+tkRm zWKSbLa8!>pNjrIX8jF&sgrPZSDddI}_$q zv=Fcd($(F|ormzmEIK{H_&9oa^o&owBw2s-cz$VZesyv2;oQpV^8NL>d-IPT-d(;Y zeF^uE^cB3U!A{h2Zey*9Sc8!^owkC6P_0Oa)m_rGlFXbL19M#Gw~Q5mW9d$B9W;>D z(;=}ZxNeW(9&&1m>wHcF`qU)15~uuRU@|aW6=glh6+)yr=dIBVQHK3*vK4;2Kt!v~ zHL>B`5>{YAQ((_qftAO(`^)oIaDi=#RoR4;z$c*v%bmactm;v2{Q|EuBy7TPw`V*j z-C<04ZCgtGHqqg~4zgxCl(^K^cbep0G_E=K&af@@&QZF?bP3YfW%RBjGYc;)Lc5k% z)*r1}!IG>MMA?9pU1~JP@3;rdFvJp@Q|Y2wRTf(HH!Np;U8{5m0oGJ7tT=hRwn*kl z+sm5OO=L}^j1sK^2VOO-UShO@A_fdu6EGbjrqb`wOGq63bIs6;|NYkENoOiMR_L~( z(W=bu=vgoVdk`4`cprNOLHHwzgi9~b^%v+v_jjX#vInTAj@h;OJ{^Iax}J>e;a?;1XR~{u2AbRp)zM@PO@52NUphn|oRAz`D;_ZA z5UGt|ydIqRIXKZ68{d2D&@;XRa;}-YhpzOKxR@R<}aflIw&;sw8#K9XRJ3fMys{uy!ou6>^csI~eH^2~YB}1^1V~F0 z$*BQ-37V!^Vhk9XDy_lfN^&7*)GC^5Wz}gh-QX}45|#v!f*F2y&qL!f*cv|B8g9KD z;Hp0r+F0i58KEQ-hGl92`lKA3oB^HbJf(S(UL-T1LK>T7nX4xZO)UMxp|^Owd`C|U zOKnX72|BS3l`3A7Xga^to2imhG267NNY9u_HGpJEHd9*IvpHL4S$?|B>MMu&73GkF zl&gh&i2b+4 z!qmDg85>rq%EQ^*Tv+{_Xv(Kab85ouh1hu LBcJ^6@BF*pCa(w$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5761af9170fd01b5ce9ae5bd6b649ecbb83b6cfb GIT binary patch literal 1052 zcmbVKL2naB6rS;}*Sl*QD~hmGSAikn5-G6>l~5vuNFlfgfkNU$h%7>*&CbMGy53pM z?8c2xh%@)9g4{v5K z!%R=%Ze{{d<1*k`2#98B8{k>34SrTOKpRe;>(rM#m-->HeS!q#JCq~GvxT4nIgE2E z0_GFfZ=#T)fU}ToMqAT_Zn-`k3S5VZ>OX0<<&HK}ot>%9{5T1d@|Md*h-E(EG9SAF zd#p|QdS`Ndo1!KU92D5jPj-_c*B7Dfc`~0XkR75^4NBlt=ONmRLKLPF>QioqlI1Xx zijdRV;WdZ(TW+%!vdG!S!r_$q*!G%?yWw^V4^bR!Ud3t2YOsq44X959Vt&UM^x?9F z>j|uOrqGHdW=U;(GlizF%*w-M+V+S>@d9)ohXH2Qn#;@i^2Cxz~J z7i6HPs&M=d_1cHA9}e!uHy*qn+lbXlUk7UCgIVsHGJ3FZI1=kiU9;I&ps@}AES!(a zQ}Oxfm#eW^>t{2@6`8&`7th^`XIEl#wVzXr2JGpexC9U953BKtBsThnYOKK%MayeF wpk=k5B5Qo?<$yl=FMx2oJ9g=vT1nd;|0G7_4PKNMN5IiYZt;8Nr~>8w8It2e@c;k- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b6ce929065f250ce62b32dfe45dc45929dd7a8d5 GIT binary patch literal 2354 zcmb7FOKeeX{TwMrthZD3m4H)QXX4$MH;)QFNtGZ_v$th8abJ1>dNs%wsWPH z4H7HFpGcSd1$L~EV2zNxWmSnCtnP|4cG|k>1F+@sH^-mnJm#D;Y;qC_aG{^RI{P{! z2!HTN{2GJF=kRzdq972VArO%yY_XGQjEEeM08TbWL!1PhYNSIv3V5tB9^#aZIunhI zDA06&lBUn5&ZbG4jFEAI$i!s|y3XOT1=`YY(I)9k} zUZ%fDXUQbYKsy~dTk^gqQ*;(DlW96nvdaQqAu|J%Cpkc~p=@q|@?;*+LMU4tpgdUu zv>eJ-1}IPRfL24<+5qLrI-reEwmCp~GIc2MXM5{nqe30(nm#3)U6=W+#q7;y@8pEK zn=M9Y@rs9aTMQ7PUdy$*K67Cah5KBd8FuaHc|LSM5{4d4AX5>Ut^Zz*QkHW zNUn$qch)gVdPO$CBt(NfGC!)*DMO?BFaw7h4DQ7RisRmQqpQR*r-6b(Fj4?VkM?hq+* z@Bz1mcv?vUw|3C`Pc0T&jR!$U7F?9SZ+qFbeCl%ZhjZ#$V_8=WS?3X07<_XCJVIK2JXb6dOr?O@{>TkM_( xW1n>x>DlxJ@`lHt%YXa`^|6O$IQGpyzqKC z1V8TQ&vRe3A@m1Tsz1-E@TSNj^evK55=o52DombDHZfFg0@;;pwqzH`ElIZ}H-qd+ zUb5sCh0C`l+ZeI+K5{&1TfXww#^S)t$^U3=Rn?;EbBj ztMo&sW~?YnZ{c*g|Fy*5N*&DDR)SwM++~qE*|2mT6;#MtKY8s1n= z@O%`9`M9`-XB4$cpYJH~p`CJ1Zd) zTIGFA@CCyK>OxqN3t6IxyG8QOZmbB=RX%~mtvHY4LiCd@*o|SX|BSzSP6GI_6{FnX zxs-?y4zI&TT@b$ z_*Eem#i9_0j3&5|GvPLn>;&JFRs$~{3PW1c-1Uo$SyAKDO>hM@uJ_&8mo-@@8V$ek zPE|cFCj^mC&B2fMQ8{E{H&_nCOY7|Fe zVL1HQ29HD0Fckz59D~=!i&{rdJjIEvvwX%s#f@KJ-m;2g(e*7Kn{Kh{{`Ow5u8L>$ zbK3+irf)@$AJW!x1zSGsYPPPuUCYP6(C+gqrS#WR?CP?L%dRc?V|b?I2n^=sdZK*` zXeG!!@F>Z>Fql{C8K;M1UoH6+B~>V??>(g2$;blqHhCQeGf>a7Hj{3>{3py!^_gyr zB{hSw*^yP%tU~P82+doAd1SU?r%7uriumdft8Hsm@Act^)~(Uz4D5#XZT2+ynSmBX zv_eYJ+;o8lpEh@M;7Ll6q3e@4eNrHzg#9)=v7ZLBC9LLCa=~ngZ$5^$YP!Q@JGG7VAKuoh~Sue!K}6dKub%)ysz2ffY3+5echB9g44HcH&YkC|uH0 znhu;gh!mtB&>Pqc^!JA)Ei?Py-ZXkyXNtR~=RsbTb`f1^?!#*-$7Vg71E z%*1On_ZmH_{>>X`_7M3hXts=I4Oe%?)mwJ;R$L=x*NEY2He4;$@zz_fn_*A7QS0a- zcdf#Wl(~_Y+}OVRghTBUhu;2*cf9N!f9aj9co)jvg%gApp9CO}_Rj+Q-0!XaZx;4F z2AbUWRM2D@O@7b)xORk=&LD?(zBo+hj?kBe+h1{y|K=Vy21oaUM{J)le}Dh_Ve43# z9Y1348Fv>PZO(WYqFS)besIJt84n&g+M;pKS~pr|$Bx*$#@sxev$Z?M{UxeRl-bE6 zcEOlmbhLZM(nnOADzo7IfwB0|(e9smA1Sk=N9>$&$FfPaSM2SRUgVk3Zoszx0|uHf A!T;b;?;|}-Zpz6ExPPUYXaflIw&;jp;#KCJMyFP;D>j6g1=0P01 zMH0^u1o{Xfz671mh=;cxUw(pk1fE0PThIBu#=j-b>$jZt^ag)$b)2HXN;Nvn1V~F0 z$*BQ-1)8Q=Vhk9XDy_j3N^&7*)GAtNWz~5(-QX}45*7lHf*F2y?V)iMYz?1o4Yytl zaJ@Da+F0iD8KEQ-hGl9F`m7q9T>zcwBBe!=zE5UAg)}zFGM7&nnppZrL+|i<^@g68 zmfA)F5_Do6D^=dXhn6fR@EfCoccBD)pkwT=6o4rvD~OBdB($(v+*Ye9Zl~zS1;9 zTPQZHQkmJ^8+7X>#<)rutNkWahx0^BM>O@?(d6Wi9gW7L$+NT3bM|U9IX!+kuDk4E zhOF+jq`#rHhlo^`nX)L9cJrxM+iqEDMJB8OqltT*x_g{A@sZX_*S$$8t;nH8UyxpP zjMm)k7@cnZ|77cMcQL1Z>{6L+XM7yG?Rx?CF(Ey?f_n~=d*35C!o+zIA-_V8{EU3^ J!@u|Me*;#$1&9Cu literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d7366585b04f4276f57140333c47510f156cc5fa GIT binary patch literal 1046 zcmb7C&2Q5%6t|l+P12TXny5t4zzi;vww8f3fixzKQ4j)+Q6|J9AuEf$5=i67PAKaM zapnvM*d4@$KLDid%q75q5`+Ze#I0o)v=dId@o~fl+t0s`_x$WP#i9x1Ft={rexv~K zEe-aDw!7JWB{z=&0Tv*TWISlW!J4r$M6nbKZ|i%KKdY4`s-+pJBJ1T^x}`&q0aT}1 zs=UYyd6q^sepH^CBuBGP0MR!TVss=(OsdE@zo87GLKi0C;Vn>^*spPzt9dClb_#L`c=%*U?49&1y++?iOu zMNyLn4hn4Nj@_il^+jlVp7e7CvO~11K?$7f+(4^Qh{9Ateah`nvK&TI5pr60dCg(| zn%k^}EOKsP;c!ZQ?66i#<{UyeSUrK$eAQqb5gJgR2*mu3F(|@ib7vA*?M$L2kJ^Hw zC}?t9`meKy56f&Z5_vSef9jG7Ix~oIL|EJZoBMBlkw}|1R7{18d1Cg=L_W!3Oqhdl zqNY%yw{7lAqDaaY8cPfKLVdB`m|w1+!`JJLYZtF9CVBk3hd3z=NPAHEH{!Dpizo;f zm;FgfgWKXLbUin8so?Vef+T;jmEv{I8BYq0$Pe8X9YoK;cKUofGlDy{|8Hud&kkEO z$48}2`tihTIrS6>;a70!6Ucu8rJe>V<1r|AOQRo3hu@YC-!F6z9Fhk;RfXf9)tQfD zCtqHU&)#@Hwi2tAz7EvNN3+~D<>|}0&5>AN=$fRjK!d_Bh5d1PGTuM+>}qV*`q_+e zT-p~;$EUBvr2cV|kpY za98!NPXU*Gj8>KU4!-Rb573*fD)pbSA&0HBwdKh--+!3TU8cWr8iFtHw&~*=Wi3>SGZZE zby9>GtWJ}cI{%TBXdN$+GMy)tB?T{&YK$7B2B1Eyk!pbU;gzTaHnK zGyttg?oo^yq|{Z!v%Y%$_<*|9Hv&r5Jf8*3WcFHrcz#a(HIotAx)WQtu7-$EZu*uN zFdt^+@eF&6*@E(iZeTeSkEqWr<_d~mIelhhVgyFt;1sA#e`wi+`gabdJ<;o<{wE^>H6l@){`e64lN;#x<=ooGB{hSqr6Ckd0+%XuBSxC z+8ioK5>y2~v-PxOD<~^j*BI%VWbT6_^?F8ownbF-sX3Z7$pClm15yY#`0K!ccp1pYq#!Yod5s6P5EysXaq^ z9uY`+T|61JHATd@BKwj|ij z@$qSF*@6?kz7l3ICd|Y*EDsnP*fcu-(42e-Qo=kQHz6M)#L8nvwk1Cm(KD^dmo2A) zXWCP!&#cpuW$qfbFS=Ej#c}frSI-RF5|w!(S|%v!M7{4UYJH)$#~j0QL+#|v?ze~S zFtbO`jiDXDE5VwW^uy{7AzkWQkms6`mzGUKw71`FcMeZZk2_&zV6(nqb6E2pt?r7> zc|Ln>WR@9&hV bsfmX>^)8D_X)RGuOTcR?dhaf&slWaOAB0RQ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eb0bc523041e4501a0a05d5cd9aab6e7d0370d88 GIT binary patch literal 2629 zcma)8&2Jk;6rWu?UVHoz$4Q!PzHv$OL24&yDjHEhX_7XPNK++kB#f%j+B0$2U3;zF zwS{xa5y2n8zW~aGe*uKVflEw{yjrOT#3{F+asnYSGi!TogD9(L_PyWxy*F=W-+RuF z(I^D;f!{tWBqIR)%9QpKYzeP-MF7487-Rqzu&4<|G2;`M?4z-@x%L7yn zW(L79yL^$^*C+myd`9Y z=)MF0#VE|ReY`i0oC~g&8K(XtooJ&}ixieUW?Le|Wb~Zg6MJ}8kwVJVx>qucqm<`! zF#!-?VWr#d%BF^6WEfpC8h?B8FlSJX>7gsQc|)S;m*Ot zFbuO$$g~uzV#}D@Z9Hg0Ynr=NOh8*PB z5DamwTrr=^Ar6hh1a&KsoQ2p|(JK}znlj|JF*sWoAuEM4;Eln_mRF2KRfhsWEbs^-YQ)W8x96l;jq z9>1sXnw;t?o>W)m?1IEP44ZCIa0hmO;kBtAhyJ431@}Wx$||`Rq=b|2!Vt}+mBkYD z(``*!i#{4=vL{Sm#>sUUrO=_STP^1s(8qFSbsS#1*6-_*vH{*aw@>G~k3`*CZI<#x zRL-tFy1dJ}GJ^I$W=S4Ada(EK@&)VG62phk(custkMo&oOC3Fu%1K68O-Z?UL$V*)8S;Wk?n+$ zH-rp7L*iWsA4>+GEpgO0nhZ94D7$YwIdb1VDPHC!MKdJk5Jauvqm6xcB(U!J#ANH9f0nCf&0wP2XpebDx-|k|&!byi=+aw>vZ0bX@xrz1#&-TFb$7 z66opC4%$NAE=SHF(sYHjsb03|$TsPRH9JYM8DLnH(*@DO{TjTcmjV7*rsbc0y=Lsgx?}SxcLg)c?CXf|J}7g`U1q8AYBJ(+dtCu zPt^SrO@FfPPul(g+aGAJw|U`eI{K&5W9I1ooYts>e0EE z(fMX{qaNM30^mJ!j2zbx1;5#CfcJZli}g<~o{`-EM^3ioU$L{NN$Skn<_aKv)yEN#r|UpgcJ65U8>>u>n z)f!`5rHoa69jd}bqNO96YHKt;J7%Nd>2UmXGJM8f4#(#wFHWmAyB{B`It}ShXzM;A zm1U;LbEVyAs?DZbR#=e0Q;Db4&J~WhsmAq5gcLSyoivWp+|m1KKbt7`FFnnXJi1S literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..df5581f825639ade57ecf6b317810b6a25c1ac26 GIT binary patch literal 1036 zcmb7C&u(SQ(;N3WfLdQ_9EF$`aMmjFge} zaxLA`A;|;RmeT5hu86+lEq@Ukc23{fc_A>*!I6$66 zp2DHhBe<8D1l0Hr@GJyGv$QSnORWtaC|jTnr@rabS3H;cA+mjf1m(MwBgeCapaMCJ zb1DMn6W4E|kfDIHkZnghGlcHAJ{=NVhl=XkG}~%No2|~xR%d^hf=PMD(fZSG5;NXpk6Yb*GAeYM_L-mG84Ki3-@H*T&bd3-iLoD>G2(@^;=;i4ndK#!q#Gu?QjXp1p|5F-&Sm>U=C@*@d3MXEwS6_@> zdG=HM{jLATwqmu?*MVAjVV1k5ynVKGFcRx4T@&{eXxxFX3g_bTbbM~+_x0GU^|Kk{ zl9Vsb$MZMixwY6_@8=Yw0edn+a literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..081e2b3e935b475da027d270e252987740d6deb6 GIT binary patch literal 6594 zcmb^#OK%&;nM+cnG^8kM^`a$34=R4paj1lL(lkwzP!wZRrbvpSl!ibtSaQbHD!bhE z?nBt6&+Nem7F9Bxi4o5Vk$C_~|K%-BmY4kL4Izysll1vdqV$ULgpL&{n7HH0bzPKjqbIo}! zH?1w`i_IktCjd@0uW)z<;N|8Dhm!!WHeccJEWm5cb9yZTc8<)`O?;g!&@HlfC4g^`B>`nf3ZN?-w=AFx zSpjI3<6aR^hO7a!&T$(8$`II-q&aR&KpFBXKvy~Lnt(FoIzTr#E+e1}xe3r~9Cu4V z8S*+nS&rKlP=>q#(3>2W6HtcS2IvmQy(ORwc^jY~aNIir%8(xd^e)H!NI)6#9zZ|l zxc3E=As+zrA;;wflp#9+6*z8JKp9d5=p&B1E1(Ry2T+OQ_5_q6_W>$%+`fP^Wd1O~ z{%0yr*6-3TwG@Yv4bw6lqitv#t+NLYsI}2H2+clcMVgUZM5x`iRMRmm_%$>5g-yfI z7|J^9I;u|bIkjxn=rWZ3SZ^5`CW@o96q^Dn+B#D;LapZ!Q2Tt7EfcV~fhX77`nk2; zHo6bg&bDIdcnr*P13^i=Lk*oe)?;k8a`1cU)KI$))>K_}Y#}E|bDHv~rC{|_KYGPf zyGFZxq8gZJ_8572Kz?YBQD<#yM8tkN1FikC9>_7~YS!~_d0F_a>qd9G=a}c}^3~y( zT;QZ&+Z<*aaagcqhFJ#Q`J5N5H!fx!4us%bALjH0#bZc+DWTu5b*z$9a6`H=CC5;R7!Oh*_%R{Rp_u1-SJA1R3U7pfBZ(%@ zOoAU+^kHhE$L7^7w$&#z73m=feW09a4wR*qiULw%3VTSU-A+w%WZ#yKWt>6IiJh8a zJEi|6p_OB>`7$TA(L~Eqx+Eomo0d;&!f1mSRJ*C;qXum?vEscKOIHwa(^V2 zBwhI#HqY!6?vf;fmPb_WoH#sbQu^~Cx>8UqhuW&rJ*Jw`R-MNOrr7fiiVqh(-#M0B z^F?@Iu`Q(DKnu&h?!jS1$=|z**Wh{52X9PR#vfmN+^jBdt zcW6>d$~8^t!ci@}1e-OrOBIV1HV+$ySo07`@;X`)L@02ovGW}q;tVVZ66-v{a7wLD zIe|5sB(I`*rOTt>*Xwx-vFK696tgmw4Q6o!WCc-sbgg?$*-fUBV6_) z)oGt#n+1J{J;vF=%C2GQ3S4TcMcX{Agr2KjfC=rS;=RpvxacnBn(k?{nQin^z69rk z*a;(bXgJR@ukB_yJ%@r1%~TTLSnisyv@UI~^yd=V9HT;UIhLV8#fUP4e6~FtHmRi= z1lyvLyC}AAoYOrKp*mWDFo2QRD{r{bSg*0#g>DsR)@v6S zb(vH50y{A@rDz?)Qk@e${c8!yY{QWQ-~w0J4J646XxcPvm0Q6NW6wiC!~4#w)1fok zD;?(9%a_jIBrgPI5rPAB&Htom_l|UYVKml#(HQsbUO=v)s{@tr^te^||oEJ{ryGXF(M28vs{;T>qXS^glB^Rle4M z7#@7T7`}|k50LDgJ{o&Zi^%Z@L@c-E3rXKQUwHjl_+1dq!K3g#++#WvX1jNT@1mta z^2HO?yJ5vvo4&1Ty|?_XW|!`JH3{zTpv8d}@AWE_u%kRS@>1(7IWUKGyosj8yH<#@ z(><0;`Sw4whPQnSuGZc+hY(_ieJr=^6Dl*F!A}0b{9Zyc5E$>o^Bj_Ipok8q06rBJ z=(l0`MCx&B{6HSHmbaVGc@srNI{Eu7mb>D!v7bYz(7o_vC>c)wDu@!j$GlL&-m~88 zcS?STy8l>xn;Rp>|C5pdut z+PsA6-ckOOe1)?GTM<4txc!8GP4~XcAlPCgkR5<*Xi!mU0tON@*ozH|DhsNbgim~R z4i5q86Ij6b!&;AzyW<#M0976tA@Slo3HC2k*h zU8+|6ioap3&lzhRqHx1i#x~hBlbLJUrYi1Pq4U4!jCcI zUV}NSnuW!k9;zZnC<9ata+khS~J2K#xa<3pi)FAns5SllF|d)^4zKN;;CMw{`9 z78nHSKhr?^FDx!%Vgh>$YDmqpLP{`fy;^MuN(+#gn{E_iVze=KXF7(_(Wo~ufeD5M zIS;roJeaUqkX>ff0K;c)7NnD*N9Jx8pQj~ZrsA8uxv7zTvhbzPMgp3}Mse;W7Lz%5 z?O4&^)8C;rHV*`g>fT`Q*qiIQkzGTFZ+tgWJK8Ci3T|}Qo3MaOuqP%hw>RHnU)UA| zJmB&P6*%lb&oL|qd&4fw+O99ezKAV|bin0@pb%nM4tkI5-mFiwFxOx~4g&5?Hhviu z@-AdH$nyz6CIas5iv$J_2RCk+a6uW2oZhv%ZyB-}b zks&0o2?5)c@zG%s7w}-0L#TPpPzZdb878|gZFd&KG)WkRZTJj#fM*B=7x^CpRsnqF z%eS{|tG&&l^_}xwRt8z~u{-%5Ou^1H`aYbJ0s9xQgeT%s?=LJxq+tIq{Ou%)hkCTQ Vln6zFuu7A_h5m1U2`T*b{{V{U-6H@1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c897c71cf4b1f1e4ec20a1f760fe994a175ca7ce GIT binary patch literal 7810 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ea2f6c8ae126a6a6bc6046ebb014cbaa456bf153 GIT binary patch literal 837 zcmb7BL2uJA6t`-EM^3ioU$L`rsk-wFVDY=)jTt%tvQ9 ziV?@0WH`pnpTvZFu?P6dk6YZ2gR<++TiMzyj6;kdgcf)=Bo1C7+4d2vFM1f&n+I|5 z8c94y5a<(#_!_i6As#M0zWxaD2t0?{OV9b8+FugqbsJ7|dV}A)IxbLeqZ}S*0;Hvh z~NR}2`hm}!Suhm_Rv`w4Ej$7{lW7d zuC^vZ8_QfiBa~#quuRQCkIUZpEzqgXQ<^8~yJQMfNMn;MbNPg!iKV|a^cHWI7xc2Q z)YcM^pcCs@sp7SWrn76kn<_aG)7?bR*gr1IFZWn|RX<-)4mn6}sFLM+bH7lB2Nmv@ z+w?qx!~iYwsZLz#g(~!_!kOYl26gLomRe9grb<(;=JFx;@6rlW&ugXFu}WoT|De~d z))?a|Wvue+P!-M-EgjKRTPLIQV|Fq;8;+iihtJr{;ppP@#aY#6_v2$#ry>0bZQVzt zvdk2DuCyCXwb^vb3M(>U1sF}-yHxGdSsfp0t#s8H71D|sH0U$Zn~u?#+kw&P?tcxn y3inqdI>auE*>lFnq1(O}U>_6G!5g^aFuC(Rf+I|v7ZLI^^vI9MC*S=$|LzxW*Z{2n literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e0418dae8ecedcaa7e9ac06b6a512f2d86ab95a4 GIT binary patch literal 1036 zcmb7C&u`O66rPD4$958;s%j*H3Pa&isZHpvR28XIDkUJas4Z!=+FC+`>zQWVbv)LL zO=wQ5J#*p20eS~<;SXS?_RPh=VN;`(K%BTG^s?f#I5CHr_vU@y_kPU0C>Bj1 zm$`py`@RCe`_wsJ+L3YiTpAAm0Tv*TWISlW!I`l#M6nbKxAkMnm($7;)zXZVk@a#d z-O?e*0IJh0Ri0*sI7_1%KPPWZlB3y&fatplF*-6xOsYsfzpD(qLh9{i26l0PJc>Mp zL#4mqPUak-#(BWA5D?AMHo(1F8{AbkKpRe;=+u`ym-->HeS!q#cPK}WXA40Eav0}S z1k5L{-$Wrp0cRoGjJBo;-Ew_8B)AS0)emX5<&HK}otvr7T%CkTdCTP@#8OYV%*U?4 z9&1y+-kDtgouVcW92D5jA9j->*B7Dfc~Z|6$PUpF2PJT-vxYXK5QQm)`jp$DL^+J4 zB;>U2@S4N?Ew@>-c?*AM;$ZV9oTjP(KkSx)l!^Z0O57=VsL< 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 0000000000000000000000000000000000000000..5bde9c3891218e0f8e02392982355efdacfe9663 GIT binary patch literal 1226 zcmbVKO>fgc5VaG>Nu4xFQwk}d5|EJSffP}3K?u<#ZB+@HDrqZWgjRNT8o9E))~-|3 zT=^r(v44O&e`Bwl@)s&0G3&HV)QSVx^7zf$k2kw-*y5t4;bZ;$div7Tv|lQje{z_7 zz)$?ZKs8&VI!*a{kh0Ud!fC{Yok_5P*tD|=&LGa&3kf!TE6CdgU4!gs5wfSn(-O_n z99^Im&5upw7e?i=W-mcT{YMpA80+>j-21NCD^M8ShhC>wC zOpCBgR%r=VXn9p559!hsdPIv|O?~T?;gekmfjcqKTEtn*dd#o6gOd~BwH~8TpWxxN zbzNE%M2~w>%s6JHSw%Lr#Ki^<=ZdQz^qz}43Ot*zvhWPK{|?2A>}Le-8E zZ6;MzEGy;Fs$SDyEF?IprV?DRjEM2Ln#L?sDO=xbd2rPDxYv}~7MwT(KgJ`WCZVoe+M=`z+{1l_KzBSJ zq}AH)HamL6$zCg`uwAN4!_6dY=`mhz!?QjsZZdzB!>G&aw=181&wLr)U zIqGhcCYh;pWr_IAp+E_XV(e@omk1huRTL4i!(*H?M?pw0H2xfKBMFyp1@CSAV}C2_ v>(RN)y$cu}`0!rG?Zhh#My8-&zlLt=shiih%vr^ZnJVZ-1r_L;_!M0kd9ig|s_nY@Q?}=YkRRQcM zzkJj9Fap3Et^_AK25+AW0Q?9Lr~)J)(G;3uH6(BxVpyt%eJn8?smeYMGaRkX_&8!J z%~&-q01;rhaU%#m`7(BCq6Mh29H7Y;$ta3ukb+`otX6*fs56ZfU%4dC##(ij>)5ClwU$mGB|358KQRTrT;~PzR{o!FI>u8L zMhUWCE6GikwZfB@{3_ClA{r$?o36!B31{ zTk>QG5vf7wCF_LLO+0!SHbcrc$BV(qtf(}KG-3SdQGWy}cx8f=&xqx{GDRb2!*T{_d5bUDW9&Fu9dBA_ z$7(gVFILy_M)!xro+6pq*unAA@aSL%mf$4IftQ3P(GW(E*e=^_$D4(0;Nzm8<7(7p zHd7R~&iXD*ZfnpaF`_g<*TI@=TJt^CjvVAGsmm-0D@TV#Z;&2|sZg-R$Jtf;! zvM-hQ+VW5ViPesJv!~{|YVM_)@2MMIbz=y?gKxvEp#%)KmDjQK4;$@hALQH79>{k= z{<-q=SLfjKOHAiuqw}3}4&1(+?#a2|}FS7J{Rxyi}wQU Pcd;;(L3EYoS(kqUcp$|Y literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4a55020e1142c07085a1da98731357fca290ae6d GIT binary patch literal 865 zcmbtRPjAyO6t|r;%hGncjjOMfwocmtao7fElxmkTbrXn4$V%ha;*r{s?W}5!>>F@J z6=yyMUn5see1!=ZP72dFaKctz-k;y^`T1wn?MBG0=*yeSPaZ;F>)`$hUGT(x=7TdF z#fW20G92UPPh!Ho*aLj&$1U#1LD_ZrR<2wtWQaiylVx=0P01 zMiS2v1bPS&UxU_1#KWb>*B>Arf#;(3(sO>N_LszY-3HTSukm{~$2sb4l!N0;fV4D` zoEp$qplO;V#(<%z(i%*zBo}f@t)jVBR*j3v4u^@5up)>QO#hp^9y%?9z5b)U{@(K* zuC^vZ8_QfjBa~#quuRQCUzELzw?L;lPidZ{?~*A{A&pJ4%=Ht7CYJu@&>Os6CUjg_ zYHJEe(1~@ZRPmZb)7h2YO_iL8>26Yt71L_0Ec;i6<>P%;AKcFultT`Z8>(cv-b^FZ z(P4%ASYpL`w%W)z)xycFcx@)4}NJ#o!rxIT)Rvyg04e>`qKpbsEs0 z($-x?y8D|V&y{vURh!LiSz$#ctN^2ld!?#F$3>T!jZK!H%%&Y4)A*ap<=11=z=gbnpi5xR~7f9>EbN&Wi~78G7VLwPlzp(1h`{wN@ z5&&NkXMZVs#_mgDJOmgR07IN|p#l46%1B|!kO;i3?o)oBMjFe8q9u&9n`x?s3PB1G zm86OEJT<@>3Q^c`5!G>qq#psSZc12dizL>GB>e2A)c10Ux0&kO`95+l^BfM8?!%4L zaX_>afTbb8ilMB62bC7ME3JYS96QjiF1QZy0%Um@al+OKL$+&iPB^kDV}$$E!;aTL z0YyHe0j-B?6PTa~4d&a(x9mGsgCNJ_f#teF&*8`l&>jcHaJ+4ydKjP}p%9NSD-bA~;)I0k<~pm` z)LU~J6)UV$lhHa2%zs5pzkbS03YYy2go#f)%n|k4TE82I&Yg*2xjl}SU1D*9LchT* zQD}pPY|w9$;?O0@AQF{?(4In+AxvA|pV+?}4S6!X0b?Sd)aA2h#YJGnSK7 zthOxX2_TP)7i-H4=Edq_wKl&}J#SvG)~;PzT8y*iuVI>Tt`FKz6@O(saep5AK4oH1 zaiM=(GYlNp2^_+i_{bp6UTP-!Dr1zzxmxH2PLuS#XJ9vZ#~)`ovn&5G-B_I+%xaDe z3%%qk@>gQ(N)W=&;PMBM{QwGG1(Zf3Q0x?j-xrR)DIC3*>l`{F0$o{#qaWp|osrWo zu0-c ij&??ly_HKz+oM1DkhsC)!u$}}9?HxgmbOb!>^}j^RY-RL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a902be13a1d61c9f1def39f89da6f554b285ba7b GIT binary patch literal 1602 zcmb7EO>g5w7`F3aCzG$n*|c7Yc0kEwd*U4@O|#V^Ew(aw=9$;?KA!h|%u6L?z=eMM?&|NH zVf>{hn-_4#SMZa6z(9?mK~0(p%rG@fn;NG9XNK7XX8>D6JHc7NxnVxRR)E67uxJ|0 zo|l+?RlcfFo91YqB3ih$fX~mjZjE7;W%d8up~YKsSYwZVFotzjoNu!ltKX&|t~lRe zHClohWM}CsSBA9AYWOj&usYp(Y~c53b%l1R1E`j8k5*`x)&XrN+|CN^((=I2zXSK? z=?RONATgtjso*gm@u1<&#$zTLBTiX!0f%55m2nkIO5URpB#EVf5`~N zZ=E(1q4HM~iuiw@atb<4bGh%{PbS)F`TRMg8ZdFqPXwP$+ld6Hh%ylgnQJ!8{k&5_ zHlb87DcuwhS*MOlqn9L#Sb$|rMC_&$x`qmOG>fR4Nysv?_;@T?>}DJ3ebaFe3jGL^ zXc92#T5FDcf5+LH+xyW(&PraNdpuq#Ln!6bl`hC9nea&fWdIRcTi&Unyhmmf>$0wN zeU6F{cg^uczoGHWgdhs}-CX#U4Z?0t>4H&yicTFJ*K=lLA#QYCr{M^E3ja%P%aD+(d-3$1> z^F>-STN_N`1(8|O5BwmKKHtI*JfYkNd76F{GZB$MXp5*EUO7fRxg>!PR$^z`r=Y06 zG~DIVZ_4WMkob|Z&MsQtoVJzSVPi52Vt6IUiK(Zm$5i(@bc=t(=rccH3U#`JcK`J3 zyw_LuB;XziB;>q9Pg>fp-$L7y2|JExH=2c=^@l*J<@!0FZjcbRdO#`%UktjqjqkGz zR0Ri|P)0GI#_*kxs*0B_tVJ!&1Th;F80lw2?89j!!|P9_7`?mib^A1@uT<`fkkeVf pzJxj(@-rAR*`j%OXH3gXt?$h194cq6RM9L0Z<%O)m(9#S{{l*fM4UU8Ph+5MS2$1wcfC-?sF)|+qOVw!Y89QsOcdT6- z6;2#_>mSfd|A1Cp+GCGB_ktTLuU0~8RjCp;s`OCx)S0!{HZ-l;(Q5X+-~49YZ|A+Y z--p8xumQjOxbmwXfM2-d4(2=K)gJ-?p92i?01H^ugsPbL2;BBCTgrPKTVl2^FFUrE z+5UXMv3(k>2J;~Sh=9nIJMQ7VH$%sJo@-U^x>oOvW$ecR3~}((*G8RF1C>ON<65U3 zBR|CAdjE}4Nx6G^ZyY*h-^@oo#`X_)Pa9!gsPblet$!vlGIA=m5l2<1lI~w`+a!j= zjOTHA#8Y|u?<_Ne2S^Ovc2{tOjNn1;qVE0Sp)NMo8El$Cvl`Q~`@|P~PVycdztta^9nzQX^mP~23iHn|S9z7h(b{%)YhyLDyOBG{ zZsxN4JL}o!&$c%&FL)*W#5pmSlgqAU_u9)UX0@W0*kq4hTh3*>lyBJT?6@r>glQ2W zJ5r{U<)>Y2tRoEG*LD!~D&LSeGT4VWyx?xplB*e^_bviszz*>+1OprZSU-5bGBU8XhG3T7SpxvuJLyLh?TukCaXH$&>PiE z_eWsb?e#rc>jN3Bv%$2ZXn%JDt)pI)T|0s_s$hZZSd<$2|#Ye%;Kohro*jl5^@e>=-FFmJQ@$|DHEZy_K|U4X?4@V@&j zTVU=C47R{r6UO$xdPy6Bg8&s;4pLoe-1`JTRHJtVEV-00@F<}{U!W* z>m0m$4LMsVo{{P~&@DOIl2bp(DQn{X$%Auo+*)~hGIJJ8HN~lOaoJjW((@i$3rpNf rG{t-8;v;K*vFAOsmYsc*O)+&YE?JLP_!{?q6c?`|z&~X?U{~}P;!Qs| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c51de0e0a8b4d59beb1d8102534ac86eace46543 GIT binary patch literal 853 zcmbVJ!EVzq7`B}>OVf6{jjOMfwpQB#fwWDaQKntS)J-6Qkd?-NJCD?kY-iPSWN*L? zp*;YP!E5BoiC36_;iNDPi4(Ts{Gb2t`~CZ~>h&VzM)c+F`9}|-uQj+oLI<9>&wOwT zM=|1r7HBY)4AeB2DJrsm0D0fq)Jn+7V#ZlE^cQ&%B+mQaG z*6%V>S!RknSK3)rtu?o0g%z2w0*of^eX7pkyv~obR=Vnr3u(n18uS_IHOFX{o5x0{ z!~Zh33U`)P?O|8VY`ewBp_{%JU>_6G#jCjMFuC^vI9MC*S>B|MnOB CUk3F6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..026718364d97ca06f01c4dbdae9c56da065eb028 GIT binary patch literal 1052 zcmbVK&ubGw6rRa$HoKd|P>KN~)v@(bXcMbYT4)QkMiHg9v4~xfWxF#?S2sJ$%x+_I z63^a6w0Fgee}F=J_Oj~1xFRT?ye0M`o}5iue}E_FF!O$V-+OPqnOFI|4#d)TZ{B(+ z0q`Yp4wkZS?7a}i1Au`6FvKYr8nAb!j1-m(iNIUx0p<5;q_J!$TEa-XnTBep5TpQ6 zNt#H{Qhl7E5QUA1sE#uv{Rm)nTf$mPB(Y8;;b*s{o|j9!?Nras_mKOUXRu%C9^6We z0HU1)EDZrx3}qGEuQb6OX%#f#=%H41!F7liAj`vu6ShehvR#XF!jVlGBiyGRcDy)>?kv_q(2`_(5x-GVpiSpD$dE)#ijEn0I6-r9X)|eA_O}owKJhR|)N5(IIUG7S8^dyI3@y3D;sk|$omryA z77bZ{(gww$OA-l^DhZ)Ai6}#uHoZTwe~%dQM7KU;JfPI&Gqa=FIAfZa+NK%HNh(&G z7V`v<$Hfb^r3Ldsb+KBTU#XrmuT^VTFJ4)Uv*s@u%{bQs9i)oCG9C>~9{N6ILZZ0P zyR8`pj_U*t;Y@sC5N9tol6;jh%Hmut^a7_rdfrp8m%QMQY0m7*f9V>lGkulj*r2GC zd_VqDY#j+g_!(UK0J0xIp`(D(a0H6&!r;5Y@z;gpcXRC{$3&nb%W(LkJo$d;%=639 z+3RnIRwKF8Re@Z3uNT|8h(4d&8Hm({wqEZ_P}_i?a)+bhSaf*&$#SGuy6Kd5N|et} zN7GlLsijC??q(#d20JRqFTkCdopQ7!fVHkBYggf(q-2#2P|`|A5t1|S@6 g51n`;my)rEfA9fugU5yW0kAuenLjG+mY~>w0=2(F*#H0l literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d17b87f6338eb59a79329480d86094fa2c8dcd09 GIT binary patch literal 2763 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4b25031769126922e7a3d4e558606cb3251a327f GIT binary patch literal 849 zcmbVJ!EVzq7`B}>%hGncjjOMfwpQB#IBe6L$=4AuEgj7LU}9Y-d$-WN*NQ z1L_0t7`#TVoOp!^7)}b)kT_v0&j0!UzTdw;t8Ok)xAcPipHzW>TBiZ&5tj>EF)sqKt zaDgPAAqeylM0^EWpAZih9$$Tgcm$q9?S<#!P8~0Zi@FV`*}cKKUOV6NY7K2Kv10oxcS-(RoVqBz>1mfC_1Bl4Y)*Ff_6Bw}#&0&GLd? z7M9vt0upp$9V=D57SVKitp}-+V=)<|$<$@pzYxnWcUXO3Kc7<$IY@4(lI3dCg;4u@ z74Dat^fZIS04?%~PTT==Rp@1(Q^ku6Y6I#jwV-@Vm8M+HF@N@Zqu zuh*_t7~?8stn%wr70wbZ9nn-9N2Ak2b~HR0j-H(lpR-rP(b@6Kld8?`O=MN4A^k~h z+-Ib+%oKU9v@@t$Z*I#9D>7jP7){*kRPEzwogZqgbk!LZ(uyfG=rhvmj?pZ)PmE3n z|6y$v?kbH+?U_J|?7t*Ko&Sa_4&lN0_)MBIIZ2kspyyzWaCn-7k+z B1!DjJ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..80e1bd5292fc66989df0cfb079592ae46cab7143 GIT binary patch literal 1048 zcmbVKF>ljA6uyfc$958;sv;6WrAJ|@)Fu?9(n_ULDWO83QbL8OC1kn2OE5S->&|Xy zCdACn0Np_>`~Z;JnZ>|BFhT+`u_bgtOq`RpfW*WNckjLL``&x^?q28fIuJ|Wxpn7} z1i;tC*Bj)8TN2jV0>nCzgrD7#`d%*awo-jN-)HV2&*31`1Gt$w z0f=@Iurvf%F_bm%u+jqeq&3ijVSMb{x-fGiIqPS^%v$aXEx2}d?%jBuZN*zp=D zpvY%5pzC3M5|g^)k%7Rm2`_(3sx7ybsq)NJdFtXgjEi-L@xT=MxI;bD;igMlgsry6 zS8o&4V7`rf%f4$h2y#3gSgtGbIUHF5+S8yIPPEt1dKjP}kr0nCD-bN3;zWe(<_4?S z)T=vTM(aGn!|^B2V)5 z_$#q>B?#dcaQP$1eguWC0!m{MD0T|N?+c@E3ZwUPog>FYpexI8?2|nGVdTt79FJGzLznBN|X)WwcYdJ@#?@N@2PRGf$oPd>dC>6KnOrJWM#^RvP8Okgim3B%{?7sjxNI^pY literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..22266c82bbbd3d970d7f34160d39f627d783704f GIT binary patch literal 1383 zcmb7D&2G~`5VjM?PMzN-6xxDH=m97aL4qC;qND{C0j-j@2TN$>cqfsI?TvPwO6`^B zKuV8123H4Diznj$Nlv7_>p56-;^`|6w!H# zpZJW18lFK-nv2Xh=jBa>^N204ps|J6_8g51h>PBW#&+b!C9iB6;7lsu99NHP)S*SX zKwVlow~=3%)XxoX5en)*Y0&by={4cXd&66T^5iNsVd*@FaplQ1XwnLLm>u%R%Oapv zXp$9LgC$yDF~}-i%+Ph(K-ARSl?+{{)t;fgjis}jFCYQ#r$AdH&QcbzsMQ}22EbbZ zqtKq>hK&tVx)emf!%@mOdbJCWj2Md)#m7k+#z0Phi;yLX5~p#WMTGjP-}eO|<@Cp4 zM1fCjTy}a(nF1PVoIMEQ6aFAz$sinROhAyox@}#kxNTy?{&!ZRq|&r!>#lz=-E!yk zQFo`;(RmxIZX;sDA@D3S8H5omn=SM1LSx-srEn4kKw>}fFURC`Ov_zPeev$E5tfd) z>0TT1Q9uN6Jg7g?*ec+qRm|5dw1v98{=}`%%Ih+gW+mQp9km5K9bH%H;YxdMOYQmX z^lg1okIwGTM`W|J)Ozy8QIk>s;hX7}YDtA;Ywf*xzWZYOMfK{d8d%H*x_f&E9X%MX zVJ|jrNQV&00zzbM$k;G~S*K7=Jf|j>$c1cpUNQAKMhl5sGy0O4xE4vBToROAzLcjO zCMod5kGRs8ERsxaa_z{ELewGbE0JSG-I-dCW@450HjDi*k@o)Kvsb$v>1@NmA4e%} z33C$Kmy27J_JD`D&Kc0>s0-o%2;^UanaRg7W&xY7UV-?Eguh%kdGxz(oz8BqU!OoS< OD)P4J>bGiIKYjvlVyvS8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0f81662d0a81b9f327c2af971be1f8e5931ac38f GIT binary patch literal 1845 zcmb7E&rjn<6dpTHY@9fR04fU<(ro#)KN1RyRMd7?EU;Uu?t%i#AyPGRGDGCl@kC<> z2~Igy>L1Wk@2$A>U+B4)q)Qdc(pIf{;+ATA+P!qfv6C!aRjn0`zxUoZ@4cD#IX`Kd z3fQCm_(gSH2H+2FgpU${mn{K+ZvX;CfCMC(LQO121dbyNOU0;{XP+NLWqMj6AJ zZt2%KqR+{wQ;u$b`BsMHGv?KIDzsjPHm2;N-Y65}gwG@;l9JdOO~tbJ)l{#&0wGS& z;w@F?M_{+(_Z45eksCTvZumH*n1562=XZx0!S0~6zj<)guwGhwdbPzzqQM~7{?kW$ zkFS2^v-*t(#)3d$fB$IP4;pi97Av!!3?U+A2))q?Ar%w%CdK4Lx!Aop9LR=B+>;ve z_(Gz-8dC6@m?)PEV_y@LH+8K}&afOzE#0K-e++mE4CLm`9qXpSbcheQ$XQ2)o?4H4 zV)bG>q^28|7dv?N@bkTGPu{^Ndc$Mv&(GfyAduP$REDaXQcWumLC8wuKZ8rgj%=r+W=U_r z4xt`^V{naZS#gC5D0T*_BC)|%od5ja`~H4b{eFVnNWQ!|`xqef)q}eucHpu5%m=q{ zlp>BfDR7G0KS>D>(g5&Tn09!WMs?rqcZ#K1oW>YI3>^qANgBOGvKt~;z8GNSCy&zT z6_R+4AkzDg@FnPcLOi|-`0_o(69^pYUIi}h`S^;ssNZth-Anx5^>K^_EA?ov5Fjnh zB&P=SIcS;}nK58!uCxYID#?YMQmbgGl~t$JWP`&*NLUC&4rchxT@M}B(bn+c)^O|D z05@wBp^as(o)JniVOXK&pik<-$y=aPUFNjR@^{%3sF22HMd9iRLo-W%Yv?szuV2&C z%2MkkAVFu=v2w+|h~~3%y_qXH5z|c{GcfF*kd>$RnLlz^UQiAtNN%W-^>W*XP`f(~ z9@gvhsDR7>t;(s++z}V5(u+=KidO}A3w#wXs2@opRhCLSi<;H;wyd(E5LSTE#Jx|`J(&6YNNc5=-nf!h%%DY|kzRF-cDa6Jblw{J z-{v;)_QI-N?5dgjZV7ShW*9^`#Dw(l3hp^fZo+`z1QQn}g#3&H@*@e!_wXjX`2~^E B2Y~

*&CbLuUGJ=B zc5&lV)id|1qPe%?!XMB|$(hTx9BLL-0&(JIHy4x>GwXyO5*Ow$^L~8advCs(C)KJA zA-AhJ@+k8N zjw}5PcM7KgwY~wo2mvu1V-q}>?|~n+P0)igU-Vn6fltF2xgkM<^81t{FK~sR0(p#c zDk2sVKkT5Gp@_4XZ6(`tgl_vG9TR+yisoz8ZLM!CG|w+I7rvW;S$*5*BF3_w@L7m` zfdkf~e4{_JahIYFk31B)-VbhvB0m(d8w9eRFOVCfBM!>oZ2vaeN@5f%3JocDV~O$@ zQ6%Jb@AG+&h1-5-UfGxm{7Dx_TW7Iq*^G7&p%D##O*3YpvB@*=SwDH(Tu+SFWvPW&BP?oKj?@xctH(D_`lVe2a6&vr0P&W4}vt?+BU=5r#4f)F*1SPcxKlJwv0Yr-kV6x)q&j^Y0$b0UsO({_1W~q-0#;@dwx_b zSZ8GU>SDThEj_=U+Sf-V&1%D;3974bZ)vZYu1jEhWa-uoc%T_&V+f3*G1O#_H^UMz mC;tZ!P7kI|{i8S3*wcq%Lf+uF+R6mjpD3+-rR_JM+}{GEoI_;* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b528c6d03b8abcee59671c7c4d43c816d2efafdf GIT binary patch literal 2231 zcmbtVOHbog6t)w`PHtX~!y~{9Ls>xhU9t!=PETv2t?mcYGf9Ip>RAEFuOz=$G#=mWmAX zM{r1fvT*nozV4qO2s2~|OHw8~Ne$C%pr=9ShM7?3K<9@-sAoXW4s)T-n`n|B7FdP~ zw?!&klrG9dAX$%GLTn}0 z)<~((1mEq|n->SvqK@uSvN3gR&mP<6#_00wj5-@*o6yERyclMi^%0@&*fFM_?ZB!0 z;e@BQZ3d8YX?ezk;w$R7hHV9qdp#N1CMLS4k93y;B#bT%lThcLhp_jb1tJ5&XKub8 zPp+Juv2C3h=R1U68Dokkx}~2}2Y(7}gk`(GrJ>FLFNTy4N@zsMHs57Xr4^RZ3~N#> z&$*TmN*ScaZm?T9=?lbp*Hcds^4`*Mp{R!NyA=S#fOUA!kW6L4#ceV z4AqkMjyg~^+&}FPl#ZB_R?uS78aiQF>xczu2}wy)Q|>65EEc6lm9&(TL%G`$OVTP@ zN;-7fO-=0`s$E4aKX&T(6#0#!i6Xp(7!2G(~>fSGs-WY3t$VuUh+q9*&=CK4$Oam3)E^ zl>uKHXvqEMO}?O<5Z#{kGf< z`m}iia}iEyC)$;0Sfv8uEPX=bNi8EG-pX)TbHN18HW*elp1>U>kT8@BBbeUG@2-D( zq&NF$c`mnR+g_aiOH_(t&2~7fB)_}yM98_yM|`-?FK@m|uG4(1m=79HqIf#m?cS>a zsYuYqU}c(WcfSp78)&D-Un!uR-vjIOkF7BhY?$#k+~_aF2sBAPP_!tJVFT9~KY;LS z^L9;D2k||>f_eLyFJMgUF~)xR+_uk68nG1+tOMa0KaXc^upI|H7+*{vA+t}IH!2bHNZW_SMv=`7mRHQ}xeH_uZ&$sM}Zdg9w zJKB5OZu&xtp6QpS2ag0fF&X(4nUFqp42W|>$v#k-k6KEy z&@R~b2lNkQ(TYv~LZ$9nskouCHBuH;SL~?LZo2B7mpvvZi`KHvob#RM+;i@+fAaew zz#sU-7nLzT0DtI${qb7NyATP$H$VghAQB>3C2C~BMd-W>^HjlY@D%1f1=`@ta2- z8yFkEVmHynx^_o_qrk%C@vSXXM@q4Y#85+#n{rvMhDwL~`$!3uWf4U$^g0&^4T?xD zD^jB=D_9!pNkT)Gt2(3{)|*ld39pc%N^)I?)T3HSt_otYSu7P*gdw_gC{;zIT@$L zHNKh82-aKm$IR@la(luSpIVOGbjIA=OlJ5kq4&a(zLr>O>z(^`+dD}vl}=dgiu5(Z zY!z-sWWt#(#%1$-F2SYrK826*To_ZDH1CQztn479>ctweCUpqX)~z&#bwHm$`v&8y zYE9rGq(vi?ibv3O*S9qD;gJsK;ElfASy^sc`PX2;jAh?pqY~dz=$epym6trmeO^9( z=)6w-v8;U%y%rT3rKQwGT3;$GZliu>V(HXo0=F%neIdQW>;B>wU3>F=t%c|}O!^xe zY9khco5XGLSu>C%<6R@Y#6%nK*Jsn|yk+wvRxeCzv>=FbSrD|L16e+(B6C~e1*>2^ z(0qd323b)k9bp`Zv04>lKQh9V^{VI#6kA_evD)~RJW;9ML`uC_RcPJ8D_9Fyo7pW^ zCA^tYMqm55qO7^Ucg^*!dE#=dDAhGjW@qDhBBs%Cv|l`|Ht{RroPt==hB;BpAw|M* zj*+;DSD6Omd@hzvWblsB=!2?UDppmT)8=M2^zENSs*2Y-R~PyEVJ+VMT~RetpO+gB z5Sv*JDsJeGesS@k+gWnjkWiJ2A`%6;(ZsW?YQuusElN zs#0G3kH0(moJAW)n)gvn77wfFiK73((Eos_E0|uo2!i-?5D;^3!OC0k)c&q@z~UKT z+F-E-7CY2Po0@7-Q*A2TqQV`@-Jv`-js4}P3G#9T`0kv+$u>=38L?TloFpbCfDbgZ8 zJ|`b{9zE?z4?8Psx^%lmhR@0So#oY@wA5LDq)W3ca_*d5>pb|(mVP5wFN454r$q4a F{R{c-@oNA8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4197d062039b181f7f3adf8272589c78b0c97ff9 GIT binary patch literal 881 zcmb_ZL2uJA6tdC%|rUOsjQ|C9tW!(nToL=MiPRBXwZIpwPOn|gB zk(?UP3(z#p5@W#7RA~(+SCR`kqgK&eE2}Pw=?;gfkgy_%6iok{yB->q!CwE#UVra- z4_8}Lp^as3o)JniVOXY?peJQ-@)qb!=PAvT^j$IoDx|SVmbrPt(8SW;9D0klOF=IR zOKnX72|BS3l`3A7XgXi$-Bih`nC)^{I3aAFNH*kb~rgDp{^K zs)RZ^tZ={Fre_%>256DbbmBgEr3$^$Jy*QQpf;qQQWMI@RB6i9Qa;4~Ex0hXiz~*C zRVp(FhrM>S#u!&AW0haGs&JWT>42u%8jjCS*l;i!jGs;h&)Cbs`26(6sA{u&Q(4t% zKz~|W_Z_J$Gew>&?F_3no7=L&icDAmMick9s`lx;?hmw9y6TJzX~i6x>NC=t4$*|$ zq0#B?f3Ug=4_3w|CyWr!{ZQl#9j|u7E4cu`xx$`}OBTQTt5%M$i$dAY;-~BuP F?iXFC5|;n~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0bef689551ae5e035fca1c6afc23eca22128b8c1 GIT binary patch literal 1080 zcmb_a&rcgi6rS;}*Sl*w7D8BxE6RlAf&_MHBm@#55#l1$3W$TEuu?Tzb_RFJdS^AW zi;GWH&$;)|a84>N`~j^5=U%qWp=ME~Rz3A*hfBn%vo@s7C8y3|=Kc7-@4b05KNpK8 zkjp&#a{r|Qz+b6zv$SjD@<(ZW2MDkLfh6NW3l7eVl_83yP`Iz(Q2srwEKx1ZNEul# z*U~K=f()QK%~It!GsIaM)%aZ*HA#+UzXwD=Qi#!!Nn%n(`uQVe;1yEuC^N8&1LSq) z7!H-bg@>8DfExDz&q6>nOWOg@Yi;ngvIE+1>Q<+|>bcYpk?j*CC_kVaIi4*9706+n zQxPzqxPB9b35$+$R8;>?t8H|&+3Ngkb@sCu0VE(t~n@y(;XM>Mj;AQ3iTBlJKvf?KvP6*>M;6bHMX;ksjhdgJl>=0=jo?}mz#!T@xWD!4+w?zlu58yI=&#N`To!b8} zFe>GSgrJRpjOV!a@Um6ljYNqSYPd$QD1?^0sNyd7MG{vv6)wo zVzbuIW{gjyd~qRO*o^15V)Icyrx*>`(?M|+o-UqN<1Gnn^bOT`3@;Teul0bI)q0Ao q@wS%(`sf<~;beFG&aY}EZF}-cjK~|jD6Ncuvyt4&N6J|R%Kabfdrp@C literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bb053ccd41678ff8f9b3070c91bce8041b41755a GIT binary patch literal 2424 zcmb7G%}ygn5T5bB+s4>qUU zfNL(1_6c%`1jjr^4*L>w&53hvdrEcB*lSo6Z9H=O>#w@2x~glc%;x5h0w46(yR&yW zMfqDyWy-~YGy6~&gU8LFTC0eA5HxXEu zy?#WCWDaIvokq`e_dUtcB3>r*bdltj6}&$8XS>v)-1I0}9dYKdKC@SQTFeoYdpYbe8xzwrd!|c)O7_N< zO(?%jfZE&VVu-`wD_86N;W=OLGv~w_tP^^1YB~fzI4#SAy2mj{fd9Ko%Lp^&K=0Z+ zwAk8vy=Cllng+et1B+E@UqdKj5nWwXzfMa$(jyXQQEbGxr$=EH*L0L0G26zL<57NY z+IZ;daUuCBmWt)$YagO0-8|l^97BvZ@8~R|j7zx%Tco-``2xzi)NvWdLx{XBjij{# zO2c$)Qw%4HNxMMx%rU{@+5&^x3et$>dfe)b;VQ9Boq>0%Cnm?_)E(QN>39=Kaw7T6 zfs~Xip`1DP819?4)suG8z^AklDuS*Nb6kqWZL_f_yw1o?;DBfLJqymt*)Oi1z0X2& zx%!-BpPcnyelu|eIQG= zP>P?v-XbU7NJn5Iv$1x^#DjUZr)aNGW^{qK%nLk#B$0v4Y%JZ`Iz8Quz9c>RdvIc<)=@w&N=-o?{3>C>2A+xdake*=i zHy>1_PtSQTidqHz&^*TQ9^l>D;U;c2_V??DySP=W82F%J)UZ)+cUtxBqfX7hl|ut0 z?W6rp<*cym~d89$^^5YE-^E#iQQF zq4horMQj-dApj>sABBs;L#qkJ>h_JObW3=e3bo@s@V44G+^O&0{vm4NwOD~Zsj+B8 zQLIxK`~_?T>y6smdbJkpvQ)5ZMTPO)L>@|Zgk(=hw27qEXmmp9Zy0b`4h{+!HHpe;(TmsvE&<_jJ(GK|W`=@S`%i_x zglNd&<99Cae|eV^*;pQ3`l;`Ortu^E|5026hFCnSPM(;WP$SbP_A!NW@kAu6=73MA L=)ota#{T&iBN4jX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..90021191abcd90d5e213274f986ef34e4a15ba97 GIT binary patch literal 3062 zcmb7G&2QV-5r3p)OM0?onYL5LABm%^<0KnPS@~n*Hfh(fPrkX>o_gv$QlxCvSZoD@ocYby zn|Z?-|237u2!8l4Z;#%l5&9=*f?vE#eBTri`YobpA5npdn$QsUBLb%*Ku7lnJUR;W z;C{@b2Y`<6Cp>yk!;R$rkbp$QVn-8!^WGcKsU8dGfg3t{)YA+oHp<37LY?@}JJrxp z&gU*a*(gncUc|=)#3DzJFUx(}I-zMcN^S-cI?TpthO;EB9~}v?$xu%MY_v~rEW~D>bCcAwr%h=qE`>ErZ+J0EVpU^+7u zo6(u3Y1l^1&}OQwg9B#H)C|gU=REHRf{Q7$YNpz>4HLNG9!Hvnp>fD;>9*Qn)(vKL+68q7V667PZpxxZ z58-IjFl{*k+<+wGOw-UbqUts?-zgetSn>du{4yGkW(Km~L~x?Kx4gOsHD0*la}*~n zW~yM~x$~Wm;USCZmSK_xR9^FfB%~3X0Jfzu0w;=kaRI6-ElT*c`2nQ~EK$|6O|{yB zt0WrJ>-M2M7#!1NR!g%L-3}zNK=MBc#68IkoK{-4L28PoR=qe0z~j;+9tB@bL$?^= zw{5g+o^{eQf&^@(W~*>k)A*Go5A~zaT$Y^j&}lp#Iyz(uv;?Uh+su;Fo*3c>t`1ba z&P;h2@Pu>;H(4FpOwM??S5U8Zy3%0shzEXwM|gT*PfQ5ml~tBNlrGHXF7-h!yri

jfCCfN&9xg4@1G_0>2$3vguY|a^&o+=WkCV;! zWLbGn>d+)!A(Mq0S4_jxZJ_4~9IGm|FPTo^Y+@#oUBjuXty{qH?$={M14rN>GgrJ_ zlSmc{)rBjDVRy6p6vw)bybmE+n7Gc5ul(>E0n6bz`>H6d;%DVO0^b37QQBG{Yz7U7Cy{hZjWvmy3;6kl_kw;VN3lQwvBs}_$7cOyE*a{d4_5khSm8O zeA3+@VJnpOHXz%z?XC6A7w5m{v+xgG1NYph47Qzm2oHCR6n*_->CNU^$+uDkh^t0; z@N^*WO4hk#gG;19QrX_#?MlA_!GkF`MhG=(gt)_X!>DV_AFVL|O^_ouNqTeMgba|I zxrBI=-}J_JhV)H;lh1e1?%M#a+!rV3%}!m>%osO_lkR?SZ@q!Z1B-)|`Bs7xRs8kx z#@{aPV9{tOs_qV!Uq64jx#q@->_CAT2Pr{JLaXk`Dy2K#kX1i0x(-vx#l_OjT4l4m zyIpZ(beh3H}&UW}pS-BUbqhO=#){T6?661%`CeeX0ieU_SSr)IxR&7GxI z+NqTbgnstxX-Fmgo9Pq$kEHbV%1PWob0_gLG}lIRf5d;@=%8m;kkiH5DQk3)?!=_C z*zDh8v(D|iCl5Q~l=IVPC-+a2_uArIM_h59JneCxJD;y`?rvNBq$4gnOHX>-qVs9) zd1Rwna2?~P~1s@IEwX*7@n5=V{2nh>;NWl!gx%SYg3^oUkHV2!} z`?y-22yHBL`HWDK3Bxiq2Yp=jk57S4b)M2ZNzaohP$7*?vdrZZh9;K&*3cWgR=%Mp zg{8KZfCQab$4V8iMKqmV=#5m#iI{G1cz5brVE=?HKiOjSm4p0}a>zk)LzOJsO&vn* z?o@bCuF=B`5(Bizr#f+0T&hAZDxE1_WKfTw&Qc4?hg50G)m+}^{#{~W>bWfx>sG1E zZ140t)e>V|rHoa69jd~4qNO96YIT2nxX1Q~qv81J@$eaYIUFAyycks-b~i>=bsN&3 z(CS@8D$7if=SsT~RVz)mtgs>zR)EpOy-(FSnAP#2)=F31aUrdkL4!Ucz2X>+xp{1K zy76Dbt-|fasCKc-X11L1ap<=11=z=gbn!CoI!tbTkKhOs=S7743_bEA^2vAq*1!D) DK(Yrl literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..535173824a9a354c125d26007ac412614eaf1df8 GIT binary patch literal 1052 zcmbVK&ubGw6rRa$HoKd|P>KN~)vr*{^kab4EeUHa5yU!?grD7#dR{K^wo*Mi-$U+4p2L2o`*1UL z5)kbaU}*@jVkj%%L8S@qN-LlVM-H{B^R7d@09hVJoUjeTknLKW6OL@k7~wwku;bNH zK#|XAKv%=HF-+DRkMs$SO?dfhQf;B7jF%_J%i|YDU|d{t7!OR5k2};e9d5d`N!W60 zWO<#SI`eJhTlO8RPLSjAz;a!Y&*8`l&^`ynaI|$3t%d;#5(@DMvjTy#DNabpZfvlM zO}#azUcuz1woP8$@5E=eRvvLuAo1fmRK+VuX!{@r586W#iZv4B#SPtS~Gl(|wg@*`Uaj zd_Vq5Y#j+g_!(UO0J0xIp`(D(a0H6&!r=SD@i&Fz_j2tc$HYZPmf`S6d180y?29YW zxf}0>RwB97Re@aE)r)Oi+`gFI9*ETWwqEZ_P+Nzea)+bhXmoh&>D5TDbkiyAv`C+y zil!E$$)!lY+RaE>4R%zJpNHGi+vR9U0Bc=M)~>-lNy#c5prnE+uUb|KI~+f+vN!0kAWWnL8@&l%Uvu0wVH582|tP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4a82ba49526232646a744ee15ca73dc0074fbdf4 GIT binary patch literal 1362 zcmbVL%Wm676eT5!k||kFE0yCs+^ph_3^*ye35q1L>^f*-L$bXf5DZApl}cm8A!eum z@wVSlR9@yYvh63#wks{WO@N?xDA^)hWRc8(!*kEv$2`vP^>xR<=6rm6@x?Zbf3%Tp zB{ZJnC;!HQ8m>W2nq%f9=jKg~^N20CkYWq5?G{s9KwNUyQfxD4Qg$n*0ma!m6fde5 zHCm)4x<(yZzOs>Do7Jxjw*dwHpEYUa%5+<>@rU8Ip)$JzEofim(62K40a|n&Evydt zi;fIv6)F9Et83!d>8 zm70!|3u(vFZjW9$8>_Sf&XZ;8J*S-6Xj-AXcfXriQFj%$i1F=o(@!pc%6et8ERU=E ze~QHZ-kbTa`m`G)&D$TG_XoY>HwQNljxwSP1fbp+{x!^NANm%04-J`P)%X758D}O<96l!kUEos_}wSy!#OI zTmn4^87ODJKkN;TPEL;psyJrc_n5?**XY4ZE$7!Dr8kBb5$#9Q$^Oklk!mr2%Hs@) zZp#3vn&fQQCq1&NGE_CfxJQ8!9&0^Gye)hQ%S92Hcp^cc34m7YyFoig~v#DwxG4pyOJUU$p1&D>45K9!tm!Om68D)P4Jq`hidpZ^0O CN2?0} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0def31d04319e7742bb888621ad4aac112c0daaa GIT binary patch literal 1826 zcmbVM%WoS+9G+dTz0TOq!*-;!Nt58zXd0^l2fK^|ZU%Z5~u^FB$~K5i>{zh^7l4&+tO_H#R!AM@;h z0V|<=SOPL2YWa4*`0&lyxgqLX<@vrfd}9>{@fe0UbjjB$hF5j0yy$^z(`66kr+8og zzeSXj*AhK|!hIblh9f-A*N-E< z^4hym)*2osF?6f%!4qT}PYQ?z=ZB{T-0UDO%DLE$xcGn@#GQ|ykFQQ&EbJ1KFwG`- zvC61Ti_};w)J{(cTP#vcQf*OpRdOLFR*~sdn=+0jh6q(DH3Y?Krma^9IwQ=|sVOMy zyi%YB!kVoWG>dRbE!1=a6V?`Y*M3h-em=S2;(oDm#?nPh?$+!^z#;LFUkFobi?t4MXKiYS}Z=qI7pmpekkdjO?LXaEh2( z!61l{Q-c(3zV8_ycLk#m$~vA=L|Eb(j1SA^D7AZ~*I{U6qh5#5`kmCsic-V8ZcZWK zZF=KkYcy)4O7FN*?^38*Sbx!eDo%R}d3Ce7lWZ=t|KiEsZu?tt72gTx^oTRJzkk#V z5JFg;b5(>eEh6MjlqfA3qVG~>?P z=#$b-n=n%|m?{#2sHZ4*{zNl$E;Vu>bj(+j>k9Iut4BAmLo1qYx`Bh^$ItgNuDU}` zwVGk`E%BZR7u=~w81uW=d7TS_pXdg0;ZF82liNEu+RwRa$)E+zuz1hDHUC)1`Ez1f zT8TU|an`I=b_O4c<#zo?v^qlauwH`YM$z$M7G=<|%b`1g460#*5v>Y6S^T#6m(W`j zvTTjnJZ6L;qMVuk~?t_T57=C zEtqJ+#7p>oUF|?HzTAl1X+@IFNb+T5r4`w3Mz%Wud{PVWEsdWA>hQNv^y}?<&;cv; zU<<4?!OHjW$ER1|;WehQ`BkG*y8>s98f~e`U(}?tuvEWyCC@qQoAtYm(0o%~xRO6~ j);5MH?R>N?P@*X>UCCR{=KVhURo>`?KyaDe<0tSpL`nQ_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4f04f4febdc75351c637f25f4aefcd136d134dea GIT binary patch literal 869 zcmbtRKTq5+6t|sR2)R4h=<4+(I0Qr00ZBfDR7G0FG&dx(g5&9n09!WMs?riJH^s0PGgK9h7JVRB#mAn*$ojaUkxzwn@4H% z8c94y5a~lm_!4wJA|BraeEAXL2?Q?c-UQC?dH;quuis+Y>@|Mx<~T)zm3nkg2#}U$ zl2Zfv5;V<=%os2XlwXnYq<4% zfSa|6(8e;?&j=-%Fsx8>&}a4F>@Co#E^}ID`MYciR7hj9qHz6$p_!$>IrJ8<*Ev0} zEVZ5j5_D!ADp%Z-Xg<5to4Jw`G2QeH*o3H4*DH05e8A7cL=wK8761!Kc1RhXUK zLAP0AjH{fnCiJByo@ZJ*plQ|)$0rBuaCAHx@1KpHv6rLq>CubhrpxZ-WKFLH{VA>8 zSERDcRAs5Old4&5U&|^h3Sk8pP27KKx<@l#A8D<0(;HXPiW#)gXQWphqBXbsM(3OV zC2|w*EF{~*uB+L0Cd9GZVG!XE6Vk&gxaVSW7X}0;m^d#XFfl1&&!eive4z8z4vk$AIf9hS9>Y6(gt*D2VfsQZG3j z59D1GYYH-V@6dYa)*y@jfE2LjF4uO5azTIuow`Z&Qe-MA*=aLmDhKlY_`dHwKJu4x z*#>giCtu(DQ3K!)<(x0$%s71}jqd;f4j_=^0_ec;nR9YPb2JL?ndg*$PbW`w$FLM5 z9~8Q#V?vMv)TDW;Jp%`Z7sus!6|isExgn@(y!jyF5mo zWuC%`(qnj#n+Mdo0(c$*VmQVg__oml54Aneg9{h>&Go>iVT|06AVK*9%8?hiLQsJ` z#yJ%c3yB|gP|Q%oS|zQ&rpQ8_V_U%k|}33oxs;eJ)}w^@Ptt z>hQ=zk?Y-eI~4h$h}|HNdcHtzjLtYHgNuC+?I$sc6@`YByRk%h zj3^TFx(|56V`1CxG?XBo^p8In!qNVxSRw1t0U|V_ArXj$eQVs0&sJ|{u-;!p+W~b2 zMM>1*t}J!H5ok_aYR@z{4e(XRuiG-H(@Nrj0Ixl=0Y(mU`&{Y zai*(OX7*ejN}$N98?EhiywTihw$^r=pX0mD)}77UTUimm8z;_6W6*i3`Ysc)7>gu| z7?-2UD&yPYB=!S8_Nn0VD}$`K*;V-_=Zt5iRuaa3myW$B;8eZj+cm+x#{ZgcX0A-8 zwaTZZqWX^FXSoeE2;m#>#Y<3p2`WPa)aFu99aN^DS7v{$%swg&KDsOeLtTe+uk>p# zX09H6nSS=w?=yR;UK^P}uf4FV16xLqR*$DrbA4d5kp`^@zAjx%tBdKyrJr_EyD`e= ztWTtT`9^x 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 0000000000000000000000000000000000000000..b552b8088a3d401d27cdc11444dc960cc955a0b2 GIT binary patch literal 1997 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 b@fLsUoPVI-U+QYV@~O*V;Ga_2zni}SmCm&< literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5e17bd96aa7ab200bbdf6026c0f0d701d806d5e1 GIT binary patch literal 849 zcmbVJK~CH-6t$g92$>n!(XF>5!5~<)T>zq@Q^ba%0ktAhWrg@pYMQYl+hIhO08_}0HmmfWZzBb_D2p!mWpZVYx zj$*_yCpnIB`zJBsUhDzB^y3cq6uZicxMg5l3?%v?{u8&hRSgA$_xd3Tt zA~`jnuRzl@PmBRWQ>8VSLP;)UMy;ZSR#shj9DVX6mcRh4m1zW?XTf?mv z16;37gf^DBdPXS8gkiawfj+ASXK#UKx=3k}r0X3 zX{l`_AVDYAu~Nkw5lyF8dNWmWBC^d~Wf^GpFT{$AZPpw(EUqbs0wgz7$!fXnLa4pn z8V{>=dXht8fR;t36L-M1D)pkzsp4f0jR8%SMo>MbN>i?8@*(%{220bpwoq(XrE;^g zJLuL+jB%ARR{Kq=4rhs$j%ez&!|}-hI~*O4#?Q`1&)KWd`1I)IaouJ2CbGKMlK!OD z?lV$ZX3C;a+8I=@wzp-a6}hkij3(}N>h96B$&a*Fy6%ljX~h&;^cm?@$7q+^Cq}26 z|6y$%?ktSj!>*Ruc8iZgH+?U_J|?7xS8&f^a_4&lN0_)MBIIZ2kspyyzWaCn-7oP? B1-}3Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..002d0ba54846ba605ab63c10a43e96c1e0003417 GIT binary patch literal 1048 zcmbVKzi-n(6uyfc$958;sv;6WrAJ|@)Fu?9(n_ULDWO83QbL8OC1kn2OE5S->&|Xy zCdACn0Np_>`~e`fGmC+NV1xu>VoT_Pm^deG0f~tl?%t2@d+*(M_d1`~fmr&^tvin- z0KO*9{!;dg-Iv061TZiFhB)Oy1NP69k;0N85qL-4r~E#RG?ooTOBiW4(^L%=f)pSs zNfYULYJf8oqOcPps^bhvKL%LclCahmNvsn|_}ML~@8uG2E7iC2edJ!|IUH1a05?-7 z0MSkYmWBW;hO!18R$Abmv<6ym{7}2P=sLs;kmX^-3ELnH*{;Po;mD?p5$;nDJ6;0? z6#0w>bUmz3Vp4ZJG9WlM;pJ~hx8=4nRi2qDPhA{`ak1_&9+;vYcc^DN+;nM+u+{eX z>TQA=%(szm*>|l5L5{}*%XLLPha)RMdmI$QiS`;=4+9h=6ygzP1p;MLoRE;+++Y=( zdUdB!acQGLnE5Zy^w&?DNyD3fFVy*8!#pVN?ks8Zaf=jOcPVvG-EkQ#cIo9 zo&fT=xKvwNG?%K&)!M>p^@4e$TDyMf>T;Yl4@5NMTpzTbDjsA!8kjuveaeJDaiM=( zGYlNp2^_+i_`o2}UTP-!Dr1zzxmxH2PLuS#XJ9vZzn^D0vn&6hYOKx;G@56_qE7Pl z_$#q>B?#dcaQP$1eguWC0!m{MD0T|N?+c@E3ZwUPog>FYpexI8?2|nGVdTt79FJGzLznBN|X)WwcYdJ@#?@N@2PRGf$oPd>dC>6KnOrJWMx^RvP8Okgim3B%{?7slk3_+g& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..205054fc489e756d7ecdc11d55564f4196603e35 GIT binary patch literal 1692 zcmbVM&u`*J6t?+cU?4ysBxKWW)AZ6*6;ZQVsj600H~h#_Z32R94ye`0cpj`%+hdIl z?Q-ouLXQ0(s!COl{SSQYiEA&F_S84V32c%cwze3)`QDrP-n@Cwd}D(s_>kYfzxpnt zD1XVx`jf`VH~91Z#(^mVg{drNsdj7-S0#=kP7IPEP9W9>sSqa-rw5r3YZkGygPf{B zYQ6!ftNc}grC6F}7-89K4f)Kxc&!XJAu0doC6>EZ2W8m$Ss7FyH{XUbRIX#_mz(cE znQh<<)ggXW^?qb|DAQe5fC?+_D)bTCjL;q{Au5O5R)qFg1<`iM?L=sgRT1rm+@lEX zv3y^Vzr(%RN2lO`(0yS0uHZg5xV1l=j7A{#4bGspz{DtrYCsrxhA>^93!D{JGwO0~ zNlHu{-?V{FL3k#2B;~!ahuoq}_w}Lf0aB^q#IzWQg@)TMK9)lghd`MZh4fk)(~+y5oT5Rb$8`OKA3V+qIyFNvy_0E>E;6w{raG@z=< zy}nJPlSS>oBHN*2c-ls2e?a#s(MBU1qpK2Jn?3%dwibKs;lN)gCmE6&>c$V?FjR{b z;|DKE@!t8qB-vHIJv?aqp1{Kq9<<5UYPTNeeiZo^gsgT9GnYO7@STWP%a%)qT6mpL zW>417wb~>qgW5fYy2Io1MN8Ju4#hllo2O^(dau)c(K&C{JLj$T>Ec&eX1^_6R>ns2 zvR#i{%+Iil%+GNMHtEV`x!(Lq8fVg2Su^%Jor}of540VA5Tqz&+@Lflj5!}$uq;vR zCJ~e3S-k$69ujfM6xW!rk3?cg-{~b0_DGT5>8GH2N2g{wJ_tv*glw-MO`~oL9$)H~ ziERj7+2a^dZ`rJu%U%t%2Df$73AFC3lb7v!kZQn4pIAN~2{kDj2Aj_r>w_?HpBb>0 zX+c05&3?Vt?p}0yL27LAp>BDod4nFG$W}iC&(p{7++j^;VmEFR-~~(j1$Wm-bc+^v zL5aTVH))-&RQf@IT3lzqD0hAQ#&~#J-fd9S^L62)&l-i5|BE6HF&p9V_A5^qNALNi zktJ4h-vsGrHfIwHzQ)^Dyk|Hhk~#I}O{kh0yL}UH(}UW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2275338e550da4f64f52f96db94d105dd3f84327 GIT binary patch literal 2244 zcmbVMTTdHD6rNpQ)*jnnj3EvXpeBZprgj2x14%Cm#$2j^EkjUcwOXw`16gM;*6tb= zp8Vb)(7(`%m;QjNQq{-2;Kr4$mGV&ai8reBsZX8RwY`p^mD<&6_MGp0=Q3yJ?5j`+ z0`kD0zN~!V2jF+AbRSrSw=}KoEp*TYF zdj5@3NpyXjFAAP9wpJV^{?T5vF`@-4_Z((d!lQWXjP!{eu=dv*d zU{~dSzulJg&(RPb3! zs2b6R9gVN5szf2JrWm4(`D3i~94IoPO(Mqpm9nuCFk@4Wmx9f~p#w${#@JM6@~= zSi1F2#g3cpQu12IxqQ0Zzb_|9=NcrhAbhp7w0p5Z@6ZjN^dX%tA8m?bT+beCXF3$|F1beHJ+hc1yhE}5#jmLGlp4qSjD=iozeD($*cPWu zj^~kD=6Q4YNL7y{Y+q3_O&a}om;rM9-5Ju{6bbhs)*NZpFogMmS<>jV5ca@4nX!R9 zNl`JdrU;V8Q3oI2nKpC!K#)W-hB!}WoK#e>hxNdoRnxn!%7Unv-u&~mXWJQ*TgQh& zO)|)qNKPJ=%#o)E?O{zMaZWMX5G8EF_3U1zu$|w}6-@3(QcHrQlbl^?dX0|y4%T(y z2tQR&R;kJBy$0x}ZQoa`eITKA9CS0nKi|vp8Q#gVXAbj{Dj71bpCiS_{rMNS*&X|i6r7>4;H z2$=adApHh>+I?5rAaxGHEs$z}RNEbGxhEU$$(B3baL3zjPuuP7u2239aSVGI1%bJ9 zIMIUf28_Rkx9i*`1Vi)Zp+$4AlcB660F1D0+Fv*>%KNv zsQX%Ap#c`Yh2L*A!N>0)=MTm6mwFQzZ7$N{62EYX_RL)UZj+sAuRN~bIS<@!u!$!7 zsQuxpqovy`PaJK~wv?u}=>|K~WFNHeFFRVQ6D8hY=bCJ~{qPaBQSDcD`EnHa=C$`p G`~C)317fgM7`B}>%hGncm8-9nwocmtao7iFlxmkTbrXn4$jaikokwa%wzFzEvVXu8 zRh;=5{Eb{W@fRjwI4MlyzzJJ<^L{+f>(`G}w;Lh1qAzbRKY0j!t%HXvbiq^inGeoz z6eEr~$#9IDKZyzVVh`}8AGf$42W8jgTiMDij6;kdgcf)=Bo1C9+4d2vUiUGoHxJ_A z4U%|{AkZU-_zJW>A|5V0zWNC92s{_H7oPJwwZ9o%AsdxPJ*IZjc3tsEX^0;Hvh z!IT^*d9FD9&Eqp zzcxJf(S(zDuS+g)}zFGS^QSnppaqLvQg$nb7mX zQd?6%f=;YMrHa=in$E8DR;uJgOt)qt)hfSG64<{oEI;32^}&PunsUfNazm9YSDR^s z+TW}2pxmG*86*a1kxzBv4!Tx_Ud}sHyvU#yqV7@?%Ewe`%GF#x#Qr^KVQTf3j7_Ul zW_I`b?P`TFu2RM-zb;kbJkintP1PHXPY&5=csv|GI~zV{uZH8(qnF23o861as!jv? zQ|jGUq_WHud9Jh*s#qgh=aYOQqD85h!u88p#nq}LsyF}DXs zr(6FaaTV?^1>482r`d7F$D!N47hoR~(!p!E<6?5>djvgPXXue1kx#z+cmCZk DdwvY3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..36f1b9ddf18f6266898b7947953af26d85f5f3c7 GIT binary patch literal 1064 zcmbtSzi-n(6uyfc$958;sv;6Wg`==kY7+_)0;v?05)cBF5-LP3AVoT_Pm^ddbKL#dlxO+do@4a{5-K%2J1ag_1*Ka;n z0QizRJ4@R(wq8i%0l>fl7~+f%EjTzcRt77ULf}n(hw}ThvRJh=BV}a$TuZlf2r_`^ zBukWMnIX>7h{g}fsEKnV`w(D#UBO02Cb3Br>F3v#fmcYq^~}I74v^cKXK+~Q9$d>D z1jIN5coqVzS=utVUu%Oq$}(ufiCvxgyzdbjB8Orm2)|7@a(zb#B9O~CCn8`Jd$fr{ zh62t)wi2yQVY2E`G9-8|5!J70w}p;2T|GWsojyAOlk%#^MQF=d@$&Q0oq7a2Cg;2tsP@-IhQxbApw|UKF zbk%FtTAs^UaD!1||0`k#D@W|KaWz;&m;{7kff(%=gK0cAdoqF5&Lmp&i6aP#f+lxl zqcs-s;k+$|BcG%~q*YQv=NMufVb-R9V*egA5~+Ga##G3dFJ?|o~x6*u_bHb!Lqk8Q9*wN<~ zIJQ?qvdU827YyE7-I4sK- zPsArK#m5(8^Kw6@7!BCdL2({#%xqNSMG0*54b`{;w-ha}^?;VudW!7vy_W;}=r;i2 hcz5i;8?};-J^n+C$QwK=&5eM~k=)!~WwQe1{u2ZUNS6Qr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d0da46baf93e8d7a0cbc0990779ab3c91fe5370d GIT binary patch literal 2342 zcmb7FO>f*p7`FF&XTRMfb<);J0~Lfw8*mC(rRnN1x0v5dWy zk`odKI3WH2m*mLL;0FMA3vuC;BiEjIXY9>-)AWEXkDhtndB0{}zvGrlNP-9b_Vwux zB}w{+PvTDwCSSnkJ(VSiNPUUOBw@*RqMwvGP6AH#(*maeXZl%z(|~jRyucX?*@b>l zmT2~(M6;*m(+bIw9LW8kOAZ3_ABs6(?&-;Z`=mNe^s#GPlMF~G3N`y3W2hg10<|CvLute?( zPK}U876B~@?p}m6av#tG!D$iF$TFZ6!99$SMpgm6C%E?`q>)Nj;@{e%^W|OYP^SBo ztc;lJyM}A6493GDWh;hDX#I+B`jT}RKxGGG(;}2zWx(v!Lq4Qo2&D6M!+y;+4A&W&#~U_8!tgDVx(E^s z-92?p$H$gwo4yyV#G}Ec_hS<3=KmN|3fyx&bhh*;9>y&`!T?rJQMqS6IpAS7M92z4 z36rSuiu_?(fk~+9=r&aoz~wn7g2mrrYoI29ljGcYpr!;@MY(7lQPZ4ReS#`o*Z79| z4c&>Hl~IP!*En)0j2kDq<4}up++iBj%1!@-vc^^~(yF1nt$RLYQ$_G*oEa9yV=q$s zBY~3wVY;)CV3iz7sraZXjvs zj*4JX*t|cZdn3>1lCptvlxJb+zFH6(_hvL^Qv`)|lo8eAsdFnya4@_4#-V+Jit#g6 zHQ_;<@vzSe?WL%*Xg4&p5ZA(EMuvE#`zPu$PjI`45Z@s~6vk=`6>hJSyz&?oMXdOk z?=?RG9`S=2_d+*z7OPKDMQ|P0^`n43Kxtt|lwV;}ecM2|@S`Cr3f;-^%%j>tyVvgE z?V~O}YQ1Q+zid?{rG`pVy0O)&%E|&#rc|@{yxD=eswR{)GD!)$hD_ z{K-XC+-)~rG=gk`=ghQDBg;Kk2+2hX2H*B~K*$aD&*GFeT?lapZV~g^Nle~QxQt6^Rc~f#mPF%mKH#t;HmXn!8Q7(fd MBcpe|WjXcle_`yjN&o-= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..023b358e7d5c124fa01dd3de865e0f7a25598aff GIT binary patch literal 3026 zcmb7F&2Jk;6rWvxW^5;p3F$T;PH;)nHq=hi22nnW8aMQd#!=#cu!Kfy&m>*5_L|*w zrSy~=NFYu~`~w_7z3?w^MnDWfu~vYDIOP@uTsZM&cGv5ef`n!5zV~~-H}Acfw{N~m zr7(gY{_^emdntr|<%<7{d&2LZ$_RaeD5@YTQCXK7awQ~jIRtXJ5)pD3sMW^ z|H;PbFz`YyCLtE8Ki_Y6W$R-)!p2E&hiIB9bo4A3$+XX=nI6{LHTo1_V*|XWdsx7O zT$9&H&N)D#Xs+n5LHcZe)0*$l`L+6m$ zZrWOdk%!E*G^5EOYp+o?bV5~It*RCSC{}H2I%VcD@5ke3_#*-j2R&M-H6EIUn$g_R zb_)&2#3WBp@*+uC=uF+vnl{n3hGttHleDVK*89*O`2Q@HN$|*dH`v5ne;QAS;U zREcmTw}MBujM_WQE~-s0*f5S#_Km&%gR3& zaEX_8a8mgC$MZBEyI2FLf8|!H)GmbL>v{t^owh4We zmNR3}Exi@)Z{IByOX*NGDn8RIk1qB%hDg!dm3+U#zuR?f0zOUa_nd4~}-z=5No=t%iFU~kILa0$A#2MK&j9s0% za{?x*33lWpNH8(Yu$y&&h#6{%L6{~3bJXOM65eM87WgL4d^~6wyPUeRkt8( zU&^lXi{d`BEOnRNXwtQ2yK%F31zC=3FB`2s5NMu*<@=)W8K8~D_H#S6~2_MG_34TH9IcHO)J z3qZ#q>jQY6gd|D&Es3P_KcSVM(5wFcg)S-_p!5+cbWoui9yxLuU zaMXV;{gH}E^2r!VoIAi1M>yBPx$p6_`>{WxNUBOFQ8ax1AT@oIn(L(IzE8~`rLJ{S z*G>?6sjNUT!`sR}{w0z5;M#t?i{|&^M`*r-=0C$<+&)CFoI(z+k^@ycMD=bga}=BV zE;iSlIk$i5P@d}Mm-a8b|7u5`Jd`hYpDXl~h3?|={)L0YR7ajZlna9AYnQuASGjGv zBWDlg#cn|`T`k{Tyz=jU%PZWL?Z`8S@{-4NwL=00Wdfw7 ziR9FPz6MRxEHMTQO_kPQawWNt6KWOBwX*867;kYH3kgesNWt{Kx%SXW8SM0*?(}zF z^l-H?7TQ?m@)@Bd6NY7K2Kv10oxcS-(RoVqBz>1mfC_1Bl4UNRFf_6Bw}#&0&GLd? z7M9vt0upp$9V=D57SVKit+!Jp$6~TQb(LxM55V%vT~-F}^=7L-q@(v+*2e8~N~yu#EoTPn7!QkmJ? z@3pHH#<)rutNc1tg|kFUM>N&OaCCaahJ%yA=-K(;IeRr2ogKeCsoLy*gskc`q(7mJ z`-oJQnIg}XcH^nmn{HWQMJB8Oqlx=BRr`2a#|K&~U3ErIUWvC--F ze-E|__m*Qiz%G;7b;ie`+rAfI9~08SYq;Yux$`}OBTSqZ5%M$i$dAY;-~BuP?ib#p B1bqMi literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9ca1b4ffb367e7cd5a8856fadd9c575f4d0bc42c GIT binary patch literal 1044 zcmb7Czi-n(6uyfc$958;s%j*H3P)k7)Fu=}Risj>lu#j1DWL*t30bc18eANob!QWr z2{E%G2IvlA=^p@6JF^%V2u4UCCbonwh>3I37LZuD;qJYA-}k*Acdzq#9f+lG-&%Vl z0q`|(_Lj11?7S4lV}OAHFvKYr8nAb!j1-m(iNG~=kMjF8(pWYWEn%eHOhYwP2vUHk zBu%6jsXoq7h{BGETODUe`U$}5hJ>}22x6T`!q09yb1?;&?1FJNEk0bEZV z14KIxSQ-MX7|JquSZRWL(lThm(F3jOoa+!TK$eFQC+sd^$aXEx2}d?%jBuZN*zxKp zpvY%5pey0(I3}x(NBRWECcOMD$+pl^CdyM2<%tWUFfOh-j0dLB;|}#qhnp^K61LPD zUAj$Bo%uHME&GmDC&=-5V7ac)b2zdBw97#;9BbV`D`9|wghD*RtU#b_iW3sD8+Td7 zrrxSkuQWtu%>0LA`YR{RBwg8GN0|7;!yHksrS)oX=*-y|mRn;h67uUb zWc?}|6o)QJ+D}vxLTeIHhA?eBnaUbaOoq+eguV%0!kwhD7FiO?+ZuX6pq}_wGSQ^7aduKBcJ5S55uQ6FGuHY zz8hYS&b-Ibt*;pf~?R2++j#-Ck{^h!6K(oP8Z{F&&? zm1t@)(yw+il2(Hq73AmO*7R06S`@%qSCh4Ca7R+IN(U%ur6Y+PKROwp4t@s^j 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 0000000000000000000000000000000000000000..a4149072f577d7d52a13b4b40d161b3c5e62784b GIT binary patch literal 1553 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 CA_s8* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..be1da5544cbba99767127fa28e46ca485de2fe29 GIT binary patch literal 1052 zcmbVK&ubGw6rRa$HoKd|5K03^s$=z1XcMa-7HXl^D5BIh7O^X`Yt<(}*)=vN z@$6khdsn>p2Pm{>FRLDmTLi_Ex5QqgCuft^AK=M3%)B4p_uiXt=1o4Y1F`h2TWgOc z0KO*9{!;dgomawm1TZiFhB)Oy1NP69k;0N85xA!AQ+}UD8q0>FC5*J2X{v?_K?)F+ zq>1z*HNY7PQP?pN)p3TTp8%|GNLXu&B-V)}{OpF*_i~B1k?Py|K5{Sf0uCxYfa|Ga zfM~}7OGAJaLsbR}G!z+~0&$bjJ3gjc>L-R9fMWaZ3cW%9xpj7zHy6-q^Ni%6#@z)V1KJhR|)N5<~IUG83Hinh{9K#dA(M@cIoPToHc*RXvVodXg^i@mGNj`^3eAw6B5OR z{%y@La9k&F2xsC8gE)JsndEDXQ5NUwp%*w!()XT&o#X{~r#Q2#|D|iJP7hR?VMC%) z^8NU0v2`T~;TLdu8)UaZp{s!MXaq`~!qA7pk++2-_j8?thee<(%W(9QeEQ?ash3xx zb2r|PEJt#=rvkbBQ7?6L5q&wcS&Y=Vj^5}=P`eF3=Z2%wcr-lmY%$WSy>v=DA}4da4!bJI&%w><%}TT&fc2gxYuDh8q-2#YP|`|Q5%u fj2wL@my@wacX?6V;AvsD2)2ru*+bG+8H)WUiL^si literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..156b4d46921da4b5018b7359bb59e5ee4268409c GIT binary patch literal 1964 zcmbVM-EJC36y9B6!2<@o1P2?}agid~t%N0~Qq`)8jA7#MBy;L4Ulu z_zTdVLRfwhARNNa*HuL!N>3pw(Oh+`^&+ak5x~)2OyVeDy%(1_1~}1Mkyv-pIN3|7 z3XNZ{()dOCB17UNK~@MN$r}hvSFW=+N^gzE#Q(KHQa5TZM<4yH^zt-yy-ss9f1`n4 z>iRLwkyVf&JB?fv{GUjg=I{o|&^*a*D0q{s%~6pUfO1mzXpV{`4`}_~PoN@s47!5U zZOl=TYyv7u-PRlxNxGwm-`;0ePmZZaxgAilGvO>?1Lp4Zr^6xTI|D{&`Bt=WPYn^F z{(w7^fN_vA3kgpcbA^gey}%h${F-v#VV+R=m*YNjF|h-?Z~GKf@&43t3FWssguVS- zEHPL@@Cenxcd# zOO}yGYu!rc1b1JyO?=ioX|{f7W)))-tuNHw)>-v?+^Jfo+03fO2FfpN$5y+FJEnDR zTG>b`n$WV^7fa@f)xgzOvum|ZOJkH2Y47U03uB!%aidjR&i(`yWrllG+qCMfS{*WV z%{G)!Lfh{;9G$kRC-vsBETvvGvs&p8b_VGhvyqvdo$)1#1)%idc=K@_NDU=u$Qw5OStjSCS`H9&Ku$Mi*VZdpcC|3oPwjk%?S+ITF_OWvkU{b>|*$z__;+#xW*rfN_`^F*b7P>`DTIaIh<35-*s7M`k-(Xe4*= z=uG{9Ke*(DEPlWtVc|iZvf~Amd$!93rGyDQXP9vN+;$zH9@-YX1{J|93W7SH@l&YR z*w}WwP;Z|doz|;iyhexi)D2)ukQ0-BxK<&g1Gfp{Tv74@Zf=NbCR~kryW6tD_{e2_ z+w~#m9ojw;^wyxh56o5Zi0MtowR__9!frDM95Qq!Q_3$#idgJf}VT94V70b=sj^B0C1!QD5oBQ9!Kh0%uPzNw!EXeY%qcx23I&1eIFx(Xk;%7(@I*uXK=i9hzLx(aazwT`ThhCt_W zH~}$F;aaQTv2}%ZvpzD|@X#JMK*Jo8X8mYylN)a8BEa>v>H3?Ts~Z6BLYt1qb2wLb z5x8iZZm`L@x*_0(+jJvMuBkidKIopgbU3!gN=%asM#syVYN)%aGM=yO?J;e9SEVd| z#`iNLSeP<>SCh+zssZY85h<&x!YQp%GUOs7&zPpmYKc?&K{2l?gi3~#mvjb{KVOj* z%Cs|nXU;z0LkA2NI$Yc>KGSA*)zY54KWj+(=cFi=qD*b6Hj3Pt$f=g%9JBdeRJCR8yQ(+-8 zhzDHXT4pOjwv(AB$xPT2^9IDQc(uj7oY^FabSj%kKaLe~kf&{|uMsA*lOmhx)z<7| zc-YR+bZsRw>*>{X$dpZP!4hJ4^i9Fa<8)$UJ+)>pWj&D$i?Ii=Gf21TTp8!=A|K)o z14tcC&NCgH8+lhXW1Fy1H$p~Lje3*?+{L3buA_Yof^(z)3id|sywARx^M|)u??-E# z`n8U~g(KzsT2$WPcn;2)5z9{qRd)%odiGUyUt!KC z2^~b^ebovQw^L|dXGbn$_YTc&0AbSBKQ!JUVE=7B#EQ1%iOVH}X(dU~{Cq-y)H{qd z`b1J>XgzF;_cd4%+(q7?o;dx<@~x^xNiJEwt(}#}>j}%h%J!s+V!)OlC!u+(cbU>{ z_?jTjA)^oB%eC-o5K(3mV^wd=^mdqDUt|3E&yZgb&-+pE@(CWQ;aC;NUg9f9{&S4F zCr?6`YN3g0XyRpPsuo(Th8E8ey7NUCHr1_#kMOU-$k&TU0TWFf1!`!jil)BB-#6754syKQoUNdLsTogB#?r}6( z6{k+cMf3I@7u_=NJ>Y1jDngXI=AHX4S~BNvax_*I$4|vM^ZJ5|u9>%%IJ#UFubhfE V%^SBH=ofL}{2~fWYFDAO{{YVKyM9VCkd?--#an7ewzFzEvTwi@ z?Zkt(kHB+L=hkz6xAt#|^Lh=Y$==}iZjN)*UoVG8nE+{N zA~`jnuRzl@ON;?SQ>8VSTuCnEj9Nu=t*n|9(`^n@Az?)jDVV`GcRh4k2D^hNyMx^q zeOzr$g*KMCenu$CgkhOlfF76q@jIY1ou@QU(#vE9R7hi!EOY&Yp^2rxIrI*1l|Uzj zrM9Mk1f5uiN)@k3G@W1Rom9!GnC;laT(Ws0T|dqKon!fAkJX0{@@vW=2gwapvTQdK z33Yf-;X%1Y&oW31&?2Ad#2t363cZ?nu6U6_Ek@m?CX|n<(v+)(e2D#f*uvBbt{B@^ zsm$yj^gGoWV_c<-ReoKn!bPH`1Da~{cyxBej)$ki(X;XJIeRr6ou9lstvc*pP*!yt z(4W%geMKtEOp)hGJF%*b=C-V`A`@1C(Zsz})j65h^`X{ESKUz|t(Zd-eMWl2AsTac zWOTaopE6hB{z|k%?E0ELXM7yG?Rx?CF(F;Nj=L@AFutTsp#CjL*pq+3orQtTEL#WFv(DbL4g*Xx=Ha8Wa?3}({||88}8nZ@B7}nclUF# zXac#+i!TnJDggYEI@e3PGA@6V#S_9(jmwIs?#i0&N3sMrBRK~$f!wjH2WPO`l&*Uu1pe>D$>uNDnqZ3dZ(G8T^u5> zGG}n4^ff%m%m8Y91b7w#qFLG=_@>qYUn+Z`17|B{SetcL4xvQ%8}#QLQsJm z#yJ%M^NH)XP{>fgS;+RIgLy&^T%V2zu0uuj&$QZRS6iqqEmRjiorOvHz~v&uQct+d z$F9I0>rlSio!xyzQHuu-3T)@dZc*g=BD6hE>bU~hA-dwA1kQCS+K)mMrWEQ^Zif=( zFp`px(>~@khxrF?s}|bg2)Aut>T&$vF&ymQ#%b|taDoU8s80l9e%BawGgK?s!sYLJC+?PO+l-b({v(a4NZQR8V8qNC~TboH9zaJ=03PaF!s{B6V zvk;3Y2pE^cN=n1q;wW@IH*~4s@-u@ZztK+f4bB-)3eCt5-8LP1KY+{hE#K7zcWVE0 z!il~zn%5d1mzwF96hFzWuRsWY2cNwL`PZP-*Fa@D2IXF9{Fl 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 0000000000000000000000000000000000000000..c16fa083d4aacb5fc343499dee896eaef916c8c3 GIT binary patch literal 5260 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 F{tIB6Sc?Dv literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1b1477304c70f0fbd63990a1a6cb90d2002e167f GIT binary patch literal 6225 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 A1ONa4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..306860fb1904a3c739afd286f9382fc44292d989 GIT binary patch literal 893 zcmb_Z!EO^V5VgIV?RJy2w{rD0Qk$whAP#K>Dk18DQl%A$m5{YvPg1uVdu4kYWsmd^ zxT1Uj2R?$|*efS~p#q9`164V3!IsA}p7+M%XVvXS$c^aByUWiWLSO6P;Rs#u)GhPD z8IEGaF((<0akG<{a4+@%U;1&2`*Bcq-FYipnTBzQ5rohJ?}o&|8zkF4g4KBsqk8fn z4&EY(X9xm4gov*|>m%af!sDxt5RbrfQG4Mzzf=1Q;=FEyX-;qOd)LP)>aCT7!%Tp* zG?APd(AS`8nkB}7p{dduOs*ssazd@5xmH%47vn7sV2UjDTl#sv^B$Ce;9v*~DD2e42u%7>-U3*>G??7(F{1JZG;5qtl~T$5or% zOUtTG1NxKNxX(ysnJMyIX(wB?-n^C-R%F5oFq*hOtJ+7?IzP}_>8dj-q!m+WqEAS# zJ49n{UmBfm|2M&_aBnH!0d_Z-U1xk8y6JlX_AwzHyoNh2CU?F^aD<8TB0_$K9{CaZ K_r3S#&AciW zO(2(faQEJC3IPA6&dJh_jiVRRcnS!x0D&aqK?@GfjFlmZrBHZJKaqSstt?S3%}5nl zFW1s79fAy?I?Yn$d1lD7G^+8lGHQ|>&HfCCzN-+UBSB(PMf&+&W#AQ3Z#Ofriv#8{ z@*EB`J%&4(vw#}s0M9}|G)vn6Pik%Oqp|_oaPm~AzUaBs50UK?Bq)DCIdVK(2r7`n zIHw|DK5_jf3K(imYb*QMmO{=YRw3+Iqnd;2dNtl$kTrNT^^9h&v z*cI4gZOYd>lk3|QHF@Bmz;^E2O^RG!gtq6&e6B!th>kTVfm0oZHlq-Qsf7BJ+o5DR zjHDvuv>x!9!~89`SqoX@Y-8bYN_`yK;y(TgfP>BRIBlaE>>xq|>Jx#O-!TTgxomze zfz{3wTJxwaD2jq6w`JKK7V%;CEk+`brejI7q=L>x#5f|X?f;kiW!sTRXE{_%hm3h* zc5X7CPnKwpGJ$5!hm!F zl|Laq3$ciTfN?ptq%^oKjzZUSLzfCJzconm%dHfzbIy2DXheSKw&=k76&$6n`r(G) zPVIl2b)wG>CpXVWW%l%&ikEWhDGmnO z@!0gUo3UBzXEVn4GJWxKe0ep#v=*B;`#HsEz@83@i*SE-zZ$PeVxw=U#w~cHXnCy% ww5--sWR3T|9MDJK0SG6$<7a+XD{0#kAH;~f!7rtS5pXb)TliKvs6e@Y1mO%+T>t<8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..84b6dceff196a2ed2f5bea635d42e2a2b45414e3 GIT binary patch literal 5304 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d29391ba320bc1c31ec30a05a4e3e5924aeac5dd GIT binary patch literal 6730 zcma)A&2QVt6{l#)l17$4XeUwZ#BsEn#Ia-9k(1qI^C?@5qg_W<7ufDUheU9V)j2-(I>NLJy3UJ(yIfdKg6raLZd{ut4&xh?k zX!|z%l)qem5>rv zh!T0;Z=r0~@XnOWv0KkOjm;5QH*geV2lnm#DFe@a7PdJG8NzlmO<~BGKAeOY}mmZt1{lmYj_ss z@&p(=%6Jpb8eLytdl#^49^Q2ab9wIpcEiKF>0mDJ7GSqMygLr& z^6mnb^6-`%%;hZumiF*O2XlG%0JCD;^#}4S%;l{BcHc8!#=%_P`+$Am;jKED%XvPtx;VzsWMg9HB&2Tm8q@9qeoPqDrpK$yu@6*=xY;&8YNw= zo0<;2A-5;hHLZe>-l&;sl}fu*H&m^Lkg-?Y(khZ7oAQ=yP=JD44Yi_B{Uuh*muK+- zm7HsHPe)7DU46c4l#O}0yqjRk7|H+~1h10_VWcOJ9$z5gRCzb288oQ~Vd8rd^G*4t zec0TK@vIpo0dt$0a;y)#@yjFvBQ|J7E2-w5KnTBU55l%R3fiAO?F^}rAsDhGney(6-gj*(#| zDwSvjmxC!fI!uvR_aM2OVJUQ+4C+)aZA*%3m~yQ|nOZ*7-ojzQ3XSzM?Ez#eNoP5V%vJkc9l}(I@mEM>vGi)-X;CJa-~5H*izqgBzYm>_%;D1 z=piRI7*nj7`rh~)iF9>^2e%v*74`4B9MK?{4V z);ks*ApR;uJ{uT7lb5twjg}xonDs&kLEy+0^(jDTu8=TD`m5Tm<2_vck=&@5%s?Rl zoJ6ZsFVk9SPclrcUWburrjQUP{yIc1GfyNr*1PpiOiz>0ZYNe_VHg!j9`EHjkvvO+ zTULn12qTX75_{qo+|~(AsVdiF5f~5_PLLrR1f#H+8DIj4_KIwn64iB0k3|uSON4-f z(2@{am`17ICyH^hP1W+Y$1zycWHU@KZP zpg1zcdGx1lhj*0KFf8@jqelj{g4`DuZA))xPiUokoq$`4S<|v~0H=X6za?*}6=$XR zckO9%6}g7$PLqDDu~cXI8A-+tZP#`!Fl(A#l`AZFk79zmt!(z8OLSRLp+d3B7k}j= z1D4e+EB1u-#jda(Oimoq#2QkB$Vav77;*+w4fN8R)rKQtn}P~Z`>o=80}d8lgCoLJ zp(qAkR}>v5CvpzcDvFoMkO%s|h4H`MhzF~GqlkwSW*40;9g6#evwJ^NfAWy2nA4n? z1Z%OG?Cl=5mK{}SSBwk;QsxbIbpppp6wI6>66I%oEK}T+U#~;LQC8OFx|{S63kEgt z?FKPcV9-zo{iFe)Aq1T+ZCfi0NStqJ!9! zWO^7YiPK+95A(t?GUCdmR5mRE+&A-s&YPWF?{eNG8MnFXxpb;1de)jCCp_{(HkB*f zUn|1GhX-e}%XtyBE~ZvNkr)~C49<%Mu?V_A=Cij<`^aE{)HU7O();3a(WA>aIc`ta zkr#@oBG?C1IROspn5`pQ&16N;;jkc;7gNK1@NRU>GvZr$shCa?-I+!k7%s({n%gQyM#ey*DY&Vx*%Zd47ro&Pd z_?Xom#+iWQM3VXi!0I1RHqwz(aSjAnFx-|trf&% z`ehz(Qbj}u5Zw^L4w>S^m)}GD`-n3gmQqF7%tQ2-`0^hp`zOjQ9oEo?YdL%t)Zf9! zCiVC5u!KEkQ18Ru)d_rj01s>G{Cc(siNdBH`YEBS3&Yul)UPe)eEZXDaz%%Ia_x}R zxwN+@;ku>b$p~*4rUn{y?ou1pmA~De)~d2vYxn2YmsT^&?O>X+YY=P+)|8a3_DD)m z3RG9E+tWR@Lfa%Q7MAmw9AtGnSgyePV8wtn?bhTHeld7p)$3GE5o?WV+W8h?v@Lm2 zt9OAw>kKg3g0x-`rDaHYdzAu2AD$*uku>XK-5!+;M=1v!=?39^(M=fG#e;M5bM9-f z5cjW`p_k@+K44%8N!0h+p__1-Y*grNJq<+sf@?_N##enlpYLx2obUWs+|{qRJNDnT z7B~Nb8$RIXo7{ZMKX%}sX!<7({PCtg-tq@p{(k#;^6$~0kAF4BMc#Tr#t%rmN#b9U zx1R-H5pMAOi|FZt=(%R}+?UY{2hr=z==E0|cjM!ae4y{3^pWow`BOyr;q_;s7I)!U z=zzP>(mqxwUY`?ad!4o1J?${n^bX zAKT|=S{G-X&U9;T9y+(1{P;e9sWqE$Iy0@yY<5SFWRD(9M@J8)qtydFj*#6>;VhO- zZnbXSaeFt8T<;QUd8^5v+~;RoGpME2x!AG6sV0AVpPz3fn3h&=u4B82CVy(5PqgMP z<7!s#QtK!yU%igZvrRs}&tGXRzU%gqN6u;a>hdk{V zc!MNfAPDpkM7#~H&xnUN9$$Nacm$q9og2^j-P*q)&g(Ut=JpzYbakAf{(3n&%mheF z6UnIoeF>VTSz-(rnkucqj+H82i)cE()H`!CGds!blI7D~R;L~0SCm5zk{haIxz@ZT)WLp*2jvz$ z$sjR6i+rXNm*z?pdR5I_@gjq|%{oghC?8R!DOU@5pZjkag{fz=QfynLGPAee?^JEZ zxJntT{5n*Hi$qIDG}Y$OKN~wPW>CXcMa-3bj&eETz)cSi~;KvfY`+)y>W_v)kB! zAfCN@(B2g<{s9W@*~_X2;}${jB-rot$J_{Gw;p&zVH2*d6&=YKrDUx&b@~c z0N)emU@7~??i*n|1sE6rL!5G<0eff4NMXs42)w5rQ2v}o8q0>FC5*J2X{d$@K?)F+ zq>1z@)yEkMQP?SQtK$qwKLc3Zl(5zkL97!=_}NXV=j9S_Gu5;6J>-7m73?cLh8w9< zfM}-yOGAJaLsF*<@xT;%+@YT7aMPtt!j@a3 z%XbN?Gv7wOW#6~z1UViLEY}r!4o6mi_BkkqW39_*H4IRYP>4sE6$q3~aY90NW1UqR zyv{3Deckj|&zgyB+225z_{75;QLm--ayfMBQVh$jF|_0oixU+3b!G{f4H~k3H4TbG zmn3Z_DhZ)=9#MucZF+xW|1Bfr$!z+J@qkj7PhJ|$#u?MZ)HcmnPC~KTw3sJ=JTA`G zmS)V^>O!?Pyb!&!!<2r#u zI1}F##M!w<60b5wS)8kdUf?uH&wB}WlULds;>@o6cXqKl*`LG|8x-i|8}YYd>qrp7 zZ(x20WOqQJqkzd8p#;HSdy`@-;}T>IDwanX@wIPyh4|9R-#>+8{#+aHHkBDvI6 zfn55m7u&kHeLb}`5UDe5eZDI}?K=FLI~o@GjMBis~jx}V6Cgk+D*7CDOsfhl(f>3M2=sb3{VGu0tiRiLnlATrKIeU XJw6~hcwU$u0NVqZ>EqIN35xwU*M=-_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6582c0dc0dd71a5f241e48f0d47fd3bcc65c2995 GIT binary patch literal 2304 zcma)7&2Ji45Z~ovfydW+F%C9~Lr|r}J>bMuE^Sn0fptMPA6md^k*cSK_w25zq3 zAK#yUp5nN_nX>wcg7Ol+&JNFUgzIyJCjo;W1^Pjr$w81q{jev8Ko zFwiL8Pw*U#TqSAbJawKX5fUXaf=K*Y06cajUUU5o8fL#&5=mV1{S4jwk?Ut^;%bX# zX!be)c8RNdG((c0L3A2C&pAJn6wTmmlBQWAZgY5tY|N!RkwD6L*ydcylPpME9(Hdo zw9>Ar_Om`i!G3(`XmE6k-lNp(2(-FtNT@EdAUTboc)7~9sw8d^qS zFg-n`c424{TDoCn-Q!(Es57+n@x-!0OD{D%wk(4|_SBr{BZ^;B+tDqPLC(c!U>TTb z6K$Y56rjjpsvCsbHv-V!JYq{27MHs$4o9!;{gHF#><={)4>gKuRH0Q`Cmi@oa*{JrtkQ zvEnhdcBKyQ)GDxn*6WmK8ejaKpN00zBxO*#Qj?oa!2he+v^q{}qP3yL;=U50q88S=Ijm zBmIV1KJZ^GXndgiU0czm02pRr`55J_IqFsYEwlXdZyej98N4=HoGbyBXu_OPi7hBeR(2V(ltZkgQ} z4eB`B8I?_Yrcah`H-gm9>3J7L%2gj9guLDB}FQataG&U@aP=c-T1grUv~Q8jo|Tu!O=1-hV>8 bzy}sj=v@@0!a^XyrvMjt^uZ^^hyMK^StW;k literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..658adc2d93ae51ba1cc061eda3cc737946f2b438 GIT binary patch literal 2944 zcma)7-%s0C6ux$TINXpB40IGI(6kh2JD{|cN})dzVwR{7P!ebtY4V)h3o}aM$PSg@ zr94jBAFyeE!6shzvOi$2-5yBGL}aNC(;g=^e++V{Aom+yl3Zbz*Ly!Or8F4MMX7wy@DIzRr}^d(>r~qT_glN zPZQ%1^=wX^UT@mQE(y~v`Lex2I%p4xFjsc!M>-v>-{FaXb+z!i9n9tR0CuT`_lbkK zyk5XAxA6KL%;oh1HqgSm;$SXs5U`;Z-mrr?yzTIIc>LF`-6LSF1Hm2d~5R)bz-F$E}(N45jeyLBEjSXY6s8r;lq7%8Q=t`N^s6Jus z&3FUv6kmWSwlT2@CKmlpz>)b_dLHyvY&mfb`wF^JA-&u%Hx`~An{fCOty~+3Gky|% z&2i#G?5%BAX)kXDk6s`+2lOPzVq+K=zfvj{T9Q@L>$zC^WDD$pA)>7{r2mia4BIo_{9gm@j8y&m+7T#yBZumZYr3 z@kGaQ@I;OkkEyN9r(i9U05$Mdv+<_KMStd+sqHd}UASXD8B3?7MLCs_^HOFRVj$@$ za5*hStc{$)a?h7xBi+z1Zg4J{k)(*HMRH+zH52i+f~hqr;%mv1O}&WtTQsxMnv{+R z(P?Yj&btvY_NQYxref7=EtScw#uj0lZP=GxWOd}DuW-OFJ5!Cb0@{FlX9*%NVev!s z{?ybn5IKWS-v^vOi{D|mN9@6!AL^u6mm=PH0Q+U@+ldRPJ{ZR&VC##M%IGwgnSXFq+pT9{5BO|v1<$y$STz)CC=Tat28MtuzvLH!<% z5p(d&q7B3|v2;TH3rOk_V_vZr92pzPeZC^8A{;O(96=wIY&J(Kp}uC!-#*SvrD2N; zv3W`Tk1-MK)$Fr#xykL&jNC27d{1%A{a+#t6w$T?L&TN*2@-HE}}9&Sz39 zx#f&0Y?QQuQZgW?Z4JiRiLpctL)oCQ3X!0h63$s3h3l`@z9FKXky~X-wU9y~)OtIP-4R`P&Elt~b|s9Anmm{$XQg&h~!i9~}3izzzKtl>L8axB$`s literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6c6ac91b84cabb2253f4c2d0ea9c5efaecdd1ed2 GIT binary patch literal 827 zcma)3!EV$r5Vf7`HqGv~w{rD$rFN_KfH<@js4P`4OSN5rh=eRRo-Qtl9obG-&5>R? zaYg+AK7-%Ll@q^E0maEeRc_eIWS-}}@yu8c1~Kw0{`&UfV}Q`N20UDm2T%QHKY1fi zf&>;M!wGKxBq1V50>Bqx(h*_Otp@(Qlda@Y5@7@pbRf7UN%u8Uy%53b)exg*i*C|= zgA`sM=$a#l#R_yjBN5#MV)YRcF$5m}*Kvx5Yt{HLlb{^s zN>B^t5)4f6}w}&QGcW3luXSDNT zi0h4WX{=*DpOsoMX<4QhU}n{D_73RW6e%ru`kv2$N@X3-GM`Uc${qcqp|^Omn$WY- zQP)U7f#J@xQY{)0P4i2$o!hzHPPt+!H-Z)1@EjZ%R-Em!=9W=$MFkX~gr!xO_oMbJ*L`Fp%>~Q_ur~Y+l*?d*m7ED zc5i>!t5+BkI%TX5n@}AsxKW;H>y4x7$ss!$PsY<{v+;BGYCJtXemSXo?0#~r?zg1B zp^f{9(vI1(D75iYsn^?XS?OdZodj!$f0DX)oHy~YG1}DqX{nsdp+#SiS@(?A+-_Qv zZvXE*>u7H|odfI>*j;Zz9QhpvT^wRU`gjfZJtlWyKyZwS_hLeRMFII4hvY|i7vB8_ D^soH; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f4df27da0732ff297f762c8eb3418325b6fa5361 GIT binary patch literal 1026 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 bWz$dY)ht@b7qWBQ0`LNd{_kgj%l-MkiuMEz literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ce35b3280c0a389073f371cb36bae11508ec0950 GIT binary patch literal 4553 zcmaKvTWA~E8GvVG*^-Yf`*>`}*Eo)|j%+2hWm|Tf?rxj$Xp{`GG|J4#Netyw_DsSU zU5q3zOTqTFb zn9(`ke>wm8&t?AkFcLuw{Gq4c*!yslVg5)v?qAqxeD);3Fz+)uv(4zN9w@NIz;*{q z+a1sjZg*PkAhf%-Lsq*J+TrbPtKC&V#h&e6mI*LMXm8wg?*9Uv^m}e=Z^~`?zj{ay z>)kridmeY$)biTBH|^`Vt;bGddjQ7me3i!DtZU={>b;LyySF_E_CubxVd(YlUG_EI zmN94yKZZQfKP#%41`ZNah2XBEdK17VPwAcYn6Ebl?A$56X^;7O z=Yh?f(!1a>U+*sD71567IdR>Lig&)dHU#tN0WItvRizsvRJI%uP*N9OXYo|v|t5P{aZI2#s4Se zx~bU$HJkY{i^8g2PF34362wnTeUzPM=OSDb1$L{W0ciIvPb_#=3qrYA%$Ia^ z-`F+pnMQ>>k2=kgZhULB!_wtibmbk1O1L@HT|L+#wp!3q8*Wom4;;;26r{5s?W8fp z{=%Z}tX|%LTu3@Mfkv`PnQZW?NTejmWz?)FNGVB7Vum||1{9DkdE(`G93KWz zieqwtJB!9VftnKpQB{c|s#!Usiujw{AnFIPBJxRi+{%fnhOap`4zyMM=wxmRjr%r^ z*pKoiq-^qKc}uj{xpQdZC5@J-1)5V(5bp?LR+Hq6AMqg@`w1MtOtGfc;5y-k5YD7E zB4s2^;y0yjDRUjtI);WRg?Wk^;j>v-Z=U*rMdwf-6$x@CBMKT-55TfVD9b8hmRBh1 zpqqsvRg<$>I4MDzC<^4Ps93J}P25Sd?ijWS-j031A_OryOHa=r)f412?4r|W&+ZODwA6d_nxZ#RlINdVShj7ngG!)}DY+r~d+AzW*kb_OhO@37pY9fl2QFu9DI zn0MLOZ^J0$UOjyXNtvX0hir=q-KV2yFsIUe&u@tYlEf3ZDBuKMgH#V?Wfigyt0bpV zG~pmx$M0Q(&D}35imbrAlOkD*$FJb;Jz-zMD=GX-z$0)JmK?0#Uu3AF7!s9PMHiITrIi=m^VWBFX4BdvTz2+TnP`?!ltvYVFv3f_}lFYNwHlafxiO^ z6L3hKglx)kmSo}evKCLR;eUV-R>uydRdf~q3q+IXj6aDM!wvjzkitH2^59M-1yixj zZsjE5rgbP`zgt^wExyX*CQKB9X^~n1)2XcD?}F(N>bGBWbkmb1>!mg4(q-#hQVNOj z=!!>|@w*;PcocGEQTz2~`(39;B2N8M>iS)$em&~?U5|rbkGg)>cJHkf>RGBTnIibXvb=q^VDa8`m{rJAo%p4deHI7(>N3K zLV-P$%n5Kc+z{dZ+7Nj-3Q}6c?@z~m0GFV=;}t3N{hQl>tD4>`ap#5XfhD-^+BBBo zVr~N~_OefUVZS*$b3OD17&J6OymtKOJ)X&Qu7u(Kvmv=ncMi!Ku)4G%xdtIDoer7|X` zD(wobr9t~twcQ*&HO_)rsu`71zEBBKgD`Pw@#fT4zF`(6Z||h^psgJnkXr%f+A(%Hf@IP z7s@;NLKVhzTT>hKLm+KbtNHr|U(&_WK{4rlVpN;9Tr2OlKu~iGs!fi-YmW%TA4P5U zlR`PK8#*cP*P!IA!opX*ph&fruhc-N1sNs%XUu1i4Caf;YGrrvrB4vL?-uqSG{bKc z%lbjV_*!KNl<8M+^@s3$-odi$r@ah&@ndH7W9IAb|9XR2e8%8oX0gsJHiARP!HIfs z;y5@{56(1#osD3Z`#k%3B*X@u4>3I#o}uw$G*d@2AEDO{L(dV@ckx-|>~Una9+~|p z5pI>0Y7b}afM*jm;35vG> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fbb6d757b363bbf987856d0db0517de985bf2f47 GIT binary patch literal 805 zcmaJ;!EVz)5ZzrnuInW2EnJslq&87{KpfhtsD!GQQl$|HOK9bIr(uh;YwfNZ)i>xL za7Fz9K7-%zl@q^E0cGt#RW9tx<9Rdhjc3NH+l`Pr(bu;ZA3TJ<)!^X?9XN20`Q!{o zG2)n$49B>6lbCQX_5fe_af|zLPa<0(}G#UxU_X#KRkpuRlUO0?(oLjpzJM?cWgRbsJ6-y~ZD1A7`k)QH~EY0n*Y$ za%wg{(2`hm}!3@5;@z6;bj0R6fgVBpV zu6pM}8_QfhBa~#quuLsMPs{%F9niVXQ<^8~`(zGONMn;MbM=IwiKTxu^cHWGhjdn0 zYHJBd(1~@dRPkCw)5WD8F3j8v(?qHqB+F+ztS&vsuP6r>;f5+%t~cKkYJac7gK~?W zW{?=5MLySwD|Mv`z3OP8c#%OpYF(ukluxMAl&htD$o;pK!qod&DYmUrnc3azx2rYA zxJntT{5n;I%S1~@G*$0ta(c*)#wX*+v+4Lbdo`Y%9lt!O+U$N^tm-tRzp37RMk>op zk>^UgRaBeJw5+fq6IOuH#Qle=eY~jiW3830I+H?Lv494BL3-0M8gu*D=ydqsC060? wY9;&FsmzWuJ`UaWy#V`|kPhC!9f!%C?-3kf;=G8EU!g~SMn3uB-}!gH0fd3{3;+NC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3e3ef290011894f6e1f93870a887fae8aaa17328 GIT binary patch literal 1004 zcmaJ&|Xy zCSYb~fbJj`{s55LnZ>|BFhT+`u_bgtOq`Rpz`_l8@5lFj@7=q5lh5lwEPdzp`U44o zZ;7+Nls#kjl`tLy3=DuFPPx#4gEM8Muw+OCuB-c$Kc|t#vY}`RBkg8ds-Z%V0z@Tg zBE3irafU(^c0xpToFVBa0IORP);c1Ibs`BryCn_0T;gq|26ldc+{?UxL#2msGj#$G z?Id7n2(V%(E8tP34em)RpbaMvb!t`DAzpwi4X{BVUD_sWxih(Z zhoC0&ZRA__U8_luP&UO03E8a;R%!7j zuh^DHed3w^>S;5PF8iAZ6A{52QLm#7>N#}&d<@H-DO7if#R&@iCbNXpCJouJqZY-X zOOmT5DhZ);7Ey*UZF_%X|7|7Y$$W;4>3~v~&z+yl#u?MZ)HcmnPExVjwwNb?JT6{t z)T`#@+G4G-uw1)n-mEolTv=L-v*y9L%s4jy?Wc+d8IJ}g4}G69F^ae_xUCrmj_U*t z;Y@s05NEHnl6;LZ%Hmuj^a7_v2Htb9o4nMovA2a|_jBFDM@66~%W&eeeD>q`nU~k13%A~nuS9aG zuL8OBQ7?9N5q&woJrb$auD;ZlpjL-pa$`|(DjJ)9c0JN7{d7t@CFJw7(d<%mt{&;v z`x!}Vz@7^7Rk%I3U5@Gk*ywArb_4E8N>=FsC9U)%QR8PX1JuzU0K$px`0;mgDQSD+ X7atKfcv@H(0Xrj^g(K2V35xwUgXb^L literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e4c264ed3951f348c67d69618a467b22291c46e6 GIT binary patch literal 1388 zcmaJ=&u`*J6t)f6Fc2^%G$gy)WZOeGJrHTzV^x(OPKBBP!jOZd)yOatWMq5Lj9F=O zs@i{q9DDDN?XlOMdiJ#^{)O$O&jtcYR-KW@-}~mxdoypoVFw3Df`;# zzh%Mr7C!M83`DXeA```veZ@}6F-`$a+nEHX0jsu_;0)lbolCIlAwO>yWQl6a1FFr6 zvl7urmgEQ``Fj=k+)}@n>@v;7|D{0+_p)7~hd)Vnl@^wt&|EvMhrlegge}zXQXIJ@poRmJN=FZlsh3MXLHU%Heue` zcyW76`PqaKT3^9|rE@t#go+7w=ON=@m39`MGv>t z89RakrHvP^M<`#Z5O(!49x^aQ(%s3#f6Fg?F%=h-bH;I!KG~C4$Z$9R<6z|C#r1gc zrwx>~LML2^{@8eh&@NU@K9gS+Umw;VeCuYMs0GI~@+bwXYYMpJzW6~wx!shCl-~Ec zWRy{%)#x@y7B&Zi-a!AmWSpQXP`Gu~F}uSxjhhY2)PK<-_h(P$dVRBt+vZKDW#Y?5 z=gMsBzv{**I(g!GIcQv)xI4Pmf76YZ==l=|%j~wdF@FEJ%fM{iY+c^#hK};ry>H2U>tMi!6 zVQbhJj;wxjH3R$nd8A=X*aYLKG-Yh+(RvaegvOP4K_2e}#Zxh(?=gwQczR7gk{M6r zqvJWRuiOD!BHwdvPcOlLlC6}&8Bh)sn)}Ygea!1O>g2x7#Hwq86zd8DNwJnu8lyzhAC8UGp# zLO>t*%V*Uu1OWbIf%ABW;ANWw;A?IzK$hHbAqy57(BW`J=lPEWPOBWt-Bi%*lA{3pwT=|gHiLRIg zM;f|jYGqB0l$xifM30m;Ok#bOH_BNM6QitG8m6YxXnGu>hNh{E(wlX&QX}Y`=tf1W zGs?KAl{6J$#Z*d)K`14ZniUljz0c0B|9~}9w6VbDV!3v%$7{x!5huE?>Buf`ugPfA zbAYcANIo_>#wM>Opf_(SX4A-)q%8=?xfZwxw-tIEj*YZ2nH5UX&y@VoMQ1PYRN~ui z@z6IO@<2ZGu?VCY7)U14d&hYs=W>~x*q)Xa;T)w<^6)@T7X~!iOXOwody&>&yEB%_ z%4w96PY#kY+D{xD$|>;&QCfkEca;0N#F32B$4BCiqVxbR-x1{Hbjr!`)2k&pIeFqp zo{5qO{YRNXCO2IEvw7(r3{Nm9FBecz&Sk`(=Sv=YTd?8Ud&S&@fRT@W^Ede2{l@YS0&ou2!B{4fcHJZ+@f#pYIL+NhD zh!c!$G2Qlwh;G?TXGWsxTUN6cZqL$_aimKB;9I(7-XhiG&iK14hLrIp|pROo3 z@bSsO#+E0g)s#xz@??+q4iA!+kRqo_Q#I))(V7UCteFJHd7@Wno=bxFD=M*IO3o*9 z2U)t^mT;zOB}Fx8O$XiIW4GXl7>0625_K%so3+&Fei_zayr4BENGcmnFf0ij=Vg?n zR&uK3t!boc3MLq74U=9!!;872Two35wM5x8Nn+4Dj!Yp_k*Yx_|7XV~q>T z`?RLvrb^z|cc`BI6^(yrbK~MT?ymsgHeP^tUVsmr_uU?dw}IFN@eYXh+~KZ!spDSi zx>q~y)t-B*=k_>lXrmo`qZ?fB1lOMjH@m^z zPH^`IfG6jWt~&HNY{5VL(pS4JZx3v?yj`%_0h`~z?>@Q)@BNQyznyMpKe+~<_5`Ub ztp6sg_g2VZZztB;`toUqU%2MCd(nrZV5_&yf+rn*{+f^WwswZW@BHSCA9y!( HX7m3579;H& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4302bdf216f4a3e22c7887915b70fa5cfceb64a6 GIT binary patch literal 811 zcmah`%}(4f5VoCc2-#h@ZLdD`vJxzUL)8PIR&+sJC|XdfMyjk34_ni$9oY^max8s< zUMn7;kI~o2wWq!UYD?{Gi|DDgGMS(6o6LMs{eFVnNxr_l_!uDc%?A%p?1E?RF`t~_ zC`BA|Qs5M~Z;}!oqygZIFzxU#jq1M3cZ#K1oW>YI3>^rrNgBOIvKt~;z8YfWFOSmb z4U%|{Akqhr@FnPcMm)X=`0^vf69`wtM}7F z8a*x`GeE0ysx#N^N>zF>)J*ZJ0Kcv8;tBN=sx;+lE+1n5ou)Egq6K5!Dpi>6ong0G zVvMVtu_pATCZ1$;si%qo&L56=O}W1^q1z?kiGR zW~#DO+Nsg3w%fAGib7ZcMicidn(pDu*T-I%rZ=gi6*Fj~FG#OCL~Cvz8=Y_bPsb+S wUZ}E*9now#6XMw2Foh($ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..907fb6630ca56779c525c3d34b94b96097a9f5cb GIT binary patch literal 1010 zcmah{&ubGw6rRa$Hk(aiD5U`-)vg*D5BIFi`WHOwmZ|fy4hJ~b{m_M zh)3@pw0Fgee}F=J_Oj~1bc>*P@|M_(cycCb{c&;*Gw;Xueecbid0QwLKrCbX_T5Jc z0AHnZu(W++@3khYP`@m!LzEKD8CN>rIj^p?$h0pv;P zQhjN`x>Q@N)#sOM7p$AL`i;xi7L%Oydtz3S?}H9frQaE!h8B;4fHEyaP2P15&XfIazGKTmSzRR1@^M4uT9WR49BlKfQsjo7*h zgzyWvvI}y%pxD(wc`OE{PI35S@z}fKu?P9i;iDqZRaH3lNuAyqJ@e{neE!yl(Un*& z_jI6^cZ^cU5Ybn2TSKwF&@t*g1!ix+&-szKG!c(XKEECt)m}Q4JtgD|=i+nM;}AWvZ^5ODOyhJ0xhj|6;b0yHv{zH?*PKF&gk*?YFV~D Y_LC2Z8$2t{4}tBW%={5$y9~wt3!_ysUH||9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..35ad3673d9cb0beee885e019af4f8120e8818897 GIT binary patch literal 2497 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5452f55f7c3d81b3389f1335bd13b70a03d53b43 GIT binary patch literal 801 zcmaJ;!EV$r5Vf6bHqGv~w{pbSmD;V^1LDwDRAs4pS*mRXA`-IFc)GYGc4RwUH5VlM z2V7A6@7hs`N2czTLT`h(1B;}m`~1d z6eEr~$#9I@FNq2FVh`}8A9uJP2i3sccd~9C#vw)!LI=DX5(lr5?D+^*u7?;kn+I|5 z21z_e5a=U_co#aK5f5)YzVZn12t0>+x1RI+jekp=H)uKS?G66u>NrKi)oOg036Pd1 zl2Zfv3N%f##27F%Ra%27l;lFrs8zJk%Bu77Vw1y#kgybp6wK(mYY$DTV0-j*d$j#x zi0kzWp^arOpAkwjVOXZ-pwFt|**l;!U8J-~()Y;>sF21cS?2NyLlaB?Xy_f@s1E3P zX{l`_AVDYAu~Nkw5l!jbF446r^|GE^@iK#^(I!hHsGd-zDOYp(ko#{TrD>+KRBT$MGPAok?A2Yy zxJntT{U%h0^F&KWH1+z?^yH8ojVI&j!P)pZdo`Y(9>1K_J$64VR`*-d-_ZJfL@LWn zSrkgULDXw)x2&`x6IOuH#QlZ3cbqrzvDQl0{b?z!$e~4FkY00)*4#ZdI^Fuu2gPSLl(Skxzd33xDw&h=TH< literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eb6128c89e23caecac79e7e44a372790ca173fcc GIT binary patch literal 1000 zcmaJ{R2R1XBGnk!3YV&#Fo$nb>f_~1r~0&d+*-&eecKJ+k9RJV(Htr?>>|O z_?|fXOW8AaUJK(Xz`y_);*<*w*gsQ73QLAW;9Yf}^5-aG>-UuBXNT z(M|xCh5##uvI3q|Ti}7T0$OnVP`ftgI>ZZ*i?eea~tTn%2j_U*t z;Y@r~5N9tplX#6W%Hmu-^a7_z`rb>hlf2XJ2xoTnKhulV>A@^!*pR>{Ux~jFTUUY* zegjuNgY0Ke=qjK*8i7)$F!Zr7@~$xQDAzf1Ok8wj8IFFD&wLs_y?HgdaO=bHN+g$i zDv-;c^ioF`x0^Ft#Ymm&=+}A@)E3~^+~KG+5gndSZLY4!bJI&%v$ftxB{gfc2gxYd7GIq-2#YP|`|Q5;=Z#Ge8~s2_PKp3?F|lmy@zb XcX?5C@Vqcv1lz^T>``gE48{H%kjgG! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8bf5a309a3faccae78a534a0ba95c5058a304438 GIT binary patch literal 1460 zcmaJ=O>f&q5asHFlt{{w<=9r#)R7Goh!2$k_YxFAP}@=q195CfN`MkA7NmA9&|Pwc zUCMxb?WO-B^0AlxhyIPdCKmxc7idnMrDTcqp(HqX^JZsucKDW8Dz*WS{pZg&e^`d` zQ9IeE1kQKx$-lu6!!rmXIfkN~mq!}s0T;YtiVJ{E&q{F-aLFsD*kpF(crG%iHLFnT zX5*$xEK(w6ViV`y1in10-5cH})N@%q+VY;Hc@@Ch-VVfzIzHPa8~4a-(5Jr|UX!}B zFKB}{?{g4$XU}MZRKbI;X#VC)IVCmPz&3sJ=wc$rhj|z_rNs677?nWpu&m zjD1q;8~Quky?g!xjj8YxN?K#V6F%gu6-=(Lsb~#3q3t;g$2dgFCR7eZI8L|#ue$c| zm~*BnF^Q8fqWG2y8S+?DavBAkVd5u#;7baW6-+`#sF<5jcD}Do5sWhKo(-d0aTLjs zJQ6(M2_`i0L)K2af=p-V$9zmB9kPQEI>$Sia%oOK@so-CHun+*W;%;+?waczv!l|(c&i$?cX@g_ z=yV4s=Wp=E<>0c{@4g-Mev~Gsch=T|U!WsMjQd?w)- z4`}aHUx5oMr9Yx4G3myWsI!`>REzk4k254JTP8?V$M5=Ge1_M%^i>rz?h{HdA1844 zq}sxA)zpgI%#t$kMFNSLbu=cw8{%s?bZS2DOEElJ--^dtYmcX@^eWQ}}= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c3e20eb9ad25b20198bf6e2e879815983db7f20d GIT binary patch literal 1971 zcmaJ?O>g5w7#=%`o!W7mrXQQlmNuKEUkfCnKxxZ%(Y6UtX}hhG?n(rZ*c&iX#L^t1$;h3FfN2gJT58t78u4 zRG}qS6AWMh;+q*Sd2lD}Twr;qIq#w14tW@dV^DzNIp?xY*0E+b@bS?36{t>(W*8@)vp)|^zlljrhshMbQ zxK?;YX*farA}+@O6lrdS_g!ohVG^Zfe=@|%z{%TWGsLBQZo$g{rwBLg_+@==#^>(( z+^ipa-OsJz`*oAu$;k)b&ih_&enB^@*|pem9Yhgj5=<# zO&t?!BCzDPuB(*79nEaCkbH`;(a<$Y8QoS*S7oS}N=-2kq4-*-p+ba5^x2PSGou(; zY;ZPLZ=K>o%Q!I#Sg+}(43Vic)V$Lbi4^OKrniwMw`*&}9BYwgF*S_8NCM%t;i)m2 zJWrEnZUuF&GP)%)Gr@+F+of~Q89^-5hKf!bWTjgRLqXC#cnV)&Y8t~XPKa4 zcvN&4Pwcc@KG-=pDwd8O?LC(F4~`DXmC}==^5+H>@CGyblFmC3_8R?3sL^+1SHuAivL9RraBroy&G@?1P2;3#F1A;u8{cAjWbz99%qA35>9AqqQvqfWk| znFwo&ipjSO*m3zvGWPs4MQso*p+hQE(oxw}+%s2jHdoYJN<*``!>2os_jYZ*h>n$x zYLX+7nlh}}fedhh=ANg6B*!&nDh&sCDx<^15_NKvb=}DkfJ|NN$wv@Y5N?px&Jg^h zp(0x-mMXjDy+bm2n?F(YnxY!y7#^D6p|8L`G7RMeJ$HmgOby%JKhoPFlDJ(3 z7GxCNGb@gZ0qfaULnpFX<) z+t-->`-lB<^#Xih@u?xd_$$9?E!^wfzhLLA&F$VwUtAopOBd|AwOtCVE$f{^Z{_*J k0h_sC-?rY$`^J51^FiRZVSTveS--LCmlGhqgy}!h{|wn7F#rGn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1a572e9344b607ab571ef9d65f39a643e10ca070 GIT binary patch literal 815 zcmah`L2uJA6twP$`iyvZLO;0mO4A3H<>BQB$QiWc%HCMdIpq{s`QWMHYRB6i9Lf*&z+fQL?kyeZyt5jz8 z4|?rtjWMoL#wx!~RpBDh(g97ibv!;jV#mYLaQt*Ke8yf4$7d%mM^&5MX~wEf1NxiV zy30s6tts+cX{SfE*-Xm{D>7jP7){*IsM;s8dj>q!n{$qAy5qIz(e`9~zww z{;y;e?yqz?#ExqAobhq!w(kYl$Aon72JX0+EPapQ2ovW;g!~FU@-yp(1h>-O3s z34rg3v$vF8WBau*o&XFCfFVw~(15)&Wu&lVNCd8_dz3$?k;by2XbB_jW*VxYLXZMP zC21nPO!aYwLKJpX-0C<((oX?aHzcgJL=fvl5`K0=>Up`u+er27d=I%Bc?tVU58-<1 zC?MJ~z|s(4#ZZ>P<4P0UmzF^jjvZ)K=Uj()0kS-dIAQk)L$+&iPB^kDV}$$E!;V)+ z0YyHe0bL1KCooxcJklpPHsR$TNw)cxGFd(|S)RNw2IJzY!+2l{J?>D?bhzo#CSgmh zv86i%)tPT2-?Hyob%Gp^2bSv!J%=MJK)W0i!|~Q7v=Rm=NGQZ3%nAg`rZ^!XyK#?I z8obUcjIPkY#3ZmB*YsCTnu&hdUq_hu#KRm>uch^pI&@|_hUL~cT5yTQ2@3r>vxMF{ z4Ozde2F0OEl717FgwQ&TC_|Vwy}z;l))Vq%NPWgcK&i{8rpK~z#xyatO*590P^>mB z<_RE=ix+DPbLPeBe6=>aR6TFrtkxDUUz?A!=KjpgIM)O1g^K$TPjt&e-=|E>BQEs1 zHN(JhoxmZSiT?`X?BzxhuQEnioU4Ui;510jdjYnS=h`{WnO*t+5My4NW}19_f{CI;EWu^7*sT z*=y06g-E~N%}81ec2tm`gPT*Ew6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1d6abeb9b84366e010fe8c0845f1380de8de7513 GIT binary patch literal 230 zcmd1j<>g`k0>_21SvElWF^Gc<7=auIATH(s5-AK(3@MCJj44dP44TYUa?ZYCK?8T2io(ct-1*tiid8rCTrFkWpxv6?ptOk09dIo-)Ot;wM<5TjJivnR%Hd@$q^EmA5!-a`RJ4b5iX^MJV3^Dh7^V@Ks)Rgp8 z1xHVXg3N-{oXosbg`(2DlFZyxy((4%JwrVMKTW1v?D6p_`N{F|x47fu%TkMqGxPJ} z<5x0#1{wZKrno3sKRLIoC>~@~d}5}4e0*kJW=VX!UP0w84x8Nkl+v73yCQa=Vvuu+ Yd4a?SW=2NFn+z%s_(U7Ii&%jo0B~qClK=n! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d713bf307aba5789fa423bee47684dcc080b1e71 GIT binary patch literal 6782 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..36a232b04f7cd3d36ec23ef2654ea7c42b9826c5 GIT binary patch literal 4120 zcmbVP&2JmW72hxJE|(NVO4NtH5;{R!6AdL7Jrqa`BZeg>LL9}2R2yY#Y*#xYYU%x; zXNR)5SQbv?9`zRl0VJT807Y;83v%hT*SYrOdw~K*Tlc+LQeOoMv?O-s&6}M!Gwnbx^WFKV z*=@>kIb4VqyNf_yB~>!>%IGeUYotyZKrNH&^7N9J+OWYh#VC`JmHdT}$W`q4h? zNb`9CnrFb0p0NDF1Gd|mFbGY#uo-YQCo~kxmno_MoJJKW8?tOw{_GmZ|I=Dx+ z08NX=-cCr#nSW%JP6ssE3F$r!g|V^u;0su0P3)Xii&^lv$m4hjP7k;+`e_ogWl$fm zEPpQve44T)7z=9<4#6yGwiJSEvS-J>K zRjsKt%`P6b4t-5ERQ3~?V(&vEjLjsbz6#{@hqMWg^xa!PfWJzp;{X`MI5NPukW(tW zmXwi0(7&9`%c!IRdYnFDQT zlfUy1IVTJ--u|09`ps7WJ5O7A3zZ~vfW`}&s(=7|q^5okdgIEWa;)aci8@xZ-*N3w zfr!>~ji?a!I>fX=G=SihTt6bD6?Qa1QmH>9*O&UWNk1!l6Qo&E+Aj)eybkgjhw9jp zaxIXX@wwc}@s#t2`q(D+afJW^y;5>}4blbdQ06A73%dqpe4~~bIzPKE2Or2N z&9o0%7mTjKBt9uZJBHZT5DrgG3FOLSIN);)gW-D>x2a6s+YJa{n6`3T7y+lzC7%@K zJ3SiHg8{qy8H&dAp}lM>*;3>M@yeYr@x3s;yK?$g_Wm?M$-LPL(jrrTbQT>Swz7qb zFHy|9AGQ##cOe(P##_Ap2AXUZFq;PvZB1TzjcRDWCt*T362B19Qiiy|ENtq~h*CPB zekw`>2B875EHN3bDvW0V1a}D%#*m5%WE7tT1H@dw^dyKyJsl1P3FDNwp|?ZBRM>?< zaBb{GR9HKn{|pdz`s%MRpldid&=<2gxFT2+K=Hu^%K7VMX1A+nuiq z7?ybqbu0ZrkZQ=!uX|FUZs=mI~7*VAois(@9d z>cAfS^d)Ze@iBKb0JP@V%*uJ`*vb*s>=S^5C(83C z1aNtDYZ}1Mn=hbvId*dGMBRWO{}iHztNjY_swXnlKq+KNjv+ZZ6i(XM&Is_T050~f z2Gk5BBdu8fYi(Q}J1|q;SJ>0sno5m-21Qa0Q`OPwPPuhFds6IJ&n=j7Q)=x_+(>q zTUgL>=#*6+G!0G}=o?wc%fvVOYg7;%!}@ zgOG&agYc}swe_kx2c_4dYHLjys@mUev!LvMHX;0^L}9xwhPdlO?J@(B8P+9jqo`ew zF1T2UF~k9wLf~O5r&FuQoh+904Pm)1NqpB8j_d9Y;lqWxE4yH1sEgX zh^rT-lO6b|+V^&XFyJEz_eIru6HkqZb*eqYPQ2;XD23Fzeh~0;mc#0rVcjR}JAlK( mklw}QmLkhgs+tWJ!au;LwyagnI#7=8G^&ns%YMfJApJL|U0=Td literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..119a9432ac41f8e626b5cb1fe7320440c081dbb4 GIT binary patch literal 7163 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 ak)F?4*Nw@ayqag*vs6384AeI55dR6#nSYf4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4a56ec0bab73aee4b4563775053ca3d4e1d63204 GIT binary patch literal 11614 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 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 0000000000000000000000000000000000000000..0e42883b9ce292a6ae72e7716818ce44b612222f GIT binary patch literal 815 zcmaiyOK#gR5QZf`R?}2o!`OvJO)XDNt14^d7lD zc0EDw!fjW*LRaP3R+|6;N3mMQr~Y$}}^q2z3_Iz9g7J16tN#vgW} zGnKNX)=Jwq=b|iy+$`m-&`JWA=2NOgdX;lK;oBmWjPidmJILKpx-Vcyj|63@NuTZ* zzBTjE_7+94FKfdk+wjHdLG@7`{)bD+3MHgD=y zrG>e(y^m^L-+akSRq3owjxH-{1k`f8Hdz2kT>K+28tp|t@;YRN1 z2r)#&h!J9pm>{Nz7NU&^5FLk$`AgfOl&OqT8({mm-WS?k`pgL%0GcZIo}RPwed-@exLHs^Tt`+cPYra;g+$1ZlZg^(OB!gSM1LbZ$G!;pyv!@%`?iD4J3MmYw a$HkrPo;aUX&fnonE3ttsZ`OF*4dXvE(BQlP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dc90f95d095241765f0e12177cbf2437363c4d09 GIT binary patch literal 853 zcmaiy&2AGh5XZfnWH(=%Y(7AuLeOgtbYB1=MO6-!+A6dX2TRD>?wZtPZLhrED&fXM zRNT4o1Uv*!fUlgmA#$k12~Uy*DpkUVpXTrJ%=~5hOAxe?U7v4WU*Q@;-#}E$-39AQ z8+<@1!YFl8H*>g)U0%aAUdMIbzzyzUkNennAYaRxyoFo5joZ9~JG_g#Jir0(;U4ef zJ`Zuo2YA4Tc*sY1#K(B-pan|nA07N))tGqi9?i>(j3xVt6mQs~RIz4K>`&)M2j3m* zWa63r;U;w~<8-E!RA%pl6$KNEnYd<33QmQ76)P5BWYi4lGLJ<{=--^_WVV!S2bl05 zLDE>qcXu>h>PgqMrg^>{Yr3;^L8nJM)dsbTdoz*dk_o-j{n?TxCHSunpUU*l;W^7` z#zdvVO3xLOis_YUy_Dxw=QCQ!QYF>l;H(sy@k$QPwPML`?1`O`O@^vvZ%f%%MG78S zbpTyJ0H_0cfIc7u3;;vG2rvdT03N^xG%Zdi4~dy4>J08xcCL_eR5I_`U4 H?b(Xo1i9=+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e49df8afa8fb4b613e6ec2be077cc6480ccb847d GIT binary patch literal 4684 zcmai2-E-8|6_>8GyV|ue#()Ea1~q(?C3v0mlRCt(N!&ImxE^3=y1@}*?_FDum_YVg&?Y}e_|CtzE)-*l(4+yTcG_G^z>s{7jI?;x2v<&q$TPB{SU+R`yWyLG` zm9EvY6kYbK-CCF&PPK3x;$;~8FkqPO-dss?KI_Zpw#(~mShEe`A$ ztvQVM@fnO~#2m&4s|G*I>--Qu{HWAA#0@_GFP)#`$M}nn%B{o2_!a&VKY{UlF+R^< z<|i?JfiLjW{LCY>b!0>1Kl``dI?9dfTD{S4{-t^KitUFk_ks=E4Y)0WEl-9)R|K&w z9`@WI^1{IGgwhs#Lo77kvD;zL@ir3a#^5x*0#I6A&!*fU2xIjwTE)0>)$R4LC`fb7 zjW_W$n!)y0dM;jV_Ihurh5Bbdz@AHsi)SybT@2i=Sbo2`&~g|0&U!(-y6{_P{apQ2 z|I)2fw?6zav9p(!mKI2ei;kQ4@$&m03^2QX>DHi4x{m)ynaeQu*Rvpm*3w~e#&wvO z0UjgUpR0HU_^e17%KqhRt``YzKiCw3?M^~r^RS&L+}a8Y`;M@saJfy`5t3Z+#_Cfu zvmA%c={Q--jXhu_`GvN2kpxGK`$V z?HCaV*?g3hwp>3EvV<2|rPyHiNa1tuN3lF_A`1O2VaJ;SM~>k_`DyHq47+>k?CDq>a%C@9#nRmpG6FK)JOxGPZQqMxJSf4% zNzVuGffsLvNo;o$N?HJgw)28s5;xRA#~j>_k~>lC#R(-mn+xB&Bar>bGPzqt1+dZU z_S!agp>x~r38`RKi_zL0FK~Uz7^EC6?7qw){(M7}54+ZB{Qo>!11l!i|f5*mnJBxiOkHaRLfJ z3Se~t%0(M=g35q&*Gcz(jC7YTfd7=qqj<;{=|)GM(2Z=Ybh`jfM2pXBKcd&(l<(<9 ziQyN7EajcQl3vzGubY~#-De!Z`cz+4n#mIo`>smWiyI;UE^>JZ+=!NBA0N^Br+*Nu z;hcIkV__r@VOCc2kaMHhMZ9MQf;gLTiZ$|xmgEdsiL4i8CY>rPDL=0xb%CW;$-z>v zsjPYgjF)glM9dwiv6@bQWtg*O%z>KMO(fzTd=4 z+`eweS4dQu!peacJcm;iXM!`ErP}J``#S{GGTug1Hft42AuC@cIWrayV|g0n1Y+bJ zQ~(uX?}6x}4n5XW?Fqv9&suQU1crCn_HkVs7`oQCV)UV@@rCv!+rje_#@WF5$atVX z&`_&bj0zQ z@{bUYs6Swp8GROi*2fI)Yw6Rv{54)@CMx3Q?mP~FULcGb)Qev6?g{D<)+a1Lsc6pv z)DMu)?g0J1m9o3&4LJK;9~iL33$O*-J_$>hu*4LhoU$)8Zl)&eVZt6J>`@w+ADLti zSkg=l;9ve6mb{LpBR00*9GhMFq1kn-zf2A2^UM|OHRh_9pR`-YLub`5C2?or0y=OD z9YaVZr~h&iP}gOn^r#;7`_t?`Ba@Of`};fS(hzl!CR5t=k|cl4sx;RjJ7kqVT_3Y8 zK@Xwd;E{*TR1p|tjoi?`BHmtaD^(0ljm6L{@S~S;vnmXBozWRx&lqFhl8squ2IF~U zZ}|qqzLS@fd8r5te`H=#+Mtwl4a6wCQYMe5>@gA(g3U;c&$KNhKA4puz|i1*seQ?jvy&XW?3+%b%oh(R)Vp zVv4GT78heQn?&pKWD3oZl+9GfSxfm@>*ZB>39Dz7qM^$)c^PBb#0?!oX+wrdFEh}u zsO}um`zEnzj(i)ptb+Cx4Qi|$D7UjRaYc%r@3zH<5Q-?+^Qvvxoa3TWTYu>g8xQ$|ym4dY+Ge`j&jg>uF1HB#`cduxo6?*8lV<(@kj`t3&p&XZDLpiRb5_cb0aUv6^u!^%-#tfUD zRuZT=^x35p$jc2mt||*ES;?Pb2qCpQ`D>mH4-=)_f!=|ys?quX!>2k7zDlI0pU+oe zdExndwf|^76|ymqkI5Olpl{bsf{!YEyGn{Pd6M2TDYE_T(D(6SiY7N|xOdulcT%Cy zN9&wtj&i(B{~b^taPsFgv#g44lvm&iHF8Kf@?Bc_3h`?<>5FeH$B-Fw6sdmX04hAm z7#TjGUEhapqZBi$)z-!QvwN9(m58=CwP|KCDjW1?v1wK_$Q#XPv!z*+m8ZXDCuhE4 Ov$zh;d{a9-ee{3oh@vV0 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d1437f69701fb3bbc595254a73be51d37aef9131 GIT binary patch literal 6086 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 Czf`9H literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..df658b15d2843e8313a8d3d17a00dcb642e05370 GIT binary patch literal 18420 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7aee7410f0d31a84e09796ec05e55557d729affd GIT binary patch literal 1362 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 AO8@`> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f533d6d33299c3937e8ddb7a675a40f95f509088 GIT binary patch literal 4839 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 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 0000000000000000000000000000000000000000..26156704f26c73543625b751457e4e9036b6ab6b GIT binary patch literal 491 zcmY*Wy-ve05VrGMsvsd?Ve5u6(7Zr}Kou*h$bupv%h=bDHjb-vS^+NtGaIjzm5Eni z!cGK&llNt0g7m!v%lMv%!TuW@bT>f=DX8nkujGsPp~WS3wc< zeo%)hDk2pZu^JU4N=hQbH(Kn;C?{$Be42yeO&DcA^#FcaKb(=kC1=(Y}-twl*7N}QtGLs2AQV$Gd35-{nLGhmuKU)lH j1}0;yYO0p8>E@{$&6Jkg8qVDd8uzmSNxXy})5QA*EWV0B literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..37efdbfc44728edc7f64923f7e88a0d08b683ac6 GIT binary patch literal 468 zcmX|7Jx{|h6tweMii#?j5KANml%dTZsE|;^ScJ$H30cN=qtH06>@-r@_!VVl)w}py8j1&281gB literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1d96842d6438d0efc3090af96a600e5535a57220 GIT binary patch literal 7476 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%0Di_)~%e@kin_LZj}7$|@1>cVaR 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 Sa}(fw|8OnYt9@fd4dfSmVU?Bu literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..091c4d6a659aa6bec6f76e9934271092828c4154 GIT binary patch literal 306 zcmY+8Jx&8L5QS}LArO&{iiWh?#1}vjC8CHTrD7#CT5PjFu{|r>%inD%xze^&T!AX~ z3IrKxo;CB0{2tEdGZJg{e&0UF_cIOui^btOw!erXNkJr)OjE-OMv0tMw3x_jOY)<) z%Qd=CKVWa=qSIw~L}wNPtyLnR^UE(9f5_=@zWISSA#}GM?Y6NymweWFHcSB|3V`9X zju_g=N0vaTHNXdP$MJTQ?>kizC4|5+^DgXcg literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..79d4d5a1292b82df231204d44b572d56075820a5 GIT binary patch literal 279 zcmX@j%ge<81a=E!v&w<=V-N=hn4pZ$Qb5LZh7^V#wg}W z7ERVFdB6Okl8mJM(!3Off}F&XwEUu6h2;Faywv29{GwvLmyAFYG#PJ+1VAM{N=gd6 ziu3b=GxM^Gn1Pahnk={2(&6bAsfzfRZy literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..41372dd0e162720c381402d24f781a8c00c0eb4c GIT binary patch literal 7929 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 JoLnY1`o9>ru>2Xl9n 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 VOp6S_bB)B(k~Fn^NiQ$se*poURrLS> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eec5234d92c46b8f07f839d88ab4f42a77575e6c GIT binary patch literal 625 zcmZvZyN(kv6ox&M%q^K*5EPVCAx*)iM+lJ}7AXzI?iR=rvbYhqXg=a9IFgxD#y7haG^?EstQ+Xf+xrOv7D+J*J_4mYL4e>ffotdB9?tm@RH>> zs4c#|y7BZ*@-+B{4z&7hgk+e>pF%5F#f?O=7N-f_=+FN_ZJ^KmV?^0Q98dsCKn<7yMt~~fL;EBYgfP<+5{lkLLnBXG&=YbF>D)>9f0`f? zGR2voryNK3vlEC@DGB+3>{BoW{C(kUzv|V&63(`qP_a5X)84xkAwmmJ$i@C4l&_Ux UgXC}E$2j=RFmYfQfb7h%PMC5g~Vv^MPD{b<%(Nr-t@EhSOZrGKj;cB4bxD9Y9HP;XtoToHTH807{JnPrvbTDkH5 zaW0s#ZnD?MtZsyLgA=y3M;l+F=(^#&pV!Z literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f552f76a4eba0b482c3642efd138ab4d9d0c3806 GIT binary patch literal 2208 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cd0997950137e0686990a363d01cf3312fd99487 GIT binary patch literal 2841 zcma)8%Z?m16s<=;s%m;>dR|Gyvqac5A{~hhAdSLIAVCpQ3&F7IC@Oi%?wNwBa$~#a zC3ayOet<9FNB9Vf6|C|L5ev?Bb$Vt<2T?8i+P-ev_uPA~Emv0)58t1^Klxrqp7$4Z zu6`|a_B_wmf1ts=vB!NLlztVA1E1Pq8CI=vt7?ziuHPyL8Dl1 z^zb>sH1O>Tt7~c8EIkNp%$Z?%$+WiZNg+y(DdB#7{@kbOf$cr0bIApNRIwvr6DJ-j zR*3^BI};@@L~Yx;lt$;Y!__ZDXOEmQL_>IE9}Wq=gBwK1gWX1kbJ?29He?6pb?wT1 zIjeb5AKjN{I9n;1vF)?0DzM*qBhX{ep~ucfgO-i5tj5u@%qCe@Nj@v7-OI8cXRMrS z*0byi0$KK(r*?2WSG{;&tNd`gSOhhf7B*bTU2dR^%hkN2%6Rn8oP;#rd>0$ z@4N9=%vOpkUw(I$f72Qn2tUwzgZcla7~>wq8z1bw@{fZ!V!@Go}E zY+8!Pi{TH`;c)oGCj2E-THV4yY=kHfC5&(2>J{}wC_*c%v;};PnnGjr(8fTj)&+QT zfMDu!F<~H=Z7DHTwwudp$`tUdgki**i;YV*yacDUA zxWn}+`e;0Xq>R^tq%5{J?~`k2yg!#4bNwdd7RI+BcgAgg1MQpCh8u6$wS~X-OQ8(= z6-j14HKLkIg_N~$UaG*{)EqOD78*H@@$+;dRmxH>Piw@sU{zYwMyLtP#V|=yeEaEQ z^(IKLp3YTqD0B)QU_M;01W%oBhUw*WcNp49RE05uyX9nEo~LYrJ+Q4t!W5HP*&OVc zVZ}o&)CpD-wVWLtr@YXnsB_b-uCbd2Ez`nix0<8r(Tuh?MJ3YHqJ}9r#8*O0k>g-2 z3_g{Y2|LvS;f8I~QW${1PTfKajC6V^08K6xvU3V>u;UbI0-e*s9LpJQg*>O!U|0!L za@4dDt5ZhcyVXR^rxHLqbdlf`aHhx|I_;c7F7bgvwo7gyE~N-AbsN>idc#AwWX`~j zOHQ~YjrEe3?Sk&FLFZqDwB0P@GS9Lm6@E;sEaT$`P~1V$ z@1aV(RuILM^OygSB58h(a_tMz+xw5}Fhu63tI^#C;7$kaF1QncJF$%q#F=?OmwMxt zmOkYP!MEuio%@a$R0~`ep<91%dQCa>+#idDsjPAaD>EzLB7T06yibp5=-U@sJ_5rn;-9y8EkF z^Y8U~#lZ9J-yS`dCByhP4Nf0BW6YB^(=fg^n9((u$#S6?<+?eO_ zun-lyMesH&gr%t5Ek~7Z#WZpTw^)%CpO>EHGOIb`p25mr7_4$S^K7(kw|a$k7235+ z+O;dR>(Fjo(ym{j-Gp}Ql6KU(^hZw6uY5KzPu>RewZV<9 zi6F@R5w7QXmU{=Yn#+$lYs}fi4GNuNT756+$1I39-b;3{zcBGsYVWvFfHeo%3nLFQ zjJ%!Ij+K^NH^%l{H?6pCl(2qCe9d(~?|b1$(sbQ3D7fxljT74Lj~^{pT<< z*i9?gc`sl*_PO3`nqNN zi;vO@I|WpVpTbaDLZtB!5M`j&g1E;8Vd_RQ1<0A0!f*A1vb9drp)}6YfCX>E^^&X{Sr}#>@yn&4gD#T)u$OQ*or#T`7 zy1WYl2elRstc77?V*jIId-lMZJt`Qp*Y+NKl|QiNjtj=E+r!q>u-=9bhwazCQ72cPkK=0a9Fp^X?4lZ=z-M)UxGbEB({(^gM}dtw?NL3a z!&`F|@*k^0TGLz|Tw^?^y{o0I@!Z5%udTUBd@-fD>8{OYFq)+i%IsX)7(0JCm1V^CMIBL^VP9Ra3JvMN9iEuoJCEGF5iF=U~#EOfa=kvu%#lf@e zjIC!82Yb>N!8(@?QV;rwBf*)Y9lhwhRM(rKm3R~=#hEsj#Noj4dRPO?N>q8?>xbFK zHa%Eems~u7H6ADZjZKFIQU$TEGV2nnNmK$SP*Pjdm0_b#%c~&b&UO&vDA>gBIp0E2 z#$j>b?c`;baU@6VVp%8T3Ltpf+Rzvy>8x`=<0k?w#Q`{2aSN3RgY7_VCVjLr-=}J% zybwq6L1`f`c02-KTjMd?N&uur7u8k(XG()bEgj{fuENO2v`B8D#9V+w1?9wUGfQf? zWR%VYT@TR#rJrme^Yn{8&SROhycsChMS_D;JXc zS2RcD4t~-D`O-MHigxL!Z8Y1+?mBfLo#(B|{f~#W`2%ZysLQjSgwE=)sY~?a%z-sC zZ0n*uJ%>6loVs>kT^nYl;^zx{D=(@)JG2(E%%?Sd{Kii{6Enj>-_KS@Oq^{`Ukzdvlv@7;ZRg8*vJ?5#X~>(H9X4oljvJ}kj- z#bN27S+T?ImyNsjpx7y;rT0-i*7_pO&RLNhM@(Xh#Bv|0>(asX#&>^`#V`we^{EbI z+}R$D!nwE!ToN%@y`o<~!0s*{{~pZ|`2;^n*ibv5*wC@?%iDYRUes?LTDP;Kljdq? zLcEJNU9@_Ag*t(dB!8#X@uiYWLA_SDp|qd|R5nC`bnJ+;z!Uh#!4PF4`nII64b(n# z^lE?TU0v92T6!nD+Py12hBDn%CkUBBqB1K^o=+8k#K-1in&6pzD z-K8A~In_BfKp!DF=1JOT*lSODg&x{|vyx*e33@1rogMDXE@x-Hncbq@4jnuPf84wO zMc_Dp(#P!M<72~dT=gp$?(`k*axZg-Uf*+xuVuBN-}i_0e%*D1&*`l{_P%m>gEt>J zym{;$yZy$TB;X5>ELcf%P7?A(NEWRmn3J@48Ad9icOR6Hzs{;+WXVKj=s_N zc@sJT_yuexG%K6&P{{Z$OecdAG9oKtZM?w)w2|==^vUR%Gd`oMG7~>p@io@%cJG;1 zQi#DIO;V9-wcV+kAc}G<7)2(GqG7?SjQCa*{anS_RI(gJGaiT{xr9x~_8j@^o8Epg z6uk%fpo;Uo9$bE3z_q<(cqlE21m6=;oc5GV<_BKHp=aaLEl0A6#Q`%%wLn5A@B)|K z71v5$aqkQCaj4I_w-5IMALLpeJ-L+n26{r2b!)$F`4D4^{RVGgybXS--{i~SS8g~W z;A?!9pMB&5qZ3q}GvVD~yeGCKyf^;+R?bA86dW)r&f_t4QP4QW$ey$^+Q0knEpfArKHetnzI8jTDRH& zw~h&-Oce|Sz!ifkn_44T66b7JOd!vTqg-hz;$hdWW$L>lEtK(TzNw|THua;F>wVMM z7wO)4b8b_!S~&Kuj}=c#$A2 zR%wX-E#D!+Kul1|q7+h(^AiM&CCTx@)T#VZh?oo>=je*>cl1g`duJ-I-fYU>(zkKJEfEAe$5l8cb|Pa01h zbs18eEmLVz2gjaTx0+3jvqi2$v&m}*j{FFk$ndFVXf(7*_~Tv`y@Blu~a8`DL3VZWCxuNlg-!+{g^&aZL^$B#H$dzk7f%ECF1Jz*je%< z$r7}=hhVuuu>2a#d;pqyvW)~RAqnElMJYgX zoHqlO;wFO&O|Hs9YKFpNFFoCv6r_wZiz*)CUJywf3?@kSakh(0O+wn9%BH2gRwhGR z(Y9>uGi{zKYfE0GdbHhXzSiHTb?`I%@3pSJ4#90SMP=01ys4{ObjFi(8-5dZ)H literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4911ffe513decbee104c3e70d881ad3757bf2b24 GIT binary patch literal 3793 zcmb^!O>Z05ad){(Qv47l%9dl-9~(AXlc{I?jPbuwL&zN-HgQ z*|(3fNe8u1*+7y@?UUqFF1`fzA?+dg2`$hI0}=?k@WDY-6uC)NfdV~s<}E3aG}T`E zh@G7`AMef1o0&KKb2^<6U@ZUXgQd6Pg78r$R{)-Fc(kdA}&$P3c3J){#LoqRz$ zzK3)Qq|+}*kJLuBO#Sp#>M_ssoD(joGHN(#nOL@=nw|?Yllf}ZCzfivzHJe;VpoV` zyTs3ZhaaSgLDFztj~UGNTwjxd5zAnP=@`BrC?%UX)_XvVmZ(=JZlL&%$9%Kn3jT_O z6LI@IAnO7ViXymMx(-ez5lfl{D#s!_n7o529E%oZD+aQ0oYh9OL@;*FC=+VT!3Ut( zCYs`S1`DLpZFp*dWWw95eJ0e$S+(MjUvc*9YA%=iAV`@WDV1!~CNA>}ni9ly-Gv2p zJxJ(!*|Vw+;wfGKb=7b>oQ$sb_(9j{7#Kq{0Jh-~{ym>x^vWcEn=MxjcOefxUn1b! zyjfnM9O%SaAi81aeQNd-b`B!X4<=Ws(Ggr60R*n#zYO5M@I0Pgn`;k%S7uh{8`-Ir zJVW<)b%wit1=#)WFM|6au0zMmfQ9ZxEXo}p1B^S}8YwDp1hSRj^kgw=rC^rkv(aMA z%D`+4XC!WAt?_&EPBvf+P6Ua$vavueQt)#9k7r$#xTa@8&{Yg`nK0F(szpMIje_dU zYNMIb?US|)VFGUG6 zY*$?-wG&(#Qh^y5(5mmL03ggssp@pBp{i-P>P-?Rh~?dKeMX5<&hfPZV&vSy5?dxEX&Mhj` z4-5vk`k`7ttpse*iFZ>3Uv~$VB*-#x`zL^`3m*zgT~5z?sgs92B%ymqmbfBb6Evyt zHVOqo5CL~m6>v?W7<_@^c((}sOble7I3*updGKN#fdi^immUXkEkVcOMY9Md00c2zw>%Sa zZy%CyT{?we8kzA5^(ut28hs6En3-WppqccgF2E)ID*)~bTe7UgH&a4#Z0+amZQim51MlBxsv{eBr0dNBSLg^ z3$bu7+{T1wXLP(SZodbl!Sv(BTzVRLg8qa7NhXtMCk>;3#dw0n0UO!D!W2wf$$N4! zR!eE4L8=q)-23%6&O&7`S19p$Rp;phL2K1i=BXz2d|x%xieoU0NcE;ybuEL|au=b} z%al|oJqVW2LkNxn2ok&~>8u9M#Z#n*5gY-~7lbkp;A6$@!w`<^0_%K|fZ^D=GeKf! zFqG$`du!3ua7jPOVqIBF?7Hf=V&4u}CI<%Nw?7hhb~pq(43S()52&I}ht#*bw8!qF zFX}VA9{A|)?rVJ(57WU&@6(|fuy}~16wj47c>yk0eTKCb3gih=uCSWQ%WM5)zwB-_s$C7EM+Yjd?Jj(W(O1v) zYx3IMqg3&Ho7pP_Iv++B2(9P)<-;QWUlgz&1c!ejU#i-?4?HPQpnFg%0E5X%H%(1U*7%w-LFs2e|37k zb-KU>f7#4lYoxAyo0@#U*6R=Jk59EyGtVZDY*?QzJz9D?)tbn)!wSA}_LJXyi)&wQ zW($o}Aym2Z@Xq7&t&|q3EI(R?ohI_Aa_8ea8^8GEE>|fuvsW6aE1QZic8pie0^BmM z!zTnXUUOP}pzPBVz@yl)hc3pLqd!J4P~(4yGz71}#5#Ytb?R)l%fCCDKs`g z++2hnmN<5SHfW7Jv6`fd0=o;`XeTY&xYOp+w>f$VbSm(0@#d* zqWBl#%-@9Le-~!9qJorsFuh^5#HoKtil}W00O01ArZ;iAHP*`?7td@806KiYwq| 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 0000000000000000000000000000000000000000..1d42b6b759bdc7840ed04bacb9aeb4213c226a34 GIT binary patch literal 1066 zcmZvb%Wl&^6o&2GoqLn?lD2@6*knPoVTll>q)>#KkhCmDLY8Y!Q@3+GW;_lpyawU{ z*zqPj!)#gc3apspG-;`@9IVnLadp+YK9B~=smWLXXBqz(_S4Cfjh2+%t`-;TOJ}w>EKnz~G$jcLS%jt?WMm2jczi_w_I36rWkOk|YE14%*UCP8?i3KO0PkM@en z8fdxi5}HueSPu6XkEyTf%fLVgowv4}?cDu+2#=aq1yv-d6w{M5U7{?;M)>EM!j3HmvYHAdGqj+0OZpts%$%$HTK zKVwPS>k56_43S6R9TQo^Dx!v{BN~V%qJZci)(}O+I--QI5O|Np3StA%MYIrY#3rJG zsA?Sc9;l+@_}p_GWqFIEyrtwuU0OOVc&FAfn<`W7nDY^j=)dYyRZA_ZM6boNZfGR9|?X=JsI~sn28ilD$Nc0 svGD#4cwErF;{;(K9cL%HQl+QB{e;nH_%Fn0Q_gsjc~q-fwplIz1%pK-{Qv*} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5c9c28b532eed1b0472238250e79a0b5ae52cfe5 GIT binary patch literal 1079 zcmZva%Wl&^6o&2G?A+5Ny(BF(2(ie5_5~19DJiXlnvkX}MnYC3oG6M z!JZv&g2WRrTUP8S6{^IFInJf1!i!I5j%UvK&p-3ESj=ha`|{z%%eN^_`;Lp*mzV~Z z?^Wt9N~8p3 zQicktzyeu-Dyc$^)Syo4@tzc0ge9^R$7!|>kz2Yp(wxjE zo!mY*+UA=_z37O!cIzl|nd5q+))C|OY0x_6qGfaM*d0Y;7`oiM?+ABHy%u#ww6(R@ z@?D=Y*Q3G44_%Gd&dciK&d%21(}A_O-#Z*wy>5RXYqMPUa4=71w>kLSqh2U0^KhGc zAw3JF*#S2QR6|+Vr9&2;^r_E<%6EOLlyD(Sv#3vlh=nrOjY2;P_d*IXb>MoZGIPKq zVbiuDvmGV(Swtf$%kyxX@qjwAG*5JdP<4y*^-jc?YBQ$dnDN&=t_(Vt*r?b z!eJP+b*0!FQ^tMT&d3zjko6~G=$||vGS`W{o7AuFbBA`$s2!o7lU%tswXq+0p$qEh zjl^VIVfq_e(so0nmE$6VNFvn3)5H>@geW5_hy{d!SVSx%OvDNzi@@hA@`x&;foLLX zh&o~wQ9u+G_S@^yuq=n$mL;?H>?;3Ja;+|I9XGtuYhF#}sB+ADg;(@n^|7kC7HQI{ z+3`fu5EI`f)5r70qg{`I;!*FAG0Xa_U8<$_=T0E(O&i9-in|ZpO`8k4X<4r4hL*MA zpG)%ra3^B)p}40`;1dtF6n-XkUH_)-UZo0p;!2z0B959S<`gavzpI+Qew8pVr|_%Z M%qQL%;;yRl7Y*wq{Qv*} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..340307334ae5ad8e6353f8a797f758fc3377140a GIT binary patch literal 6635 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f564e535f999880de36f35b3d0459703a559b768 GIT binary patch literal 8034 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..89b6801ec1c61ce3fc7f88b023e4fe55e4964fd6 GIT binary patch literal 7665 zcma)BTZ|i58J?TRH*^FnL zGvjR5+CbawE4=UkqEy=O)>oc*LP$u6H{N)dS0tpqAu5Cv1m*kBjJ@6)U~B&Q&;38= zzn_UFCTs=2OFw;o<)vea@@FcH{|YF)sVHjvHy})DDNJQrpmw#ErV_3Pde>+f5;uZE z*KC;*F9gM|)v_dR2BoguvL#*&%H2w<(w%5cbgQkZth0j2)+Dd8(uUfqT~?UQ${#DN z%%`}$VYH@EQ(+URnaFEqP*Y`-sF~#T4ZU>$%Lq zk2JI#WQWjlXhXwlhfzMvj-Y&mPw?6i1*4BFDeUMk)z(pF-B23G2Ctrv`XS@Y>2YzK zvnB4_?ejj5omD?scKH2X;CKAQiSBU`L>_Z)^JVXzAN57^b2`X|nwGsVj1n*LqcBFX zz&K_AGI1_m3p-a5F1#cXs3~|Y_TDVBv)8b_wa^ZK<;=)>wzvqeh zw1A=Ag$}JU{!>wSlUU{e;Yv#d+cc)Obf&cornd^rXql|gDl!urET+|~Abl~2RI~}j*;RM#2Y2kJdb?&5P zCZe7j1&qf@UI_Ur6->cbcqf@}SgGZ@q1WZEo7%41#pD6u71zDn_kz5o=(;TGpt^AW zovRn#PRkdsU%lpDedprbjfK20KX;1?=JmO6&0SddOc95%X$c;^GrJsh`Rx7VPTvcc zX0M5e){bX8-Frd;mp0&f{%kBd+bcSe;Ipz-^19hu6J^Xhg(t26Sc;(<8XnbBWy}6d zcnsb5X95^trlo8BEh0^t3wK+?)`R7<~1-7js8y zgtHh4DRG(lIg6ej^aYQb^Pd@eZEH_rpTe^Ad|?1s3dp3=zGR9g(F(FAM`T?HqL{P6 z+;z{#J5vs51oOisC{a0>S S=DheS=HHXhr_t~XHGYi%mHsD@ z6C)xo^@CvW`Z$T6vxvH!pneSXO_sUSZfEM)ZcFP$Ccx=Q8}Q#qBeI?wkw&G3hL)O( zA{y{8EyO{T#Jh;q(d|vLNh?5xjK2v7LRN{xDxFc}+sNOA{B7p%V*a-BcPW3{`MaFI zD|k;}zAE065UE;fOI^7E^BWJIyB7Ija@-G(Cq8tl1H%sE8%xoOLOv#5%~VIv)Ee5h zw9@Gd!NbIj{Q*y_%l^``yXs+d*AsUHJSX%>+VwtXf^=RyF6*)r<}9s9+X&@^*2d{H z^W%;u;8xrybfr|J#_cEyQZo~g`9@(csZJ+dcg&l(?w-=qz|u`TF)5*~7@DP7uvFXs z6;)8bU91{;{6C;|$SPtKH&zOZ@cvByh04AueR8p7@*;a#dSe6r)I#Z$^u<&vq4Wxy zVfBxxWTW(|^uAOoqaS%*qg6reQFaX21hB{0}%|4U^RAzJ;S~Z zY>GY08ld5I_B?yxW3x5Q&ax((Mav9(gPmX}fz?6PH`t574zQOX2ydpPk$!lW;UdpD zao2-Rc;tiqDA$eEW$5>oFVD2W@na|CaFUMS&AffnwjKO#hJk;FJEx_AI^E8vYD$S~ zzaY&3sr@P(Y3$sN5H93GFmk8lN12?fgW#awg)}xD=bRHp5Ue0GB(%J=EZ*&apGL&t z$I_bk@ctMtIDB%_7i4&Db680(=Nl3=+Vur#l-e{K%oMB_7HYSpv^|?k0(u5+Za(&7_1i38y5SmT-nC z5I}qWbK2J684mxQOWV-8INg4nfO(FWI05&7pB!kA9~vuxe-GfF%K3q4R{5lXA(Zy^91y4YjZjx zyR_I@j{FXfck%-Padz5oVS@N&0IO)Jnp#n-Y8~&Ys(qfLoc}cl~Q2#CE=f74VZky^u*q=mO(H{^ujHIw)_7!mk zEyah5cye7azzwFIg1>XY6+f-IhBX=@rRQm zXgiu5qaGXT22RdB3S8NIOzueAo34}jNyWck#f_xqRFD99^KSA{r24!;Z5O#=w z)By5ff>P7e^l&2*GN~-7-u3PasXL@fsf}AXe{s!48qhF=ba)*(2Gj7e>I09in? z-B+bLvbdk1Z+sd+`=F*<@WU18P!<20TK&@2>hR80)YJhD_26IYQ&sgK+A13Pa~mH0 z#9szmNju%|;*emyfb1u`{6Mbl2M*nE zB4-i)J)}gq8lig#dCXa^gXl@}oh-!eDqc6l6oOI$2blQe`Y zG*sJS<&F>|)t7kkEA)wpoy4{ZG2}~Poej1~ru<`^Dd>L*@pLf{`a}=oMqDC)vyDpw zRP5l=v*3~{h({9QthIz}035uuZ)5@0O@mIHkxr9GQiwCEa%-2JA-x@G^vipk|09{? zjU6hoOPPK;lED8bBoa)qh`bw8!}Dtne@jv(qyJ7n_ERzm&MJRh%i2TT89SWA{?w4dN@{FIPEtW0uYIcDP#B(uF+V78IU#3YlQd^WVo)Dz7}bjJV!AyE zrk$jCa)QZp4~~oxoXt8kJ@}BZb?qT2%oMK@pbHK`u0gy;;0yrFlEkTYh(9-mlOG(` zQwlP~>5dNGi5ycFbjewcj`g5W3_YYg)5zUx#$&xDEeH3yTa@Wl=8C_A0jY&=KKSO9 ztYzDIo~D*te33AhK%0Q{+qVhp5|DB#2ulc%1Z46-EZm`R;+v>@1CP`U`SqZz{YxuU z|EWD$Q8k$7;bZIhf69KMR<$SXV%3nNq4hLLrWsEWMt(or3!RdTpBu-rPaXKwwAjam z6FxNwvfOF869oaj3el&z?9yR!tfuMR?g)x$@fBWr$s|DrIz&-gxlX>Yn_r1Ynxz&=!inEs#!IoX4pnet=E(vR-XC~%yRT5 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ca2acbda5577f6946733cd2db2202917ec7c484e GIT binary patch literal 11981 zcmbt4ZEzdMb$b8~-vmGs1O<>Hj~}80i30Uy$rANpk)kA1Hl;{%95b;&2zL}g;e)
Mjexi8_IP&mYa4WM^58C5$v|v^NW^o%s$SmQ> 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 qq+GQ6IQBUrmd)#xNp4U%F1_yZn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eb8fc12fb5617fb9d5705ec3532007e9a0cb1c9e GIT binary patch literal 6623 zcmd5=U2Ggj9pAm%`|#cQ?)>f8PQ0<5##bD>p&$~+`A*tWf(g`A;zBsDZ^!ng`*EG! zi*ttyrV3IENOb_=NPtu%BwD3NJod3qyzp|)Me=o(ib^0s-yEDu&?kN~dwce|S0s31 z?3|M8pu%%9rZV*=7|e|`PNi`|0o7gq68#4Ct9e?(zJP=&0ZifTX?jX*Xa zvT{%l8d6p=LfH_nOM2LdWFx#B(xXOOwvCs=dd!Gt<3=KzFxs>2Mlze^?GZg?q_b&J z2nZypM(>H_nYN}9Sutgz zE|Fu3CKt2<(KVAy#$t2JL?-4Vjxw)%gsmg|hA;;_j$!m*RKIW7P#GaJpiuw%8J7}`2uBYk(FcqBKa&>9oEFSStb@nZsnI1bBV~)d0nyX>G{j`EzGms zI*OUcB4QsgnPo&bu9&9fO=M>RZkwt&3Loc&7B!-)Ac;7dL2{;zV*0B}p`e*dSE+TY zG>7eEBsX~8EV+?qiAkuIcO!KQH^RDKR47J;ibYNRf*ZZ6lypl`=iK-#S@A3Ry7Ksk zsTxWZcb-IHLvZS50#(-glbP4iQE<0j29 z<>nM^DsNFT<*RhEP;%RICRxcr3we^$RN8}K>}MyDtqBj~=^uaTjZ?3msz|-G3r%wn zQ5f1T6VnaZy5-nS%a=fcB<_r0;Tr-GvLbvb@J)D7HmC+*(ZLz?t5PMaJEk!QxAbpe<_(c;zoVrRNjMT&zrJl7)3`}&|LvATX z0rZMITQshguqmpPGP1&$D(W%r!OEh8?G}m)x@IpkewqW+m|MmsAO{2M<^0NoTF3HM zp(HOV<}nNFThW|l_SKAnWqS+pK`LK@;4o`Jp^la>>I#!5rj{+rnL#>~sK*RqSQO%7 zYczZWs!R90uFDJxfFVYKd*mzlUN11lsd_B%hXLLv-`fj#rkC-AMn08!CT$+T$lrO2wq`2oXZm0AVJ zw*UwQ^!hNb6dlWeTA1!im?EzL_&u_cT(EQvk^yT43Wmrqit|$e*){{;1#EX$9ALZv zotapPhq(}+jA1WLdb?_eU%HXH(%rCCP+l$)H)uQ5jobNUm`%=94Du1fH8-f4j+?AE z--KOZK38ztY^z8;IAUPuMr{HnK<(L2Vi+N>%Cm3*HtRpBOT9xyeJXfZ>iX$(7r_N5;TV23T*TkvWGMqDcF5ZaeGuNLhscr}8m zS4F48XM>e=`sLlPzk^pqhn;>rAq*2%#Y>Ho*QbF-`WiHGzZG-z`>i(y{nGAtRZtVo zLBGD*(7qqSi9$)pw9l3g?(q_UD1483Hh_T`1*b$0fkXLlv&(p7AVHj@69q$#Q|36} zRd<{wMkZMT8r&r10p9_dy7@fi(PqzSqG&J}ZT7&{Lcrsm{wlQX&n6t&MxHb|erNSmqk z@4`RVgi7-A=F^wny7=?yyZPTfUF|yhAaxWC|FW5TzdCqOEFWs%hs@^p1On9oVQ^g8 zbOMB#6lEOU*o%r@B$p|*D3#GbM_HC(*-@4qVc9q`S5mb+Ac_@(3WCu#7JBl5fZ&B2 z2({gKE@va2z$u^0QHEBKjARpi(e1zqs&N$cBY-Bcv^R;M+}m0-Uj#=Rw;{xgwN%$u zYWRL?_+jTz<={-U^K>O~x)x7u#Sd-9553uOKR&{H&Q&|lR}$yZ^Tz4dPv1R$KR(nn zBGvWAD?fULH=V3@PFE7sEtp)Wc3!L`E;f29@h9JMwx%v@PF;BK>-VRg;luY54Q|E< zEAgXmUD}$M*_@bp_vQN&=Qz>XYUjC1;@n?{d^5@^uWV-a+52yn!NlV ze)(bNU}dPjrH9evR&-!9I#5fbYl;4c3G8;Vnwa`HBDKYMu-s(X4Eeni0)@{yA%2NK z83@&|Zjjc~FNI$RUx3oem%c%8>X|%<<-1wfM zIC-aT9-p6|EC;4;;=;hpjGI_;>)Smo{ZJdS z!MOeDrEd$u3&86C)Cw?P+9?Sy)JDx)Ldm-l7Mtc%57T()>|fNb~T+6yI7EMr5py_?Q}$N#KMV|Ui)%Hl&CW^O_x!Uf!7e_zde~j=X_(;N=4%*Y ze;3)B@VHw@_t&}yYZG6pOq{(dZS{}e?;n5f+J{HKSUGxnUD`^I-cOIdcjm+K*|L2Q}f+Yt>?hT^*h&VgTq^clbeH+Fy$fHE7tmlz+T@7KJ~sonMWa3CN17YTnI}-PiEErsKUw-FxG1T>mHO1@CQIP0toLYIhYGrVe8Z;G2~BQmnf{mllv+p~gU_VbQpJV%EF{T%LO7tu%kFkt(xzd7V>6w`4(G2uFPi6!19r^y__64PqT{T}Zcbi!%CWCu zKlBtj+$f$p@MeHJ%xOkB##7!+(f8 z7&ze(pQs%g0fT|6H1dA;fwzu5=sw|%I8i%zc17@FA$qO{FQk7J?GSHM}d$ieVi1;(Z31f9|-*)2qS+M zj#q`_9|*mV5CCZQ?WU<84bf>mW#UPbG)*U|Jx%R6X;bHqIElyIHtBfmc*dDYlUCEz zrX{k!-*@)DfJ@s+f!TAtDW(!$qPgDk~xk66r z$;x1Ls4yh;RAsn2QW%kXy0WD@S{Rjjrn0rL72{_rV}&u5RRfi6)$zi3b$elZb)qm) z-BH+4oh(dNcNTV5cNKP3cNcb7_Z0S2Z!6q}`?D%n$yaYL++N*V*jv4$a7Xpd!kyAS zSh=fucj0c^dfHM$YWR|+hW&f|p*62?FIq;_7PM^fr`FuUeK;RgTXDYCzt6uPJ@%nx zOl?EUHvc~Kc)-u8ar|$;;^4}J+JP%O)|@rFFfCUmuh?kcsdk}#m$dK4`EIob=X=(i z!h@$Rb=yC;3vW`HW7br@@zy8umGbHN@Vw7*U%p&>U{Tj+{UFGfg5}!mysp>kOF@3I zyy#cTH9xPHYGJwRPk)a)Hf2WxrCO~Xmcnwq7EC$Ofb!=`OO;TSXTvC={Ig5`lot&? z!LgnVeO)?L@w-pMx{lKA3#s>%78lF4(-=3SN?~cXQVIf`c!z6CRs4>`*Pqw*SC)@2 zFZyV6pQtU*d=HpnO=Y9eLx-Pw;)Nr}i_gwH|HAR&^UobU9&K&4J@>-#c4OvXy}DSh z`L!?_X;%-{YoY&27^M$a%Rzt@MY(7EQYD-}>Mzzc+Mip*3OHr&Sh;p4N*=2(=~+J- zDr03uKfWozf-{HJY5%8}{3SmcZdVUh>VdDKq36!|dahDG|9rhto?VWz((f2x$fc$a z*UtHs`l3He0C)d!iI0;k@~HS$!3JqK$}YIdDR{~)B$QW3szf2BlGxf*lsjs+iX;0` zYF7LBRZ%(!OF9IQqZIbC#tNG10{18-HDAq4rK5DQSSwZiVlm1Vi`BYXs<56b7SAq~ zD)E)n!J~(tIDYt0ls&+ebE zSN;93glCpYwbT2tr3?OS80?>|p3_nkeZa6-D(?^UY;VJCUHki6h`6|{cVo~yaRfOO zY0INC3`_#-5LnJqF-mt?Rlm{bVWd^ZqNd{vwe*uS;d@X8zQ2 zKAiVA)5pv;Pv3#@H7BO;LJ_5kMOB|I7CS&NwSDX{je?ceJQQgQb7#vf=dK^XRnGNk zU0R%fZ>drSyxX_dK@D|oM3o>fQy z;sep>4CZ)P=FnclV=rQ5n06P)&zHk_fi`AbpOe~bi;Qtr3R{~}4yU6O*Ig{D$PGdr zC1y)Y0XPpg@o;Moqonr3C0&cW8L)y`SEg+W){{8!YX|qD(zKga(P}y6A!RsyJ&&>4%ADwSx@^htf#aD07PkH(gjG-Sz5ha2puo041xmFH~ zMeL#%_?5X%mup~u#iB637PbhE9Kh{w!4Ys~NjqupvKu=$gKoM#a?*_XW^|WlYu>Hj z%HnM(rji>_RLjotG0|W=fmy#&X-z_i_c7ir(|H&*E=#6EvKx0a+m1sHB_LR#ucTu&7;DvkCG+OrD- zg`CRbYyeksg+Vol`Vi{d)D|^*$tw)`!w{QW)z~Eu!g56AuvOb3SU?huClC1mHaL6v zoS&bq*MJK`;-`GbMNklsBVS(%7nj03<$%x<+sd_8)3hdxqk%vKLXrC)4S>&{_QUuj zz9Mp@b%iH-kT<$z2%_|CX|Xh04woBay&@vt%oB&x^;mGsG_p8HE73Oc$csChI!igx z`lBjEW$Pee{Y6xieaJd7wql1?6DYmrtN?xWaMQVJuUo-*=m4bw`OiV2xM-gRvM;#G zJ!9#7=qSMLB8XtYQ`Qo=6KitUG_EChtuYuTt|pt7N`@(1O|RMOu5G<2WZ_w%vwXs~ zR?yNISE;MmJFo#OOdYb&&tCB`N|twlqz0NENovh*x)&XVdp_Wvx6fNsxfu}BA*@0l zX7Ln@r%^OU^VOvw6t;E>Ji59VE(=^6!t(MhxSC$*0ZvizOSt1rjh$qQd4jOd81LfX z&c1vPOdT}x2nf@0R4xFPCz2~*2MgAFE!#SaIUlpG*)tI53Gi2_Ku+XLKNuw!MdEky zRnk$EIdIyq`L8VM#|j*l?9*8tvl@31IYG>e^&pIS#q@zneYR8y9-D68)0kMQovGE& z15IP}0FqL%$TyPj$b;$Sp%!&D#qk&>E zF#SRKle|noMBYNZT$@VhpW;b&uIyKoXbAc}Y;YkOp=He1%g`P-E)m?MYZZK=Fx9l3 zP3G(&+qs!_()O60wjFyC=VSOCL9639H`1%ljmGF^Q1t8xaXy7Z@LbrnwO}o71wIj< ziM3$TVAa6q_?#G)5_TQg(+%f=$FML0!QhvmmLM7N>zXZV-n1y~2ldK1-$0>=j=>O$ zq%%SV@{WU zC-F0jqvy}X+2bUOJ*afEbsuaU%-F>*Sh}LrPeR5W0mJ3a%Ak;f(0UsaY!H?BrIsrE z5>)&XC!z$5i6x&r;0_GmQLOYyp7vW2CwLl%NT{rpc5=368;_np^2x_J@pHhW*2Il8 zQkb$zeAZQUG0l<%&rhgDG0G@Ok~ll7wyOz|D=D05wL?wHSsG`7+NE~OSq5jJx=rQf zEQ_-xwO8FCX9Mb-x=Y;+Ih9lA)xBy8wL$fYx=-Ct*@fCZ^#E$as7WF#|YUApI z>N)j1YTMPS`e}6(wFz}W9ak@)wnJT1?^Q3NHmTOsB~?%_T}l*oqV;8U0I`ao)kjoS)lj=b zc|eGtjfOWU-ld)3vqmih&!%LaE1zDXoUYd%pyo&IiV7%1s`=gL7{Ps?D3!L*n2 zr>VsoVTE}>Th9mnA~g~WTwkgIig`i)eX;I=iMP$oe6{oaR(s5tD?GFIUAtZzALoE-*OJq(b^gNy=8lNts7A0WKmqNXO`t^yPp~$#m zJ*~s?+lGdV8)75FNUq;QL(|pCrhCN^uyq5vgq{^L+F)%dO$q8h^Jn zt*hhf&}HzuU3yJ~@Gq#uCA2Igsk6zrJ8*qcuJ1%`m(+HP8I+u9?C#o!KFSqkcH^GS z+tbsv=ARa|yhqxDE5JksTPMe+=(>Peo!BB;tsPPigC4s8=ulU^;wt-!6YFITB|gMC zCH<766@IjjVg}NGmM0W_MwU|L5m^ZdJ%f7W5f{3|jL6LAapmW6h|F|WJ1hIb=-vvL3Py&i?Oh1N%(o7$s~^3lim%v`YxhLI;;wWL@SL6iuW7U4`AFn$us z&LG-a+zd}Wz>zY=7SfATMBmNEWD)dB=TOWzqXgIyERo^PFv3bB??`?24E#VO^CDDG z_`Zr^DL4~ghENrk7UMa&IEjX&8@NH-I9wDb10GZqJ{L_E$s@ssiWsh5sh=)Gr!CgC zf>}J;r){8EfubPm3^2Dc_~9!>kn(Bhqo=X5cC~e_T*Cv*wN@3jE_EAyhDDWy5Haaj z2zTSg(g<$g2_s+g>L7f0H@y=8)T%xYc&|U@`HZ z$3gtOMm7M-g598xpb7;m#E*WNMMtv{l;gZVkK;9xLDFgL-1xLElrau4KBe{7{ZxGg zt*z(%4C{TKN0R|t_e;F|F%*q$8=oiFH!Y2`5J4xw|0D_LI(8Fjm3SDc3MnF*3+mwPw+a!cq1BIT@M2 zV_(8$+2skVv47*532?ZZq4ft{oU(l{I5>HmzRaQz8bt$L1N=50L^5-nAy{Uc$)>CC z^=*8H(FO#p$3OzXW?;5)oSx)1C@q4LfNVT)RQ!wipjKL>E*0d@_{+v=+%gD_(ntTp z^d_*=DiN^nLPfxy?FRc*cirj+y|)4MhH`^`JD@*>5x;^%Ad<93?BvbHbYJk3y8A=l zzBS;p0!+E}ciK;JJw9df)=#; zu*K1wQQ1rB%g_c0DQ?QgtYjjQDb?veMgse!dP*IS^AXPj_6bQHB@|y}1@@B7|ss*Cz~*63w!AQCvvtkBCV| z`AIJ7Iw}|J#{0RDrhU~}7u>qyVDc6w?_7o(<0=$AxXs+AEAF_bp)w}c!OZcS#P8i< ziiRs4e6{=0mR`43yryy8y+y7i#L4DJk2^7MtNVJ|{&cw}^o{81@zj-G-;i*_}f2uLrvf!wB zPBubio`U0C>xYwYa(Hom-&q;6PrA#|wq(#E+Mx(x0uC(J2&`lm; z^QYO2n1ncqJ+3lJaVan#^*>;T4v(WIfS?8^`3I;5Poi*E9eVc2;a+D&i*C@s3+m)D4jb*Id!7J4=Vs^RIBf5f*Lj`des{pqR6|%W6#h zxCyrXqBAY1O7w%E?GUB@3{Ii)%bq4-Va)qll#wWbP;NEQOT6wFPiM+(i1BHnQRXPR zq|EUb(e?LnNbu2F9df<#aXWi6?KZ~y&$S17Wbqjs!pu%WTe)C^77DV|?&?L5}T|M%hGm8neQ%}1AN$amSX9lP^FB}H$(vCA{dZuw3NSmG_(`~Vp5dq&51s(kCp0O!}mOp__ z6~p2gQWy6Cq|jkyhC)1ty@Edz5GR#gcNYn zxV0@Ok$imw-g)l^#P}vc6sU9CSU~v&eltiwGUsKR>Xn=~cgK(NeI6+O8A?^`I;9}km!?nH`d6Xx5 znInuK#tPqE5yCz;E=P=o+m_>jzK=tZ$qUm4ScURC>^cRnB$Gz;ud$ElxdKJu4Znr9 zzrYbZg~C}KadTk9IcT407g{LHM*0bOz_fflmmIa(hUmeKoSTJ_$$v6&=Xy4sPNaXB zO-)CeO(6k%4>n=T#6R*CV$+9la)A*LD}*M5!<}=^*&6;DY4ITPM@bm)?=U_B?@o)g zG={>|LK6oAuZuj-Ek9|_aL*vsj}B3jn5Pnr$6ti} zWaqvED?mx>L@mx8_X?t++B=xp+3qjz0ug1W-`s2Z@rI)@M!KMJ%cS)*Vuw{L~dK> zRMV&~w$8b)y)D=Ur}6MHGP-?Gp~@)9`AU`ZrR5+_1M$0om4*l!fSrJOTY5&@IZeU9;xgJ5{e&^jC4|dQM!c zu?w{g9J0$44zf%^^6%r30FI+8Ewp$HG2j6GURkZB(}9M+JqLb(qb71Y2xZu9Ai!7qqJoC zO0t;@{w440KXc4uR7PpbWb~y2xMvjGs@?woKSsZ2EsRq`fVL1rx&MB&CHnu_EYV8= zw*MH2G9QNr_T}r6q$Pm_g-op);yj3}ga~9*<7fjSJIszEg3HA?7=mC?9ywLQI`S>& zOl)Wb@%W-11bJak5&-hcx|JYeycH&}A>j)Si07X?Iaij5)5(+hQ}m!Bw1nx^&y(3P zGRwTeSPemLVZiW;nK@0-QiXx3<@SIGrXURshD;Tink9rNFpX#D%nclnMu(4}3ysy7 zxb`7>QUxT0xzcsp84O{ew$&o+Ko-#%T;f5o3gasid*QNXG~jME_O!{juj~G}rYEmW zyQnj(N^FLJ0%|W#E`Xv=6&Mj7qQ!_}vc@TV zk%fpB>K3AX=-+1T-?KQ+LX?^>poZ)!190O+*K~rxu}ywV<1%)wXN&C}gO{Jl7jgFs z?4E$`TGCS~qmYn~3dfAL_;b)~nab3Pc!_EfW#d>NVsX$%NfXeuHo%Q+{XW9&2rAH5 zhSK&3V&;xL4vmB^Z;Gz*AEYzTQXJ>zh>IKn8k#qP?Uk!8P=~Av zY_#X^*7hzD;CZWru7c$F_*411rPaEa$Ok;AXhBz?Ie2{Qk+036oWMgVjbG3ro*mWxGX5;i2 znt?cMY4HoajcrD)2U(nnGE9m#s)}ed|B*w9N(}RyC}tGiR=@-nORqX>+zcDA;x}-s zPpr5T6;KoKV61(l9dDIt4KqaGLb&Oy-t9JaY^1Ta;SnX{)sS|3Hav-jG!EHtI*lpM zsq8>L!Usm%6p3oI^5_F|5#V$~__qQECS~KA?n72%=fiUSMM%*Q!O%4uZNu$0O3xHF zFVR7ovv)pfot=CXcfmHq*6cqf5$5R@W4(9EFg24Y3hrp~;t@SJ$?Qy&ZuN~)U`>d_ zA%m(Mlt|6Ys5SdZLtp<1i|r^7K9fLvD=H;~@C~_OgjD3w5fP8lv(3B;a60QeY4Cks5e40q7^YpuY#uPa5c7bC@ufm842!i|G(4LTRwT zSs%PCO2sJ|#>f3MW+i~s5o#?aC{X-!G(?$pzz`se6YA+%3-D@!{xjaZ0mr|Nwu^-4 z7%I-{BzPGdW_Iqzm?zNuVPkh+oNhr8Mgizg=&=Wu+;+lKu=)ThJ+K`6Nni;dOAI0X zKQI7j_FMQtsvX|ASb{05a3(omJ;v#G+4^l3-(j&~xqpndk8-(2K(>}UM9aQE@yhaO zgBb9QqSmuelCOtjjUqing$rGPbs~NXIFXgT4uomC!7xdU%APoP9OYh0H(h$?sZf{g zkH!3x?rOk1ouCWin9t7=RXn`nRA)Dkp|M!cMKx9>AkVpk!jX-eY*2n50uxJOrWEk z)VD9+o4yOOeL3W4NVmM$%9*iSm%wsTk5DWpEu~$+J7(Wb%tfV2=GxS+3zuidn_gQD}Ja08q z&GekOr)h)&p+vZ&v|-NvNQd0 zoksCi;`&=r5kKMtGh5MZAZy76xz=@cD2p&J}1>u_w{hzmD6lC*MxPBlan# z3_|`JToCdQR5cDd#kLbHgisvgPZZ@sP*1S#Q@H+lVjZJnN%#}J*VBktcsCq3`FdmD zW(I3`e;569Lce(PEG8koFpD#c`z5{zINzWg+SixGeIC5G%TfZ%DjGpj}z(BfK}=$~l41 zf`bwUmtJSPUDTiF5CMztuqA`EZ@4D`VMCyQ!z%)+Z=tqMP?5vHkK#cKPb7cnq)Djs z7>-iSR%OZn-GbtJ62jz51bcw0cv(Ps@L34Cio>w22n%~o%=B<(3P&D-tz0&9 z&`ylL>=;83(gNJz3=KijQ-Ts`*7&z!pS=MX5uj zcB5g7HwEMYaEZxEh=gX&9r7V6{0V35q2jELTaAZ)@>#abTBr{qWxl`%2vJbJz`57K z-=y(jn8*K+{RAcd6t%DM@kVDr&Xbe8;U)I~XpP%`%tZ9R#?5W`U=VqOg6k0EdJq+( zFX(Z4sYNqrqZv`G4>GyE$n+z{JVPi*1#F3VxJ3czU%^vn@GG!>11Ba^g3TRt5nsZ8 z#f*)JzZG7zLkL}n(FVC|5+yp&HjzaEig}VeQ=t4sJpCJlat;+|6~qifdgvx#oWBK( z+mG%cUc#2N^%deDDZs!tNQ$qY8ImMmNCQNem?!C6iA^iep6F>g$fRrT44t%}!|nQi zp#Ys-V7<*u_3z*m@ADox(&48qqw=flDa81F)&!3`$ovOr`|n)mC@N;1WM9JC-bg0; zVAKZhNSY~N$nXuWkF~RNVLP?bZF_)0KoB4Y35<84(!|TN@WH|}Yq%Kvu!vTZZ=Ki% zw!>DjILUIP=j7SJrbloEd-3auP#+{uGwE{%E|G`yp1{bPbv#A?EsB;_Zey`{)7EFI z2|O>tltdhC0L*{JJ>P8sW*i`aT@A1>{SX|zpB>WeO6plQb@Db29Kp#2dGBjMQc9pt zVZlL8@PV#Z$bM+$)B-w~Km(6DRT8>Yf_fBmnO#kfxS?-yaVg{3x`4XiLKkYFZw!Gr z^^ypItX29RyYQkP48De*e@z%npaSU!A~15#phr5vB+NecZI_SiS2-ib?Ttf?hnkL{%-hh4l$RYAEtZx~NSx>y_@!)Whsr)<>vz8>=`{!~WQ}A0 z?DlYAA=iC3dO|mAjS*|IgUhO=$=2|89Q{w-V|1TE&*iKw{Xw*P%mDA`op@nY+_?I) zfXmdd21ADVL7^sD5K)@>P=ePNaYg?oi%TrdvG_8J|AHcNOG=sD|7UFb7cBk|MawuR z{+l4FhZ@eb|8TJ_2<@Xx%sIf-Bykf|4sWPr zhT05xQ37MN8ze3tK=yJB@Oo_$6)`q4MtNi60$)ln=7j7hRI&5u73cj#E|AJ30Biuy zr_kvxfJYLXC#^oZSZ@Y+m&ond2Wide=_Il4GHZ&(Ar?=u_;)P6fCAH@>Jz1^<^_18 zo^P1aN6_PT?il@Ibhhn+RPH0AdM9>0-^pkEyKWebw+$OM(Ah9-6khq*uu)*oKYpY1 zAR!{l!`DzOJ={K(wtaZ48HV z`%#c?&7p^Qsh?8x0RimbcQF z@2?sYEOAqc1atuFiL!Xm=AGd=HMEKUrU|S1gSGTRxX|qM{8#Pdr z`%-exF3cZe+Z}vVraYlHaj!ds1-MMu*{bc!QJ>;d=H_3G(;+ zq-8hBEByNH7_RYFvyw9NcdN*+Mu59C8{#cUE*YN!F+rPs`RC%G1`+^t{un&+eb*}j zi*|`un}naVg{!WS=EOjHI4 z8p(L%y9~^wOAm_m^v9$|Ml){DPEa=Upm*f8JSu{D1F`A={aL&|UFP~V zNA|_IJYx;QsEtYGpVofg7xO~y6ICE*b&nkZ1aiv9+o{L}EzeF(8nyNz z+$7q8yo-lV$ShS&w!s)LFzPCLvJrC6@bncHqF$U}O(b2K@=df2iC58wkMu)CXypa= zD1qz`PFENWukko?6yjH=NMFJ$2^W_*S=J@_p{JtC1 zS$y*;8frm4KF^fY0y0`^_}CPDlQFyidf20_L862tm_#YOnvBS!ijoG)3|)_NAwt|A z!d(xLxT)mx<+oAf2N)YQ&8$WS4e-^2WKv#?!<7-J=FA|V?-AcbxS8`h^{lBQLEUqW zg$IXl5_dr4k0Qf>C6egyYsQ_#_;45B9FOmV56r_mC3p&Fz|>ScWi@$yBn4xjpHr%r zG_MbLsnwg_9;2p5?e-v7cuy#w7Ek8So!-Rj-WE*9Trg~H=+$r|(|XR@ zG%w0JI*a)*Mpz|z-HnP65763>!8qLev~{zB>i8T7-9gvWsm6Uj`I51;Gn{vYg}BIT ztj)7{GmA2dI*SDsi!5k~$mgPT5j6;ZY1rLW=Wwm|k9XC7uB-mZuKLe+)&D8#`u%K3 zO7f@vJvMxm#qYCdKmA9&&ha*LzxdQ-9pB)swA*iS|DW@c@bdoSf0x&V&39jy-IwwI zjMu3_bd4W><4dl~UH=o;;SBA}sxy{ne7~~R?p9g1z+YPB`Vlh>=-AS9S#>=!sXMLo zb!`NhXB;k_0aSJB?{jA@)@f#}-a`*!*PO%V_`A4h?LJ2N?f~WtO#V@e< zMHaus;wvnEm&KP^{056ZV)1nrf6O9c@hujA%HoSG=pxp1hw87f_!5dJht$8YJd1bP z=j%!wPnx#5{wRwNvG_0xp{P!=k8N`(IM0CC#?O^GTliMg&<~t_5Kv6b-*99T*;IB< z_WtY^Cx?EoXWxO}g1+HaT=mo&|)3>@eTR zlkcKQt9kE1sOKU1+#0{GXg(2wFZ+P>;ToGYFC-!-J}|6^%qAM{YMw5gn$-!8o@Buc z6HS4I?{Bs~n~9GwW!U2R_;#kI(-Xc|=pKRj9zYUxMkaH-waMa}EJT;50c}1qG{V{z z7J}?sSrf9^#+ne=cGP0UMB+x~6AC+ceUima7T;m9i-nK}m*4(ArAb$t=0%90Zg>g! zX4%HQ*)La#3+5he4-LLd)VSfQFvC)8FBbRocZSqaSu8ZIlO1#j-*Gu5igs; Rt8!D&8xqzB&42Fx{|8`c{-6K= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..849dca5be7e5c65574f706b9623f2d51c9ae0d44 GIT binary patch literal 37994 zcmdtL33OD~nJ#)x)v39pDwRs5St`wFKw^*(NPv(K$P5;k!H(tT%Cf2e2@UkAl0mX? zFs2ickRaObfIH5!oKCl)<8+kUSvNTE_H6@BJZ0TmMae0-qTEYXy8GQ;@AbXHqMwc1 z>#g_w`wTVcfD`AvwcdIMv}>O|ojv^f-~Y7tem^bE%He5y_0Zr$eH`~&dQl#uBMcjQ4D4MPG7g)1OvC0L^RT7IGHmU!vT(zY zZP?ypXMW>Q+HiVLI`f-`9K+5YC-a+!GKO6}F6OrkW%gvEe9Mr#$L+KFY(rVYo*vI| zc2D+jPEXEoZcpxTUQga|eoy{zK~KSOVNc<3QBTouaZfSwTYdH+?{G;^$#7{;>2O(3 z*>HJJISWr4su-^9spL5w*TwnL&vHJ8zsjHfeL-qz&k7dqM0ke3`ulo0w1$Pc5Sr<) z@z<)U>sYuO;aUC~HT6mz=ePSjKF{mf-`7bcuyi>{m#c()Uy9&*>ZR1L=Y1c2Ob<(& zkF*8L(l)TTLc|p*aXqV!aK7U2@;$4^E!EzM_3hrFfg?wQC;a5rc?U*T9v4UZ{eghD zFEBpRe^eYD8669Fj}IL84-Jg?z2ewNaA4S9|IhU4YCdM`8yOi5_5}w0$0A7S`BV)t(b;|L3#nF@F2gZ;45vFe+8SnmQ>WgY?%+%^+ zckiBk2V$8@*q(z2)IiJD(c$BxBmR+K%&B^}j*bNVCxbEbw&8(504<8yAM*DN1&{9Y zA0HJFzUMevKvfp@4~!g(8TXHliT(aq`T$zi>z6YH&|u3p-x2>KWBxIJ%%OU>4vhx< zzF7L6r~TsLq0tk2M~4Rb$75EOZa?~vnrhxQ^0a?w^tivDI#_w+q2`yo2)V@hxgH*q zOXuTz^gdmW;M4aQd_s@WXXr7F8>>w*`#x!ys8oN<)Gzw+>x-EK!9FpF4vv{Hjw9qj z&?xKkn2GuPzV2#s%-q{M(l_ky?TuM`dxuATV?*S(_xAqrSl^HwW7@iJTl;}+oiX$N z1MT|`?C9=_nf4v*?j{d{_Uzruf|zex=T&Zr8uO_)Aoe%(4?m6mLKpS+4K(zRiv9)# ztd5V1#Yo1Sa(DvyaL#h`>Di~d<{i1GA4>8)*Co~~skQsdiRk418rN0pWA42&Jj?yG zO%zAPiTaMdfZyvs+3!D2@az@)1~6dWr^da(qyFVnp!=#IR?z1w$*F=9GxhfRM*Dkv z#g!Dq>efKM)rwOGPrv}@EEf~jODQPw_p5!E*mwNsqkTgIm?WsERQtAlQgu%p^^bT* zMty!v;Srygx;fzWjUp)MJv=H7d;7dkQ&u$8-#-?l8R_k8@2c+>*HA4}YTiU)1o#cBA`#H=NS2yaqjnzeM%jU<> zLR3FtFis2vkFs$<%|{P2U%xVk@zxhqhHD^L-!IpLRfZNNyz@)D;rTQE5;w_Da=k2y zoJk$t$^WSP)BbjhQJu`2F`Rw4C>-q zd{dJ@2;owN%L`e={ z5iuSXM<@Iv=(wTLU_cwd{v2uJ6Z;^BiBO>*sBj}M^r7BTHrK zvG~l0v#>bppa&UJo*ui;iWD}M-rkeuOT$|_d&}@S&I�euvNHbDkB{71}v&ug=7p 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 RBln~343kawoQVO}{|zR%-P!;E literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..723c5af42d2757c95c260d7cc76820ed18095cfe GIT binary patch literal 941 zcmZva&yUhT6vx{_p+Na5%WlvEUQC*-OX9(k8dt!?1c7W>50jWl=}ekgXIe5XYw+w} z;>~}@<6J%YFL-ihSSlnm;lrEv+VA_$%uDL^sv)21ug`ZciiYu9Ee;<`F5b#t`dtDU zz(6LlsL3o~nJO;O0xNnxM&A zpvBst%{ri?>t*_gbwO9h4n1Q%&@+w5K$RaRI7e0A@M_;)e@qaj&q<8%8cW34AX(kv zFdHD8$v=|Bmof|!MB-==#Apyu%(DUCP@G=@teX@)smZ3xXs4D;TQDSN4BiFV#%zQ#Pr629-6 z@I}A_mP#M}TYqEXUa@KDTF@_-d83S{ys~2I1Jf6hZsc| t24sl#V>g75#3Y08YP}WCD`{KK=NmPhX-A@HxK`I}*{(S-UB@+D>mM{q44(i1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..19c075a5efc5b1155570de516bc7748bfa924775 GIT binary patch literal 1019 zcmZva&u`N(6vyo}ZPIrAHM$=f6Dp^v(pV%869+&V-H?z*Tdf-hOUTL+D|oY$%1H<1 zhQ$BCog04(H*Q$k331>COlT4(YZNpelPyMujBW*QYiuTl)k(?xo-pTO)sX8 zbroE^SHTAm0t7-BS`2avSy0D0mg6?Ec^>6?0ToQnW)63e%ZsRJ)_GRqWmGnCfi3U~ zs_-hR@*1k~I;!&qYVbw0XzC8T#ha*UVwWxPWwZ=I1j6D+h*rj>cKPk2=SfJ|JsO8( zNEG6%l^matAZvvrQ!kptn<@;Fkj7EVkHePFh{#%E#7MgJ1FDm>p{(vsKQ?mhoLuSj zx`V@mqt4;8UKjWKdw756$=?1zHgvq*+20uqrm4GvL^REaz{exXG8*GqZ_Ks5v~9`8 ztVM4~gr6m1+BFe+5#*E6}zB#`q zgzCLGUmOfcAlG+CLq-EXBlE?{jKd*_RimcS&?smW6}s(J>0lft0mjlk_ETacWnuFB zuXWN}q?=q_!9-t6OXuo88$IbxE<%iNbTzXf^Jb2WLzzF*$288g8J{tXKYF1r#nz06sVe-lgTJ1yJ}?2XONoZ2Y$CW$T?I*45U3 Dx1TE| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..90a1b35c6d05c414a11bedda5a98134e584693c4 GIT binary patch literal 5413 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ff3b8137e6d83ee282af90c837132add78528c4f GIT binary patch literal 9010 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 EAK+xb2><{9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..46a1cf00e208e8f9e4c8240c8c45a6e8cc711de6 GIT binary patch literal 2004 zcmZ`)OK%%D5Z;wm56iNx#IE}~l($x>y=r?Z+90Ttw1tt@FzTWQ8ODktN7BaJr9yK3 zIyM3NTY~R<>`iMXKTZ?tTXI{!H@AFj*RG#qwi`y}YE-F*Fsi*v~3eh!5*Trobj_!lnKIh$z1^!OPFw=cork2W(c zLD~2p^0gn9>pai3)dDo*0M`&&cXE2N$cpWo-n}`hCf|sgpA45APnee?k#c(HPs9*FgKV|J&(O` z*)-q^lb6vErcGeNSA%H!9^>yd71;&9hyC#8tDh*YOGu|PXVFo~(6|b5 z1Cd=AKP4ciknnV1jd*)wg^t0PTZre^Wkl)ULA9@II7QLT_S@)+iC;D*glW@4k+P}y z7(3>6%N6M7DYn-hi{tpL>WM1c6UE>((MaFt3d>R^&8>>t!bIJw)1SbVqANcOS$ zGQ``m6WCZ6x#Z>=+hjC)lS2yCQnYj?&4ns#dQo*3`8FDzezC{CJVP;qFn|iE#4%mU zV`R4QXQ;I1J(}L9i2zhh_mC=EbBsI{?ju}S4}h(k-Bqdl^%5^c{ulI%vfVuV1@nri zTEcq?>u<3{3qUW5VQ0Efcqw6667V8&bP;*4&F0EK@2PjPArxLKpA%;rr<|_wF-WhD4&53i1Lyjddae#BsqdRNy<)=;A>DQqPt1*yx?lVX(fr& zB1sJ4)!e6vQf5A;=}k^oKASc&2+o$a+Cdc7+TF0$z^~T}J@=@;VLm}7S)CLJ8nkMt z1ZAC%g{dRut(99sE9wGzkz!)u>l&21)a}bgj}bNgm9wTv=X~7cLWz#!wS$`f!^yJ! ZpbGaPb)coxM?tsNt98R(tyk}S`Y%kLG%^4H literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..99647f29cd4fb734920da1bf4aaec33000b7b4e5 GIT binary patch literal 2120 zcmb7F&u<$=6rQ!$KjNfGOVR?RpsDGh7KsC#Dp5shDWC|5DuOt;MCYM;Y;>xXmL)AZpORp?KLPDtG<~BKT;=Nhhq&*-pmS^9-dGqGI?|t+3=X2*81jdg~ z_U`{$A>wE(O7u6&Tm zluIyKxHI#WH?|Wtke?csNL~b2%+pANlW*Yt5s{=50171$bjmUmRZ%()pG@sL6&Ws& zmw%!r%4aO9eouHtQyW)W^?Z3A$d{NCivmJ!QKgxXg1(Y4)rTo)dt<6oNk{QWsu%>W zlMyIV*KRZ@jIW21#;KF0&p1dH+^D!G4RcbBsU4?$XmqMGOM8;D%u0aMI@r>e2H~N* zcRsgNJ{)N)y~Cc;d_PVH^w!;N+otsH>|{Mcui99eWZ*&f`xrQNLy1m3?v^@@3TlVU zNa6jtp^;!FA4;CVZh9U5V8v2ARK+}Y5NDC=$Y?IDrH9(=OVffNt;4;#PdQVN{$T1+ zCRD@*=z&{Pe4&U;~iqQQI4HfOtMohDrw~%OE@=ZocqC&|u4-w3x)t3p)e{Vyhd58i ze`dyR!gAr6LRf$s5Ubl&tOxh1+Xb9G$Asj-No@XTYvraoK$-&n%6?h|!NxEhljo&sr9L@Ns%tMwYk6u)q7T&kkcrL!HR4XpEDO>1hn&jXHG=70&bE%4k#Jyxlx zYW-#b;+DA%I_wt~i#aI=LGTON`ja%Dk#~P1*Pf9Zr`2mg^QYZY0*}c~K$fmNZCv@j H=AHcm`tVO7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dbe8258cea87b530beb30fef9e01ded166d20d69 GIT binary patch literal 11442 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 IvXV;vAD|`Vj{pDw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1d6af07c87e1b59ecc2e7425c1f26c2b9749e550 GIT binary patch literal 1895 zcma)7&1>976qmGGX}`SouGb$iJ?PdhG`l3EJp~hkQ(8)FV}~46f*5ONW!a+-Mzf~7 zxKL=Iy@dV)J+$=XWB;AG_LM)Nfs(#A(yC5rp%Qxg=FOXVU%&S@TU+ZG@cj7O%ggPS zVf>DZyH5iaPYlB>z6FLFzClfDaWk=e%S67x8;R}Pnzwl~aePPfP3|Twzm>H8cGB@X zAakh8yGhUQX}-nRlD^;9e4DQ)8~%poJA9A~{UPupI-tW>w!cY-k?D`<=9w|xS{|On zIpcB4yqwRYIQ0aJ;z9}{<18(_Sth(Bqm18=Q_6A%B$Xazl7*DT zrkN~{!qR<4@;pu>U>gVNV%$)j6K&*6xJ>m+{9Igx<=L}rR z1YEUU3!kB5tMjn4TkkvcLv};DcYY_z4(#?|WlrQkS+~^}exgjGql@(zdiN8`7p7+KtVM_GI(I zpliRH-ZRC#U<3mBy?8P9CVZP1Zyy*5Uf-BM0B&D5 zz=CyjYl?M{i#Cw4C5Fh4kZc3FHQpXgFS3M9zm`{Xl19@LkzE3iifNc!3rzw>BNmW& zT8Qv{L5;a-3DjbeFVsdU%|tLGA;4pS#tL-YGRxQbbNQ&OClf(^N3go%G0#H6i~Y$z zFIw&;jK*Ygjj(}(8aH5)Tt9iBoEapJ70b0R5)!e`a524puzYw1*k`jj_db94>7yx- zPoP!wN$;G5SB!e+3opyDa|riN76&i(5ksn}ZKlmLw{cgF4=c1~z!!sYS(dc)04WQ&!Bm#1K9_ou?kDh5!@b zadwU+x?i6w$3f}0MPl$ByHK@jh2>7oP6Uh$B7+Fhf@ve12?>7Hh7VAnQ};B9rC08I zWG0#L!YrM|(Oi&o&cGqO2;9+`_by^tlrGp}&Y5aSmgJcrTzmxi96KfQ0H+oFwvekq z4T-H9p9$gO7NT0Uj!&MR9y~uj6>GR`&qTIlX~CqxCLcEhLcYZJJ>+(fAh5+Q5ViN; zFhk45A&|apyNF90J_laQ?3nf+_B*@#f2FN=VhR@>L5$iRoLi+&IIPMT2f&TDlp6#z z3xhy)f?zg>8ODM@U=OSHFyp+O;KhWTheD&!kqeyG!bX)5w&bcCCsn)XJn2!X)23Qk zHJ0_PC>_!>vx!wHbl$`V=oe;GJC0rv&cPF%zQ2jiVe2uV36AdLA3%Y*wT+$yuQ}4R LXZFlaqciyn4>1!K literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4f571ba97068a647ca4a79ef5638380f89c41aba GIT binary patch literal 2336 zcmbtV&5zSY6dxy!?Rb;yChOf&psEX05Iz#Ir9dl+y3$hm#j;uu2e-)e+LPeLAJN!Z zHPKe35>h`dJ+2RgddQD4CDw-&J+BckrSAl_wv4Ih+G!Ef-e~o zmkXZkD~941jiRp_D(ECf_9pz2QR1@VmHmoQ;d0TN^rws|E~{SEuNgG~6`%S!6=&kQ zXiPh$wqR(za(%MDu;g~I=LT5sc-^)e=mfXjC?;0yhC!saLZbVjgS{he;NT91DTs9k z$Jk~Y^Z?eP#zS_zxt>Jks~0XUtv@U#|yDi)rm*@hj=N6$y zD!iM`ahLZ9yR*=kGHYbhb*R*_h~-D53i^oI>iV_67MoW>A2&aZuXe4V-CQE!D)fKU zwEZ>0ClfnuY+7zJBKCGgc2%0m{fZi$9-T_GAu(H)4PD*qA4>pXVrB+rYH-R6ZOe;J zH=ec97{(W2`tusvnAC@*>0zlh+*N-hi&N?diA8k_7Eynvj~&-)tg#ya{`nOE7}na` zN2%0;`qeBg4PHP%u4byJUt36tVi`wWFJ?#BzuXOUYs_bu0OD|kmF-AomoQs`J7>9o zoe>HxW*ho1WMYE=`&qHshP1G>Sso@a;K~BeGX+oG8Q+cdWL>vfF($el1}(SUCDw|E z0T6QpaCqkRD{dSm5Jb}TFjZpgcS2%$);v%$fmTQT>X=kN0?lC^X!SCc z2#&iXAdE{O(@NS)gpm$mY($mwOJ|nnFP>i}jOJ*uMZ!J~A{>)4Gs>q%q{lm-%z!p& z|4($n`i$;Oz5cU9NfS0qF^gYdf@XZI6bkC7f(qsP;>=xfW<#kBXXiGmyY5%_-L3B1 z&<+f>-8aI)bYnR4(v8mtT64JL!;R|BpLd?HDj}7hcP}2u{jt{0!0FJ@Sa|7+Q6v(N2)wj5l#|#LW zg@f^9E?X@{Pdd^@9t zwPF+Y)u2+`W8X=__>5|dRZ>D}j7fl3F^S2FN|kW@LF7eG5HTFIz?vxHRV9oXc)dAt zhRX6h)4Yk25w1|E4xQ|c>bmx92MX>l8~fs$xQlKK&1l%^}rW05BE3AdT;sw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..75d8566d700f1caaa8c7aa7c14f7be2315fd93b6 GIT binary patch literal 5564 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 E4>~NtZ~y=R literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..42bb807810a6585979f74239bfbfe370eab047a1 GIT binary patch literal 8196 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..86d3a13c78ffac419054283662c36fc1843bdb7d GIT binary patch literal 18780 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..aec7f378132baf9c4914c2724b8a06779cd82721 GIT binary patch literal 31145 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�aJQ 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..28e3681cba4152a1563cc044aed6d065b6cd79bf GIT binary patch literal 501 zcmY*VO-sW-5Z!zVr|67knx>kKOoKC{|-C{VunNDCf8^QQ$FdB#T0s1Gy z@l_c9QOBDWOu{2d^9;7iuvRX0!_Goz5m70gl=r#FZ*iph5Z!B5u#B=SsYX)fvfkhR zPmA?bT}R?|q=nExa1dMsTf?N=tX%+_Cjhlq6bvfecBclDYI{xoFUPW22!MAYTi6ca wr&r1(NYWPxj4m0(JlHN2Y!(Utc*Ye#zo=^OOp`0YF7S4xr@vrS~8dAomng%SE~AFtuOko#3b&Jjfk zqL?}YODA!po4C?TJZv>rG^C&SGDrd$CLu;6M7;}~e5e{N|Mcs|mYt1v5I>W@dGz~xR2xB5D&C_x(7x|VQss3c?CM#G* zS(a8ErE*ozP5=AFWU7|0Tr-yPEcg}z3!#OEfnKXw2LLor0qU?Q7*wY1n;M%`dlT_} zxh*3?09>MblY<_2z@EHZ{RVX1;WpQLEs~R=VdHC7kRY;CXxxG^}!RtLw~dA814KZk>LKquG+pccEpPO~$BP7FPE_BVg9!Or!) z_Ql!F`+S&cqo_$`9%tN8Mh%VkrHajn$9v;cm`<9<_wH@Sk9T)=CpY3`#FK+Urn%AA zU*PT9zFnnxE{)qn!?1dn7Dbv50akA3(-%Q$b?i%Sc!DS0m5hsaU{AfPnL9JJC*IZf z3!c32%S}!`6yUCynl#amrI6)T8+PT6lt#~vZBx~`r{w$~yYjW5TH{AfxUGFd6(b{R zYWFRc#RFA-H!>J0zCp^cXkRCU-^@s}wHe~z9aav?B(~fN9yw|D) zA+Xh<91aE7HX1ocY?#4S?5d{w+)RlcNp7QpDw^q6t$8B8hZJ9nAr((-q_`QYTt%=~ zf-zk@h(q{wfuJv&0F3w4Gd}a51%2;%(1TWnZ8}zjl9|E%H7H;6lTM5#M>(YfSwEP&w6|}6WvY4i-io>ucbN(-XFkl#B zeFMN#?-v(h7MQ?PScQdb&f}9+Y|Z74q|Amn&A46HUU8}nwB$5*!EZke+7>}V$)j?5uP8SC_@%RUhigk3KLB0rwfYQ?tCUW-^HI@j zhpt-H1sJG{D6mI>*13h0F11%td(u`3VKO1aHVD}tLr>v^DD3q1cp`-;8@;|x_Yze> zZDgkSpP;bE*l>?(?)G2W9V25yWU*^2Oa8e?;T4_hcr+CPj(Zy`hg`kZJ_m}81;2?6 dO`ii1dd&b{zZtCiO+T!Jry7k$y-{yo{2O~UH6{Q6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c5b61dedf506d40aa29ee4d19500fef26253b81f GIT binary patch literal 2304 zcmbtV%WoS+7@xJ*>t`NLoVEl7HZ3X`;<&A+JQaN)!^v+LABl|W)G&-`ZQn{U4P9>4j1Vj_p& z%l`D>VKs};?_x0e5)n`iz5(zA*{F$Z#ZI`2muMywfs=01Q=6*9s+;n(rUp1=Yi`=h zG&2&X-K>{u=Dd6}595rTbqn4^a{_R#IcevcQ&|-K?4n)xT5V2`jVEASv}f!|=x18U zp88g4&i1Cuv;8Z#aLb{Ln9K=$-NVeZO=jv_0nu3->zf_NWfjNQZ{A$f?{BPcbT8>v z8(Z69;P{L#?~ALI6`nVJKVVV`g<<-J8HSGE0+?L&d;7uzfG4kEhAnZ@jljlkIl)gH zpNWm>@tNcEjSyS=N~EN8#|3lU9&;>uKX8Lc)hth44FX2{lWQia+$2GK5Kn#Tnv{wz z=whC^ZW7yQn~bYBOuF6A)PqX2uL$V)R=Fs5#|RYkvmkaIyWpsP8lY4nz+Frk*X{*m z8xyX|QK9A#tK*u)XqY>^_=!WXv9`;=EggrpL2On|g#>r8FPvi3G2_L1*tJ2%So^Z$ zgdVJvHLgkPDVYHm@N_F^wOmZO)|Q5F)q|_>JTlw5iU`zOF^r--n2q_2e0Cs7xE7kk^e7c4=zraRCK~7VA*{U7u#BS!Se#N&XWf(3g3gz8)%P>-{&zqgDZ5R7706mp(q(pm|DaM zMQQg>h?|b09$(pqJ1+`iJtDi9VtxI+6}=W^NV&he?zGx$2a8sx&Td8K&VRk5VDgsW zKDwks!=Q`#V@Z$r17-w)Q0ah(M07zS6o@B7M^EnM6yT^N+k z4ppUCQx2zc+1z2WkkW?pC^a!ATp1KE?kz0um6l`SdL;a(5SE2gPDaK{yT!rvvPu@A z?-%2@>T}?dB^_R?2V5hVb%;;WunuD9pd%vD$pvV*S`U0&F7dQs*nwpjJZBhN9oT>k zgNSP5Q&!-*(L+a<%}tAls^c2#i8qm?q>@dOVtK%rdf{WPih7V#J7%%;xp231nmF2Y zUBh^a~b^u=LGQ?x;0VVH#ex76%G3i{#9YbFn7<`wOa57m6)tF*M>Z;{D>r2qf` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..25421dbcd0d449f2da439d3018bc2b83cd18db27 GIT binary patch literal 7502 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 Hwe0>M0aI{C literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e97719cd70806b71c071539507dce08929f1d6fc GIT binary patch literal 11384 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 TZdR+A4t(Zg?1pcd$sGO{f^R%K literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..74c88840d09adc183c60705079bc54367a46a9f0 GIT binary patch literal 331 zcmZXP&q~BF5XRHAyA`pZZ_tC6f^1$uWKSXEikvzJNzxsaH?F zf~QTygW?DC8)iOchH*`EjFOgb51ZGNKXUO~I2Fqjp-teZN4(%z7Y6s(lzmwsUh+!f zzT&eL>gF%!H-hRoT(sIrsjO_N<*gCXD;C;8=&kT!@mZv09iE!o;Ow>YhQ=uTbjF;0 zRC4qJex`js@5_X{ZZ_2b+%bTu{%2>8Y7Ov?e4S@9zE)&#Mv&+DG*T}bIVj#@y x{5L>wDS#>xJp0>l>mdA&u9a4CPXLrv5n!?1Pe+%=@llJb{6v^1ROl3+-~(o5Tv`AC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9d3d2ef8804bf9c30e696bdf0724ea15be20f46b GIT binary patch literal 307 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..76661800de037aa3962cc2f42668606360f6da10 GIT binary patch literal 538 zcma)&Jx;?g6vv&fCQVz0ih;30ELoZhAjAL@0##HjmXMX&R_fLtk)0IEK{x;#C*TxZ zz$+71U?Ofhl#M6(C;x4~|9j7BG)h4mAFq$APY-}ECl14J#bvvU0|5{akNDgZKJ-NZ z0};YdL@*LP*b^~~MFJDgy$N_irs512HSKb)wN}~=4n^9D8zzZ8>h|}Y)z>l~O_pf)7dmhV9U_OGL+p?^^jq9z zLmMMR6h_D<`_Z=l$hPsm=FY}>6r3aU4)l@pcKEr?mrBt5xn4JgEc2OGD~fBAW3kaY sAWBM#3YHs<{~qiI$q{0b)d)?itxe8_A`Pb(?$3=IAp~dsus`&F0c#(a2><{9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..14d417c8c910148ba755429036cb5204bc237da5 GIT binary patch literal 525 zcma)&zfQw25XSBNY0{P=R537Cz(AJv1rTC@2?-$(izQ^Gu@wxq6FDg;Y`g^QY&-!E zfp`GCGO+;_s>Fod1gRr8{B$SZefRmEd%X@Y@pN}~@d5#Ov&COHO)}jz$vq$d0RoA` zp>U8RT;vK5dBR7&XrYz}P#{7SLfhl=w&)kXxTN*WzEYXe-Nm9)7Y9rdyx09SE zx_>G!Wp*tWXZuBRPDNbcBon#WJmxfldabrX6&_C*FPX&o+;95myRRl2RPAQDbd{ zF_Wysc$il@+!h&`aJr-VX2_nh*f98TA%w4BYvzT}nE``Oi|;;w!!NJhb#4Q-V)}jo DY$cJ^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ae13b9af4f189962339b84da390b610a10e5ff21 GIT binary patch literal 9686 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..392a6b247ddc1dc10d074957e62983f734822f35 GIT binary patch literal 9627 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 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 0000000000000000000000000000000000000000..3d809ffa860f6fa647e46ea27e6366bb5eb6daf4 GIT binary patch literal 319 zcmY*TOG*SW6ihPhhrt2YuCncglMBe82T%rd)gX^_5_)<}=QT-su)TrDaOstL>&h#* zGHFn>;MJpw_o%|vYDp4q-X8a_X@6w#7c7bEG~ptVCKb_CGfO*GF-r8Jq18ec_oO^} z*=#*VRA?`qjP1ej=%oui`p9?2_QnT31fzL9@)`%Hr60LM=ZuQz!}^n^fRaw`7|@-ibd>XES|m#SS|op5xOXT_aMjeF_Oebq7Ba o7~L3kbI8^LH>&;S4c literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6760d78daa422ec4e8107680e7c3e561b47b3fb0 GIT binary patch literal 292 zcmX@j%ge<81da=1vpRwFV-N=hn4pZ$Qb5LZh7^V#wg}W z7ERVFyMUtnlKkZS9NoOclFYJHg_5GgyyAlVq7sFS)SQCUqGE;8;?xv{q)LUP{L;LX z#G*=t2C!bQduLCHyp5Zn4M5r{pKc$KPT} zN-RzV3xmWGb8_P2S2BDCIrNu$aZ$2O1YJ6g*KHL?>`tk9Zd6^~g@p=W7 vw>WHa^HWN5QtgUBeh1lIEDR(*Ff%eT-ehoj%AoXsTlNB%TqAoC2T&XUn37YB literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..72fc6be5021989bb35f815117a8fc21cfc73376f GIT binary patch literal 900 zcmZuv&2AGh5cd9L6PBdnfO_^d0YK29yr9Qox-ymcmt~El`Kk za9x&Kr4wCa)9h^phG9uIDbyO@6msF%@N!ikiCba<94n;7`Phb5Mmx>5e4>~uE?|)U z@WS?k<~~!%2a{S^3_IiBh1|d2wFrYFFa9AqKxP3hAQXv+M0^v^BGK{^HxJ%0#g-tZ zfAnn<1FklziATtr;|BKTeSf}s)C8T6fd~*Kynb{75*V^ac3uxZoELOY17)^bHSA#Y z#nXr_$c1Sm$oBgAI$P-yvMaZ)nObJZ>I`vg#>)${y>bvs$XStD!~ag~6|~tY&M>Gp zO^Zw1#Q~z*5U+$>AO6d8uq_$&L(}p@csl*K>EyY>pOoiKn&+h!we)^B&(CWncQ3~O z`P<2N-aVi5-@S*&dZ$AUf^BBhPnvd~3(a?^iyGxcRO2UW61S-ewKRTP+w%V*H=Sp@ SS)P0OZ3nNIblctVVe|{K5BcK& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0d9621b473718558eb6e5cb6dddc9e9eb0c7ca0f GIT binary patch literal 891 zcmZ8fzi$&U6t?d!X>|P|kbngVS?Ux4$;MDsNT>rKL?}&IBB6_Weo2n{?9_JJUN@kB z09$89g!oICSZ_s0uz}J_z{GQo6bVcB{CxKN-uHd>$JN!C;JEkp`HN4Kkk5Xx7_};) zvpbla5<&VzP!UL)2mOHhJd|M`^&?6Ga-WFkgoqkM@58FDUn}a(CEh^T?wA`8%o(oi zL>tFsHiWcDB`~dI(Td~V7}(J891gYFW0`WGT&5M5vS31kWlB3X)FuTs+S%S>)*0Zr zy~Q5%dfUvv{uHdUj4Kg8efDsR3GVm^@hsbgai#?J!3KN}*kE8miNS#7;Ko|C7~9fH zLF%-z42~w+0&~bF)p@Q}+|^TT+I}(t!|_PFo2fnAQ>Z1yhKJ(}Mf?*c!0}94;PKvH}6c7mdjkB9{j15#bsmr@4>Wku8Ki5(b*MDP6?1c#VZIzDCkMl z4~w7~m6slK#YZ4|3ujYw`(k*6*M!j73MP*TMU;Ym2oGs9IKH_95;$r_EndB+;_bU2 zji%bUcH8Q_^Q0pSa%eq1`@L>W+D`KWlYkgO!n5{;1-8A5u&qf^)>4UP9$)*PV{86@ z4S{SO!&mb2CHZ9|OkV{N?KFe3Zoo~ADx(fw8_H}XiNd#$BxRf=xfWCD^OYpopK@94 zm}RUt4Gc5 zH8P%Dh2}d{)xJa7*sSQ>HCJ)z&9OHyd=Dw5KY|*K&esTC|3a>x)dRZm=IET@G_U^y DS@!x# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..57006d3c2770654b32ee8db05144ce6df20a0f7f GIT binary patch literal 453 zcmZXQ%Sr<=6oxbRdMPRj#a*|v>1l_kVVoSMskw|vMyxL*xbu~=b>%C# zGO?gl59EXMlbrwLm;L@Gay59qSxf_j-h8uOp=(av(MJvhm52l+WC0J$kVj?2<1!As z7O{kmG$#`f~C3Gv58Fq#;YAEQ@yR$@jWOpuATaV|J z5Wpr6PcSZ@F!hHwY%#4DH+rd9+%%ldACn|A{ zD)sPRQ;|pPaFuV^3}Yf|jBQr0cKLJ1#<$MuXM}%HF^2IgQd@qI$H#RsmmG>mbJta( zDJDuTpfX1eDPql0ia H*$zJe7LR@( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..772516e6dec40bf9d49777f813e3b8c262f2795e GIT binary patch literal 435 zcmZXQzfQw25XSAqNlH|(;S85_lNX}kZV zfv7d3X{2K3s1lFR19?Oa$LW$y2%&XBh)s&cTmJM|bn2{rR``Kj2tw|`%&h?LTUr%a zAuknSv}7db+4t2tE8UG5A-v*-kbX0@tsPm@iC}wb#SQzf&>IdfA%yS|Y|asa!7Ff> Rw*lOJL-BHOov4m$`2cPbcH;m5 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3352d2ac58a1219e80d0bbfa14ab67aa1538294e GIT binary patch literal 2578 zcmZuyTW{1x6rLH|yWY(vA&`=AckkV*Y*Z?>FBJ`>l%iHkNFtD2AA#B{XF|qexqcmD;|YI=++E{F>p}Nj-IaH*NThwCOj~34ekTQRB`& z^;@?Iukrc;;dL>&Z~0TOcXlxG=ma#?iVphs=0C&DbtRfwmGwxuU; z6#Xo^GrUaKZ;MRGIMVgCeox#FW!u(`TYOX8D@7^v^m}qC$qT{t)ZHgSZY256a-PId zUpLLURk**dhRB;GL&9bQiSP1U3GmW zj6topvn4Vw^m;PqWhA7xBOx4x7jA$b9>jx}GR`)=!pM5XR;hTtlXbe_jT;16n2I3K z%^*lYK0&$_1dq!w8Gfk;0nei#cugc)XqLD47Pj(KEId_@$}rnpSeE%Vgtl0S(kId^ zfq*y#VZ2bt=qN+UFe`ewgwva{7cF3>I=#M}fXh$ATiCF;#HEHa7B2j{h~ZiR;z+p8 z@$!WIS26ZihakE-C=ft}jhX+YUDM6` zVN!})Qsz?D;7V!SaBlv7cj-4>gCao~%me_eqD+Mh3KdEvc$;av5Xpv|MVa?SBWGZX zB^PJ03W9U6Gd1s0mooU-@Vfsto$CBp0y-lhZLjBf5}|*i2Wfmwu+YF=vQ4(>DtS%2 zZ6+@QN7qam_-iUJ!}gd-@)O{F3U7gNcUd(*u74me8*pGuIZ7Gj*f>%7U-T8^L4FL| zkpT{bMimmZ9M}FC@VNz`+sUMyfB^KYz|>p(oGRcm0$E`3Gs zK%f2tCki8T9MQQ5L8BbeFwg95{cn9l8TXcdIY3G(I%Yq3Tf+iHkvD z^R_(*u+=M-tb=9)+O{=_^XI_OmUtQ+^BKz{<6pzq;w&s|kI{KLWr~2kV~qXh?y+~( z+3_@>*opBsp5tB5=_msuuhHkDxiHB*F&AyP;ToESU54$=S|?@0h`*gXGAsTVHt4m7 zN6f?0z?xZNkQffcbB5*tkO9yLWDffPpbA`ejZ}^AOsXD`X8`c~kg~_q-$UB)D}bGG z>*f1oqFX2NA$@kYS{RF+SCl=|b)i{>kxKd=bRC?D2D)ni`#O({D3qAfOJJ&Qt(2LH z(;@y5M-t-$C5<4`HcXlHcw!p?^n92*LGS~dDX^cjy&3?#2H8Cg|Mt6%Q(YLd4gfxs z>$a&pgKF$S`}hcGiJ6si$YlfCJ49oE;WQSTfyVgh!;)Ba?Fre!)04PN;WCZO6YKJ85uNfYT+nyf!R13oGZeNpiJqdB#$}h-w8dDnMVl7<+$qO=@3v=*A{@3G zCG>t6wh~K3*Hpg;Gg=~$CDxwA_OCDEI2}#WPLxa0F=N#<-a$t;n8U7TdegvvoEQd& nMyrMa9wrH>I>B!3{x}%>*Rh3~c5IUtn`QINbx&EdcB}J0UHfHp literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..581c3abad14290f9c7b9ab6e65dbe1372c4d2073 GIT binary patch literal 3476 zcmbtWU2Gf25#GH&9?26aQLg0Lj&zh<%am%VR@=CBepJc%sgXeOj}V}2gTwjuBvT@f z%-%6FX}NG^p>P5Nb&4WN`%));Xj~_KXn?*If%DoI$!a0xpoaiO(fUmd1;kDB(3v|@ z7EQY^UEt;>! zioBQ?3sP1p$XU6dWR+k{%0~)nRxL!c(LyX6E5x&LN(5pnOuj|K9kZH1o+nIMA}nIJ z-4g4giC|QP(Wouk+E-zCXD-R2EHWcx+bgk5y!yR~V%cFf)4Ba>*>*iW=h@u!id>(D z&*NoVKbK=UZd+ci=$LtZ=FF)Rx>a->7%6gh_%0rqp?=(SoT6uhA6 za%W+G*H_Pks!mzqZ9`f&2(7h4f0@`MOTo+nqgk;cfI{69aEg5=`&^y zj@0MR+m3GPC0=A@%jWt#2UEDZISqHv!4|B{bIy$J25WWqeA#2gd1rVMZlW57V-{?~ z@MDHifc1IgHN$wdZ074T5yN0b%P?+|8Y=w7VV7GYR^c^n*kA_3%#C>5blg&rd+x{# zFIgjK_u*27w}E&-Qoy(}kR{?v-{+l;@g_UeAwX*{!gRA8!0|CAEXi5PmRX#MOVTZn z!Io8+#-t@F8)0oshQG=Z04gaPWl0u+f2^Wr+Wn3e8&=20!D#5}RshZCyz{z!p#%=h zdHOlink#YJb<5n=O^4|gdV1IrLJb0ncLV)t%>!ncEKo-0V4lalx_1d-LIe7X)T`f> zfTSli_L;2*TceMECcZB<0$_m{JF-lM9-bPs3dtywck!s|e#D$<5S(mB)^+rJh*Pp4c4v)`qrcQ`@($>8qN)(J{0gk-i?gDqV|j#Yo@4 zRq3y4PXI1!$Lg>b@_}!LfdSu;S@=KLW_NqDK6#ehq>~weAAr8E1YT!1Der-v2hsy5{1qVJgNDNTYEoZI_N}OWq0YOFC4uM_g#OQg zc>%1wOrno6@vvNW3L3i$9fR*r`H}k!x~4xgR123GcWhcnHg&v7OZm z!lJ;)CE>@yJe?;O#2=7(`hhr4Gs0x`5F-A5B4{Lx`h%Er!-!@XDRi}1DtjCfVfc1s z#K7Zzq`u4-?F*KRH_;o|2HZQW+SLjH93KQp_XLoCl6#lQmLNyFH&R{8LvIc(kGwf@ zV_+>cxULP}(FXs$ne6$Dh|%uNRA!_7J2g@0OnlnewIvH}-T&DVVd6^{A-qV(BXp@} zki1I|OTUo@$!~GsJvvB!7af?6k_h8Xp@3RnL0?S)6Z+~A;ee74v<)3eexKrb}s@6aIq0juE*1N;_1y) z&r0v9wbbbq?es?aYgeVes6AVP5*^*>>HGEEujW1{v~B-&w$Zik7qKl7_R(OuzxT{as(&qYbX_}oM>~4&`oTv{ed$iZ`HQ_19rRsoe1QC38Wa9NpHe;) z$AmvdVBk-5O!$ZbbDN^jZJmzyOSjVs@&o!rLi{*FCt~Wys)Bq%gz?D?y^Ajmn6DRp zzL;}7U*Wb_=8i8-g0;bN-^Ll~T(Ow1cR1GTup|RN9!G*A9Fs;vVi#ZSZmMX1ST7}~ zUdhCH0@o#xVDZU2kmx{s6{^XS&Am#H-Z*9qK7<6l$uUYfCbh7X!vxQ}8Uz^^C6>r1 z(#dT}l?S%=5GC=MK;^!g2;5e{DOB#SDT>@(YZv9d?O0464Y$J$CNmv@HdvQVm%$uR zmvW3%U-2p>C}%ka0mn$^ST^|wc2}gsR*~DoL3tXKD`6cIJn2}>1ojIJ_YLd@bt<0f zL#=D(^Kex2<$U$YusD92KLui#{9SxP0aBEHLZ16O>H7y6T_vNROEMK|1jxVIiPm}j y_*%T@vbwEMdXm1K-zNCqR)^^5+mRdU>fX#Y0e7K> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8b4e1337855fea88babdafc00520a6c6c00d7cc2 GIT binary patch literal 3145 zcmZuzTaO$^6|U;r^z_W$oY*EIKqnaFLH4W>0TEakTTXBy!OF5tEGAmDd%J38d)?DL zuIll6N3&ou7VxtC01%S*(Z7Qy{zAX-z(bTbUO`Hh?^N%s*K1d+t4`fcojUcMuc}yB z2pH1!gN^Oykp4}TlgUBl8e?2fAqduEf(t9-L#tzJ;EaXrre+vu3=vfck=1M165)^%jMF zhjo@FA6(bFc@hq8-?|YdTXCMtEL3u=Ww?`;TVWh#@nknFMrB&$aTW?WlDUw1vfKTV zw$$NO3zLd6rU81rV^vKNmvNHCT32pAm6^a8=T=;9p|r2(y9v!ZnK&xf$Q{cNBU~Gk`Ip;{z$1pP1bJ5sg@$#*^)V|tnDvgeo_`HOm&DYpci{6#${M; z$#4KIp?qAbxVzqQDnE+ycqpT&3ZiIOh;c@8JBl8T)9Y<4H_mg7+-KdW;nz7(c;2^A1G zE`{B0ccW_5fYY6(^06(|q>O^9T8g3*c#9(SCUu>qj67GlMy)ze4aa9zOAFLS7+b=x zNn5~ttIgZ|95?N+`1UwZuVC;?$b3UM!hPz%w{MytbKu#t;+$9(D^J~?C(gi^=c^?` z?vXEV!Do|y-cS;53E`~p{(ZtgH%^Q0(UAN1!;w@Wflq`8vrz%Zbb}y7x(fu4pduWO zbs28TFfp1$80VtKI$$zHBxoF1Cc~J*IxRx^Nap3`tSCkZ0!$H?jJjc5CR-S3BBSo0 znvxI4sggs?(P7a)VIyoSHVx3bMnUB%S&mg+Idt*~#qd0lY~jB_sC*kj%BC!;b6#4{ z>;tMhg26|8z2jER=qM00L_Nn`<7f+&^Q$6xP`Octg1AJ>tbgKc=~yY6(>yJs=-a4i z51Ge3zQ`xvICTWw`M|0*_i&U7wPKXa*EVQSq$Q#7nkPab#ppk1V;8Z_KkzBrX7964 zz~UdXzy6+MW83_hHRT6<&z{OGyXZB+V|is z!5n-uyVk_vnu#YcKXdn-dn{jZSn2J4#Mz$9*~Bdy!kO`@`?)31@;i5j??C&uuLj7Q zX4N|+Jqr$44m#cQrp|V2>IrwB?=>K|w-?aAIBg(b65hVO=jVZGUpDP^tF`A(ecI8K zuR(vk4@my&E&kz=AN2kX=ZwDJ;y1_KSf4B!ik|yCoLn$qle#>j=`bnsewHRgIN|X@ z5SAt%n5lX^BI+b=$9)6N&}E!uQv7@_hQz>^iF_}|>1PyN8y#+_0R<64XGOh>LVXKa zWrNBTp`{8wjkB>d*GlEkw5s*Vkd`t{hKFCUOchWNMVq@Ix9Yss*&MqO0umQvnZ_CT z5>Z_(YMG3coDWcEXl=5iT88m2^?~Q!quvXYeUFCt@^Pxk7wRHaZTUEMI4KSpLfSq97bhzE-Y+&Q%DbN{uPZBw_nb!b`E*Zvdh&~pPjpxRe$ z*TYEv&~tq59R^N-nOFWbF<0>kLhHhbUtj^G|NNqA3{;%Ri0=KY0KpIm%oD{C#m|gl z=Z_Fh#m+mNl@v{e9%g)x&}Kbz4uBv?ZY|w=B~J3zG3tJB5_P4w-S`vhb9z}l^{3W{ zY=(E0a8mvkK94bjOn3by?jn~JhEs&A4W@?giSVNDck zuwApO=_iUsaK2B4Q#1bW^!#!eeN6MA1n*sM@#3 s2>fBqAvIypJHxE?I^!yTt$uM{C4STNgmGBg@+}I}Hea>_c=$}?|2IPmFaQ7m literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f1746e9e885250a82ed96de2c92d1e7c9fc48747 GIT binary patch literal 4710 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b50e5d943b29de96b5fd00e32d26afb49ec333df GIT binary patch literal 343 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d0f850e8a5081aefb3bc5c9643b811630a66d483 GIT binary patch literal 213 zcmYk0u?oU47=+U<7NPHuwIIz4h!h;eMNn}OJ5OVuXzPg>BoarBkC`W8;6P9EGH z-*TKy(t<8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6510f4f6b82164041b754b458c587c93b24ed085 GIT binary patch literal 220 zcmXwzF$w}P6hsph6~Q~C6~w)Oh#)97f`W}8pOE}rR^4R%$qITAYfI1K4Yu_Hf}NFF z&}n8~G0Z%=-3|*H-?qD1@Mi^o@n3kiL%1@{HtfuMwaa*g4(WtiP}Ue!-jW+Edc14_XsZD3>|M=#`i@kjvJ(O71VN^eCuv-6 p$-C%Sq$&bPP3i##`MGQlGpmaOj;I;R)lpZSeAPMUC7V(sz!x3jK3@O; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fcecc5923ce62e7a67e167a5e01c2e5d21ce1bd1 GIT binary patch literal 296 zcmYjMu}TCn5Y1lWvT*+}?IG^|fQSb+HgdSw2ohp4X5(cuVUj&Pzr)VT(qFpPzpyfE zA@^Y3@E$Xd8CotIQLz4ec>XB;dltv2inuJ5>Ow}`3sd!CwRn5K#e}>=15xR%vItMa zkoU1TcUK$G6awl@oZ6pnR>X4M6_w`~hI2>9SBr1sb_^>qU)>RDs8vThB& z?Q81-u!;?)xs7t53eOZtQ+tH0f^{`SRxWtr2#e)eS)9+s_CQKQTFSRKA$u$>-vWcn6Qct2&w+r#*p&pjc;*CZD?=Sltm6**jD literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7aac0c35c65c4e99e199ef1fdf09c9bc8b0dc926 GIT binary patch literal 447 zcmZ9I%}T^D6or$v^MlMR6v1t`1)Yj(kwKWHGU!4mNa!TjHZy5cZW0}R8{PRz-MaD> zT$y$@ju-O5Ih=5R^mKYad|kgkt}iGdU!C|{ffsZC%(;gk84*+jidI1uRAClYQ5Mn8 z3)P?+WG?yS^xT!Lxuz9p}!L8qMt6PeqcjJz4sIG!|;RM9smN2aOxR_GnKo5H+@kcTi~yyF_m#w4I=< z*-B}?k#&(o*sX?Php5BA<2D($BgTa0jJ2a?*XLib9oiBh7yXAWoh!xIC&BKJJ$`P| zQdf{}?Z)MGk@^AGz^zGn^^83jhyqwHQ-gfpcPlGF=CiG5@$~A zWNz$cUhLtz=E_Fq$38|WV%|NDgJ~o3uMc{YLJBI%tq{0Y6;QrCR8`J63;Bf8suU^@ zM_d*hba<{HEM%36JWQ%`q(FDx?L{bZ&3fMogf4kr_CZmu^{Ij}2ebBv_5c)^)f1kn zk}nkVQiM$ls+{}O literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bd2f084025f645ddb46d1ab8568373aa6bf069f0 GIT binary patch literal 6626 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d116b9cf719bbefc3dca2e0202d4d11845580a23 GIT binary patch literal 10517 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 Tu0CR9#?G6q{fZ^q?Zp(j;vwOOXOe?XCj{ zXpo%y07VZ;&`X~tcV2VqD=bi;LutK8DQGyP=jWSmM%I(b2*G;t$BUP*V}w43!h7ixTZB5Whw^I*S=?gz_8v5@)da2M~_EX%$n*Tye= zLe0vWK7cSo5LeS(F0v)ZS_$7jy9g$}Z-i@%VmJVX74{yyp$nfv7cx-ZV28G7g}2d1 ze1b}}jsL>G#1y3r3&ooMsiY9Yg_IY*F8Et?YdO#m^nQq zOQS1tq^laLRPIPCCoWwZtrCuM>S#vD&w{pDOGg@Ig(FeI~cB5FS{lQAO!Zb=j zpdys-r*JAtbyL@@XTnF*_R@@lwjUxS45sOCWbVM>>hXN7D>1)xXAM=W`BS4`3g+yb zRp+J^h2Seu&~k1K`~Ss-HfMl&-m#h08$S*JcHNsocpGLZ`t5LXh|M9Wvr&g=5A+Vv z&9IqCxC;Qg0SxYD0AhCF)1u&-6@?!a#j=6P6GdU7U~mA!PnnhyP{U~3j4qggKM3S+ zF|Al*4D>VWoU94+>4)uiptZCeqkXrjfx|9s8lr}@E#8nB$bxKvtS`jf5NPmSrFkR8 by)gbZ)R&-fG>FDHJO_9XNB#YOM_>I1ogiAw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4f4e1c0a1d349d53c610fad32bb0c0707f82a192 GIT binary patch literal 1448 zcmZ8gO>7%Q6rT0kUfb)~af(nCK|54Ml?AbnMJkF^QV^1(P_-2YqiQqR9ow7C&a7r< zlQ_6ifT|}1JrRUBAjAbly#puiToQ5!tcXCWkl^Mj98gcZS$ivyS?!zmGxOe?@4ffa z%uEHrdgqI~>-S8A9%~_6AnRqEJ^;FhJk&*=;pLbS?5p$1vSPdj49u~bG6R-~`3#r6$M6XRZ9O@T*fJr7sT zto^CHZk;iJI5I~0{oF+V5ftcs@bp=3&wNlQpxfvw`Z)i{+%lRuavl=66?44Cgu9^u zZQs7p!LINq-tyHz#0p1>$juBH6#BJk ziB?x9MRGD=R#$OPiU2Q(AQT+DEeWpZwo1JPR1uFT(!zL|k~FHXaLgmBut%A{MkP^{ z4WY(#5@#TyNj^~EEJW%mOJCBE5m)CgBvvGSs@URGMgu>D)`4t^dUmyy-d$x*QzPn9EM&cTtRpP$r0a(w>mUClyL>c!mE%_j9kC3zzIQBvI4O82}AT@~q0 z(6HgBo%TQkw7so1V#53Fn^LS(S4C|%*pw-8sMn_s@!OGf|F75*as%>jPmpbe!=$Q{ z$to$IgCKSIqeUQpp?`PLKl7-1?%`_(_QL1h&-TJsy+eCp_r`CH^T&xS*MF5!Ek@Q{%XV>T~0As;!{n V%&DESmr!;7pfdk~B`-k8{{WIxlv@A* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..68c5b342f5fbc66e1c1399fc4e91d5457f1dc8be GIT binary patch literal 431 zcmYjMv2MaJ6tv@ngb6ynHskkXYOfw8~j zwG+S4rL|${lk9i*bnkSJb-O;$^>p}H-$DSs4f$V=CNBU$k!e6c3F!RLnMAjO zq$u>@rJk%O@hG|Ye|QQ(S{FQsi1b$J&66`pQ!4cx-n@_B`~BWN_Ve&?4hXyZ;YsZ} z1mK5E2A1vz@EC~BTi^i)c+g7)FibfqC}Sm19JTijh3TrOg_@H&>13U(=%_(1)Eymy z6u>&pw*`~5+Liu$*0)cR+SRkwe(Vj6i@-6k;ib295seDI3wU(EhqJZm^HWVor~com z70#nQ_%CNW)yF^(E3s14w&qqa<7R~r&Ny}J<{F_U-@qotb;5j3Xk1`sxy)#_Ts8wY zCJjD&C^?ES(cLH_+~q!r*dvjvC6{j;%4;Nw7$}Y<8bThaB9!D1T5IqI#RySF0y33` zA8fWcH0v^ksidBz_$l={PO_(^L>ts{VpzhFOND0460I(F2>c3F zdn!WK4}_?kU?gZ_37|OQLbtSRKU`c`LCdzilIV}^)#Zi7exit5B&uXe(qcui$4781 z_`siA)&>c&^^9*e+-Th@QBuQI&a7(Kq&lgdROvjk%8ijqs>KN81*fDOj z@&_Xmd#jz1$(H_INw=PNm5Y0G@8{pmca*6ERohwGUfQv@?cE1&*xv2#rNS5W+K~d( JX*wbF{sMNj(!c-! literal 0 HcmV?d00001 diff --git a/tests/__pycache__/conftest.cpython-312.pyc b/tests/__pycache__/conftest.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65b4cc7506f7129d66681964b5d674c270f1e0b6 GIT binary patch literal 645 zcmZut&ui2`6rRaslQwBL6^mBAgx0cM4_&aAia$U~Vew$fy1fJ>n{H-pLN>F^%ob8X zwx=Gv)RXH;Jj!1DKRks+S_eExQRuDGn2!IO$7$`C9Dy{-^jAG;>S=CtWmfR8q3cw|-jigf^kyX@-dGwb= zGZ(uB4oWv9a3}F(pzWFx8Pd?5Cv9mrrt!Dio%lB!v4sBnf*e zxw_%=?UVdB1d;>Qvd}#*AZ^bRhUazqyic*`QB(+0CiI>hh{G+a=B){-31g`cI>n5{ zJ2)3moNy+lHX6>8^|hwgaGa(vpE;Y2we^A^xwuesC!vK2!<2snXVMS+zQX0(SglQx zPL7s)shD`sB|$&NkLWe2$X8`AWI9Cmj-5L59+U99a@W-wK%pa zhgL1KYM)?c-5pFFm2VC=v-14F{EoE2%Q3n-TzS9xZZ$&-N2dICd(Oe*H*9!+ Se7*8TT{=ZTy-jE3U4H;2UYe@_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0d184eaf2d631d388a4f2bd8caf875d84c91bee6 GIT binary patch literal 3643 zcmbtXOK%&=5uTnIl0%A;X<4=<8*zk9ur?D&P6A}%IF4b*mLHoerC36Y5Lpby(=Br3 zc`)5g*&-+)iH+QR$uE$RlaIONlz*|O^ff1+bKAovU-giBSlJ-J4#8%1^<%25zxt|J z%+59(Jb(V{=R5y6<2e7M&hf`X=Z@nr^>+x|;VgAB)@6)jH+8!%e!bMo{H||xep<<@ z-Kv!;sG+jrxmewcvee9Sp+&4iCFHJf${skFmwS2v)u`+B`K| z4s#Xiz6iIIJOM;4;KD;}-Aao%O4aRE@lwb*QDXH_Gu?ly?Hd0@lxQ;-M|mvLaC-B0l2dsgQ%c?A3{#UL)$I8!w z*I;v%fX)3oV57U=_*$%6z)Q7>-KIXhLWLnBkv5a7ut+YTY1ias{1^|jjfXpsD>U?d zYA8NUZF6gVV`J@p+n3)XU}dG8CCQf2wW-aPN|qC~Y-j#-`4Buemo0dVBd^nfnUxhBZY34FA~m(A zcb{|~cOJHDrUnc}T1#2SEFcQf6_Oehjm1z4DUF9;<3U0~t;W$n?I}=akv|uEgGk4H zb9Q1Ta&m~d159t=coX>p_n2${uS0IY5YwTZ1 z#8Dw~oq~yihtGyds*^mNobeGhQB5@Stl>5peGunsP<*}L?_=~E`<;TT+A&q@w27;zH&mTiK1%m=5-U41w15~{hP{=9)ZDBKa>;Br-+P$sE z>zxzX+e<|*e}Gx0SwbXK0}(3_U;u0ml2q^y!M0|d2qH-Va;kudA4=jPJkuH#zDRT* zSOxV)oJx#hK+~`U1fr8nsL%0Ei^ORvZdI-@`73DKuB2Qf@z2L;Up9?!Qon?uBrYg3 zl|r$_l0i?&_z>GfBD1ES|^;#|jFYR?EVMsu=4i(>gI5++Nb>xsz)9 zzo2w#0NZffFhrggsz`T{@COkYrA%|<$@-`8c=%*}eao`1{EXg2N!2o)4Lb`dd+cLw rw&}dAU~U1J!&KZRR#ZgcO((E{yXZEnO*ZdQ2sgaOM~zmaF*oxclGWfS literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6d6689e981d21b36d4be4fcf6afe80e99840f837 GIT binary patch literal 18118 zcmeG^TWs7`cH|7_&C%#-$#yJ{B|GuhmTcLUWyg=$k?q9EuH?1dY@=>ErH7O>W6q3n zNy(PNSty&J@M8P1zyeJd$Vag4M~q~FeC~d=N&3-6f25U?5eNrbv>$1{)`^lh4f@e@ zE_q3g#7J6K0=j?*&VBLRbMHOp^75Q>&z*lyCgU7jd;jC5!hr^k`%lcUD`+M7C6VX2 zH#nJ-d6g^jLp+b^fEpMI(pV^lasi$zhKC~dlju+so&?ocF+LQhWrCV0ibH@1%0p_h zST|HxObw-q^+WZ=hM@*p9#$KRO+!sI9Z{Q$Eki9l7vL08+4c_f0I$MfEB1!(*qOo+ zZ>g1*quU`dv|ZUQ$EFe-C&w`y+5!0l&5Ja@Q*M-#uM0zu#kd#;={iX7f;a_nJH+)6 z?}oSm;`C%7)ASjl&+ukKDwWE*r02^eZ2Fi7Q7&BZl$)(Nz@RHxSs9kbRXt0Tk-VmpNwaxEQu8uCl3X;1 zy(r~N*cY`d$=O`FG@Kv#ECBs8BUVP6ifhI4Ev0)>Dyn8uR&*t&XElXPC}b)*mLF5p ze93y6!*Q`I0tuJ{{L=9#W7!qp436=G@$5%{qrz7Lm7o!j1IUkx zAO|ZULohbD<|I+C=W%sPEHx3&C%q#Ihtgc zSZs4N1{@8+F9H1G3)>i#+_(~x>nn9e%&4Qh3>hKq*PdSc^D+dyY`{7mUN)|qmn|Or zKD;z|HU{E_^y7yE3A}6cVD*D_=ICGVJNlI>(Ta1*=6LH-<5g8^HdwBvh z=L>BvA6Tjq(|53R;W3tS<4OYPo*3ugEe&a3d&c6&sci;wsugSeIdV#FTlbvuzU@A9 z%B3me#hUAb!v$6Y=2)AD-hOi|!C>X>_WK-_cgQ=9s6BV;*FAUY873Cj%$J?An5#k@QvW#hC(nlfNyf)Sc;8kIXntSg%$?o#@_``@wd8gEcM;+9!mQx zk8V$!qU2vwdBTfSPs7%VYEW=-Qcp0;BcMm_B$4v&_L%Hccu zt#PR|a`-AK7eI#A4(Ey!ga%m!1o^C#KddVnltX*U2%&;LE%A!%*_@j19-B0yXH=

) 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 DUr?x2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..625501ce987012107a3012f980b822fb0a7ca20e GIT binary patch literal 6013 zcmd5AO>7j`d1hyJch+y#>p!r;*ew157GoRJI2a5L#^%py*2GYvSV>32&e$F^yR&&S zYp`QSR!b|=L@H9N;6o2c?Ew@j`AAL;&_ktOX1r9p8W{;SJv5if!BkD79{RnRo!vD? zG)*rZ?Y{TTd*A!sH}Cs@e+-8!D0tfb{OTI%BNrPX@*V4(wNhNp74=hQ>q5it&mh8b{7}K;@I+@vs92)yiaK zJVH}GN)E_XpVID*)2$v0rT)_bINeqn^B{@a2H<$DTq}j<5JgE9q#dt=cjUbD&ifXr zMhahJ$G3*45Cw20!1d5apl^V_3i@r(S3|#j))%Y&l57y8Z6u~rnjspBmeS+!UUOjz z%c68n)6|n!IJ`hY(>=)UN46?#&;EO*U{ zN!1QZvLPo7L6`B2jOW5>B`vE;%AHM+@A4iN0FX4j{x<+X%JemAfien`pr~oG$8(R5 znx{N@crtqjXe`hRz6EC9C;5mxBxm$!kee310H6n zmjW9wxYCRV8IBf{w`txREA^QI7xRo1EK!=JVqAQ~QIM^FL5|`$eDv!S4af2FgYXUk z4y^cr5`EX_ecS>)PfZ6)jCjyI?P1WA5%NY2P+IpfiXY+O84+(zV(zlu=%)qYEcimy zyl)x|`1!}<@W)`X?}LawVQw-%_50!50KfKmY!FLf=y`l1*6A(Z9^au~|KH%3opi(I zb&~&DQDrvq#?JfZnR#}C0rhEo!PX!x>>^;36fdyAy#2@UL0kW*muLRwQ& zhQ2`+33nhKdYzc(O=^~o`QqR3x}m6QXGRwik1h~u_7eJW!}+Le@Dy@`CxZ?l90J3OVq33|Ii5GeU$ zyVB#;-NvrlfD7JS2dAzRpAaPvCLYh`yknai{GU&rh+YqZe*KmDdXZXzEx=+7&dE%*D9#+_8i}0@h!L6dlyW;}( z-yXX@{3pI;t&s}v{yN`u=<7V+=KR+JRNHy_pKJU}R{cxA3J3C|!7anh^!K#N=rH?WhXa3)9_nWwywU_X53A{+W9-8kj=!J^&2jt#WJ2@O<@qRKGBh5b2bU@=+S4pWh+c}VZi4o&hfCgbh z{}`G@>Zxz5f8VnM)Zydw@6c!H{+Wj(*}hSW?OlBRMzguA_X*mzw8ug%x0DssZL-}a z>Rv^a*FRXgY(@5EQO9!d_UsDkGub{9_2tpl9Ew^fdTV3_b(w6JiMpJz9TwX0D0(0l z?X%!lLf?67U$O{9B+{*PdA96L(HPr$3W3<5|3O=OJJt(NVMdG*icvp@f23L zkHko9DJ~nu#KMc7lZ*#R{!&^15xW?XJxZmxxZsll5DAOvMhRoaOEP>fN4%Zci@tlXZ{lxkp7_fTuUN317QgAQ$bX^NQiK=NpR5MhbJe&A8(3F;CqU3 z3Bp_4i9-M*UXN)f^hFgt>R|Rqi*|S<>e!)8;U2O8b#}tnb*l1=tlGi12FK!O z4U$%49y}1UesTeW9u!p-fTGfnf8c{ucF9} z?Mvf#Ywy03jU6$o`m^Zh=ky91G}%ED4dxq~a}6=8A(n0EFxf46)N39d$sLYc@I!Hv zjhiT*M>`4@d}0OdHrd@K+Wi+GyJe~3H$!*%Y+LX0!sm)vbuNqEG=Fe$1zj}Rizd35 zuddHkw^-FJ+3L2%kv!V+^MMZsmc~}lPLthfp`EUAmzys!Q^&Y}20(eDEzy(3^jPw3 zFwCN9!;-?|C9KmVVR-IYpe0BNJ;RjPGT2`R2TCv)MKEp$(`p7ZUw4(ODWPGxOPAlv z$dJAWx|YES`FKyr1?IN9l@_HP9%%h&=ix%oP*?trC=h4CC z2@4%H*`v_DL=8RxHvV7KKniY92`+929N88$VhG#~xQ0xE+z520({ksmrfM^NAg`ga znJ5JD=-Z>;AD!vbLy!+*QJpoEgzjL^h2mQ08Cgxr##|7l$4;vOprlNaajSjh}vCZT=P6J1>zMs(DKM9^(g_kL3zx_#3zr5eoIzgY( zWPBRV9y@wau10%`uE|xSqM{4VVF}`B3C5zmKn4qkMkS02HA3`la2FM7Rs{sBS4>p=Oh9Hc|5$ONSbtz@i{S z7BOTIfjEjkd=GT+gvp+O*3~oMrudNP@3>u))%-kV6zli$%HGR2 z7U)v_UaIQLuRb83ctN({UV^+ZM*B&zFkP%qi(f!V0q0t!>jyK91;kV;PmWZ!OPj4OH0G}9GOR0PQ!(l1^eoDAT44sspEr$8t?iqK~KvG9qP+~8~<$%yk}ZbWod&uJn85$L~s>+ zFu4^2BP!gW5qC=#=)x7nm;zS{NslOzB2-HgG*|+W+fb6Sj(-R{UxL5>OK3pL!wrwN z#j@dji>Fp2t$D6;mD{;2KjMx;O;r_GJoBuA;v?6uespYUa0NA)Y?Fzao^X{puEF9O zmKrUtJ;!xeT*vJZi#wF#1}tvi-Uk*35qR5Eh7N6CV;Fy6%?A)vRD{wJ{o(tCDi7^_ zN(DLt(C~pZD&P-%MIcA%$!TC23hb24#=+;u8tn?9P;v_bCK1#vKWgC25*du)3Zkn} zk-J5A5VDvlRk?&6!8UA25X)*{r&e)maZ-3=B0IZk12*J+ZX#8?aoD-Mbaj9O-r)QN z0g5MC*VGxPqCnEXly39q#zsd#L4>i<(F;xGsr=a;ePth%psgAD#R8`%L zGar4Bi?myj_H1NNF4Ap9x|eAya&VE)M;ezDsJ3tKw<52?YwgZk-BxYS@~BliWKvb@ kft~chwP89;N1ymZ>wf6hqe1%EdR>?vqMt=;XjfGK1~eJlcmMzZ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a759bdab8d68a29b2dd9a7a4f0cac8445aa302b9 GIT binary patch literal 5478 zcmb_gNsk-H74GV8l1*}k!`U^GwRxv4QI_{bu}5BH%Z>+%$B6?9Mx)hL!|9n`=<25A zVJOAU2#|cxA<4xE1Vp+;mmrq_IppSBenMS>EEipZ0KUn#lvqp|BI>EB?g>cw%Ad0CLe z=~BZ~rT4u~Cy3k1Sr(skMeGXM<{zah}Rp|w(;*LT!d?`Abu~1`B0`FpH6D~W1+H0Xn7WbFj|ZoK*%(=xQ+9*AQgBKlFes$ zjZZ)-psmg)Ar<))PJdR-y2rzF-bVV9APM#1=Nm3ck#%LP?4=|YKHK(Il1+Typcezf z0BPTF^O9*9AjQ(=lBA(Hj7omcUKploydwn<{LJ-rwd#jSD)Pz=rtDOND>n?e7ZYzC z%0!x_M}jyLZK=0*gj#UDgIgJ0X0x-Q9M=uvAamV4n41<+6j_l~Sa08s3O0ws@6(MA zm2~@h3PRtojWy`@0#t^M(Cdb|bW`iavlqM8sI&2pxJDGtSTC&oU8y7FfY3`Jv58cBMg1)6MqaJ zk{x{YMLiGU0=r<{qmbD*&oVi|tz{!)+~js<5@vXbJDc{VwZd?bK=3m7A-R1gI=Z217mk5KU_6^~Ov7r*Tw zO9Qk#gywVxgFwS5bO&?)>jldL^s<8}8cgmcd8fN9XoNmT z-4CPa?b-QddeqVlQ<6oT1$`BMCz=qvk?lye+NoJM9rw&~Dl=sDT-=CD?n> zBQ{NUI(V(_%b1hW3b5nd_q}yw!;-E?Iby)Vx5kHjd>W1c#uLPyZsv9p5Vr-J@+}M> zJ|xrkD>?*%*yrqm*@w|zV|{jAd){S3K7)>inZKt9za(M~NNWU>Wyu$**t!hO0wvgN z+Kt2y(5DH>{LHlKthaE#B7>1jvPtU@n>UJ5FZ=>E=+$2$X$xHid4w9CqT(nO8Xm8Z zw6%{dP&|zeS|KGyAW)cg{f?y8tu=kr?s`V&La+Utm5mIi!p-aKB&hHmgBhQg4QuJc z_RnU1|MuT{7hfZy6O#GCXU zaagR*%scW`wA{6`0vgDuusfE12`b~DnEt|S*txZEC z8vQ%nk2-&8!C0F)Z)dZ8JDb}y(?3#A-(H*N4`bY)2en;%9<{FqwFPdW_7|$@5a8HS zud?E;i^DLO#iKpv1mpv!PQ3-beUfgK{OIr@nZDC>tHer|;cT_tX$&D=DkOWT@rEcBu?+nocSY7~@{PYAfOiJw(H?RJP^uvG5ERcXcZ5ua|T z(izWR2Vzgx&Xi204X89NmVUhZ5XktQjG1m0heU6s@U-mBc!(`2O)7D z7!bGR#yf#QKGEdxA$fF|Wbqf^{O>tXQ@)E1`5p?KsGSmxkN853QQaMAa2jLuo!D@_ zy^p@fW8MCi-XtyD)GooLj>V&zaC?)Z07pBVBWI5yQZILOjEpl<_BsM(c>Qc{D}4Y}$6~mvLXe3l zRn^pA6_KYMG+OO$DpF;i3$oSTI=kN0yaFwBE5S&?;Zf6H@`J{8^#uoDn|-m zfc$u`94CiYb&M6%5iSHzb+KAOnLq*sNkCl?yQ={ZQ$7c!0cF~tk8}ufj0!eb%>qOc zxiYFC5lW=;r?e|u6>}p{PQH^wDw|*~-$!?EJnw8K-3;!sH!&R3%-c!z6`4fJ%#z;p zsB@%K+MCcOv#p`38*(4ttJ0O`w@bS$5W-VcpgYmK_Bw$JyGSYBDi71NREJV`(LZqm z#S*_JqWBYp&l$O;>mYvwuOR*_>^w4(YgX>Q(PtCUUuWmd-r=lx842`dxP?9gkruAl z=dHfAR=Va~H-=8_BcpF#D_@5JWyV^iZ))dZ4tx7-t%{Z+cdnR}1|y&uBeMHn=bY1l z^)mJk>#IiIr|!8#u0wzMW3;IWP!tkfS8Qdu4kB-l^3)G8V=~Xr(C>=@E#iL8c|oRX zzK1j+bXU5_;BdC%@))gHZwsWv-8e>etG%uyN2+F0G!HZ8;oAVIJeAJ8bX|L3Ni3pD zm`~_Z76etyU!;n-=2an;ldg6%!XYBWdshVbkQF5zL}>$!R51%ciWYe?n9?V zHz`QUGu>j&s5($1T9rSfLWgX`&^owl?ACF(oJ7M=OCcrYnzBTN4#_AEmGFe1ujP|V z@8!v1;migI5*F>nZ6V7vgf3QsB8)s2T_A}+BMU*Nv1Iv`K^6t&Ox0X%R!E4-?nd?5a755 zl4%Xsv%fL#P-}6*{=0LKGD`clW1F|^zv}io^{2j+Jv8{NAyot(>vo!4H4okH3fY8T`A%Gn*;mI&9Inl(4SqB{s~2Zjy->I6`zzI2;Xe-MnN0uy literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..71abac30f4ac21dfe388ddf6fac6d5dc5b824d8b GIT binary patch literal 20895 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..00025593ee34fc3e0ccb9923ab3d34d259f6d828 GIT binary patch literal 31858 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..003ee5786347d575ff5c5711e6a53ca97e817804 GIT binary patch literal 3408 zcmb`J&u`qu6~{RwcbEH9yQ{U`rbf#inr7QrZqwhbVI@tSxM?7`i2x%D6a>u~NnCMB zW@e~gm4PJkp%)?7wkPSROa6)e3AyoB^rUkxMT)}xzTrAr+40HcVjqW(!#8iklV?n!eVxtbftN;^pAsb<1MvFKBR!v&5RQ31if@6MJIg?j+94ow!ElCbe08 zQa9~d(wKP@Z{D{#X`*jEY0cV`_N+7M%({~<-d*7>-hSjxdc4ECkF3cOzsi?*ADTW| z13pA+fYve}p*7_8`_}l{Ir%tdWjjpMOouwoQniNX;rf;oA-|Vp$(=nB6*`m9)>I-y zj)wC_x<7WxYxjgyvC<;d_hc3crS4~PTgY;F>G6(~nJfqIiCLya)uU21@^C-NLN06Y zqO41y3zw()nM1uK5NYuWsGGA{AU*+L$>MI>e z?azeX%D5kesUOes3>pmX=W(>12tU>j10Elb5S z&N_JY56atBG(KicIDEpmeae21oZYpU^`Sj>5AI^A?8IF7yWzGda^>%C2?^if-h*sU za9@dJ<3=9m!p91YH{z&TrGAKBMH=oPhv9=ntgLVTdGy)a|2%lKx`uo;Lbabpab{Ar zFd@p7iFo;Pu#LqWAkA9Fkrq5a3{xE>S!T33N+I46K0Z($g#D52^qKuw(sV9OmO??3 zeG`-Y;$<{AruoQvXeNAU9a*RBAv&T22Jcv(FiyF1*Djf%A|Qx-PBFWORDaAU zVy9%tCX>fx#yW8h*(p{!-nqJFjN7`d8{FY8uN^xNorCz$Iq^;{#Mz=aA3D<}uk!}? zj_pdvo7=W}jU3UfleX@hvQLK(UDL0%*pFHFVZ+7m$YECM{n%3P?qY2EeL$AoX?NP= zZCLxjOsk`pNO#0`EcJji_i6M#?_zAqd*zRPGC0N8+L{gzZQkQcPyB*KHeH6M$NR@N z0q@WXMu!e+`h~6RTI0c*>>84P=8cPj(>zH+gy!5Ms8uOL`@2q#ULFBGOBjfYQU1m9kP zXX@7w_D3FT;n!jI{8#_4UF)&y&|GELANTDM8`>Tlu^y{4mks|@wLNyQX83Dz`-Hev zCKMGtVyFT@CQyY0*90kPe+mTcR%+!|l0{*nZm&FX>8&%T%8rA~lwORl6Y$~8mbWS1 zw@4VeTp;Gwi+J#T82o}#>9Ni?hqeS=o{t@Q4R4^W(kmN zUBXO%j+p^4)8@Sk?|_*hG;Ko995c&=ng5p^;OYgv0j~NLuH-k6M@gHm{1%Dp5C?`m z+y{NeIG4>r9c@XRJFU&X|BbG*Rry^!tse}llJjHb=UJ?9<$%(9jS zVZ<5vLlSh@dI~4<2I+2+_z^_eS+Dq4orzXR{|1Q$iJy}A8Ht~h_yvhyLX2JcD{515 z%ioarn#wUH^)_CrcOag}fW9F>&!+pg9}k@i3fTW%d|F__A5BFPw|PM1mI8dIQ<{m5 zdcq0ff)rB`fe~~Vl%NC=^y{!7S7{QzCGk}GufE`5Z;{R4k+=(iz|ncroRuY&wP}s* zsy(6}%VjH=?A&B}bMsdf~XB4Lh5WgXzhc2k+B9G9bLwj%?< zH$@P}E7dwu)#hR{OuI*QXrx16wN?mH#@mHhT=3AlHOqLBh}*Q)Dq7YZ%dfW~<(6^uF^ya*;+% literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ddb01210dfbe05754933c19232d5b0f4ce9a056f GIT binary patch literal 9808 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 VWaA@QBJGcYn@Gp!2Z1p4{x_I%)N%j- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..61dd317caed9b7b708e3a60aa8fef9da7abfcccd GIT binary patch literal 6121 zcmd^DTTC3+89rxc_5wS*u)8)_J038Ovp5E0Y#A5A5;v^VrpCEgZ9~+qCOczT*xlKj zndKIox{eiL$x@6Wg``beKgP9NQK?e>SYN;oeOU|*s}YfC^HTamgQd8%5B>icW-*vt znxySR56r*+oH>{O{O7-X=lsFvD<)8$x%`8^gFZq&!WX+ZEQYSU2FwJJiA<9uMY}0Q z-jQ^4JK&c~aw)!>H|zMMGv(@bnY=UUPIj$Bm&iW32;NHJw-|l{@au)&;IJd?UqWHR)bL8_w5m&bLQQL3@E$zX%M?j| zR#lUoLrOfWs|;$LT2fImz;I8dhr^sv{;a~Zgr+NL{aL2Q6;1n|%K8;%1UI~PGNv*k z@Vt^zb;W97R>i5M3X^g`;h`&Cz)X-)P)q+~gfOpOXeB$YuJPw6eCN#Xf{s1SBJ=il zN`{@#!ZqR~LauMmYk;r>mI%9u2}H+#^M)?^Cdg--r%L@F0|(O zvK8n>;z;a*qMQ2n12aK%yB_sNM>TwR|pHi@C@Z&U)kEYtPEHV2RMiIep5~mY! z%XdTxT4mGH09cgta#CsNd3D>bkN;`pe54DE$1Q2Y>3Bjl?S^d!u^v#wqp^N)t{9k` z&JuB5kz=6lv>r>Us#%)>*Gvp3tP;A3@MwF0{Ds^4Te9Ha{$}bz>Yb-%{WbHR znw+O*nS<)Qr)E_Xpyj!$I{F^1<$va?qZ`!7!$(jAUHKs}5Rd42a^`c?=m7V;3azo& z3Oz$dX&;WYsmP8j(H!3<=jbpAb6p#zc0t>>aO%H;0M-tE_Ws$Ef`I6HW|8T&LR_-*mwHi(md-eY)<#uoIO`zfesx)xm9#@60^vh%wKa}1^Ka@;fly8*N29^ zso0}|-4WC7INR=^J$l2YBD-YwHP6*G8{XVYF2dMnsZ~ciGD{!2&ZeSQ*cJ9V4wEzd zr_5Qr<(#}NDWn?svt^>>M|>YT>Za1SV^amZ%HDN(WkKI%w;0`W1Y6R9wH2B=t+D&8 z|H?kl5XLPi1q~JadDPLj+xqq(wfA9z9n?M8!s16Avj19%z1A&8r+s@L?j`%7SE=0# zlk_NwZL`~raxV~wG5>()&+z2xC9W5!{I0nXT_LA9)$ zmH-#PXcjy&ym3HGx)ReRt=|mN3HT)43{OM;p&kf?J~oo^=5Th9ZVa6hhzym4Ia~m;pzPv|(6gK0jxx9bQb6_D*F(0U#3DnK)d1iLciP=Ecm}?>M*n&`n|%JAowJoK^P!gO$L@yOb3%JA z)V>fXpV&FRb8gpxxj@t8_w!{tCQ{?6%ip>lyp)i$%P{MP~&f#Cx?Fb+HYQ)ZH~@_qB#NSB1o}seBb-@(!SYX{k%}0 z6YBFq@XZq!PFy^>5UiYN7;m^-cKy&?uw!26$O#>Jp>kfRoe^r^=dYj23AJ;=vHYI! z*i&;tZN6mtm~Xj~_-gLwLoN3gOWW?7BIN>2tINg2x9|R<(DCTplByP?20 z7_5SIpvhq#wT-*&tC%!94kKaYU)cxbEcw_`?ku}^kO2C6>B39zR{XST)>n5ny66qO z8MzR7N15^N&hfi}K8#;6mH=+}p8~Jd|6|~#7hqRkAw|QMtQEZgrexPOx1G`tV|wNK z5QA?jvPUkuR&2K`0ESNmPcX_B;7Rt{X!&!bD}XFn_(Ib80H*$X(iK43`a%I}7k?(; z+5)U>3Ak$4$Fw;BT;6N`hxE4&=V%`ucs3Q;3%v?3Z4Gc0_H_+#eF9+W=0jlXMgeU7 z_@QrS3*S=Ex5ETtUqm09fs$h@`1TbvvU&V3p_2_jtkeQx46TVhf&^E|L?9z3s***( zRdB=MOi6mYm*D}Z=O^!Cxe{TxAE!ow7MKtdnwU`&nwnPRCG;H@MuJf?yv>ZytN|-9 z>aj*72aupAtpg?2gs;s=4goQQQx>wbZd>rGgLNXogBxo>aumrokhCIc0}|%h6UetC zi6H3!qT$2^gkz?Lk>|`zfap<Ma0hYR7Wf9U z!+#OqtRa~9MkiZ82t;fE6P=V1z_iYnw&qG(5iNAkMSus9@yKO)CWL4t7pgCypQhZ= z6CVV+?B2&FYq0mB`O-tV(nHw$B!wG>&G6#ufi#PyttOo5pt(R~b=gA#ht2ns@ZPlw z)BcC>&F^8K5Z|<#_$FZDn?}GlyP^Sdv%I{snY-CQJDXfL8+qiLo1y;JqvcV`-Fkv{ zMqIbrdEjq5JWzkTcsq=98!(EGzb!TZKjo*<5`L;gM81(io2e$iPux_qv$M@T)#ApQ z2!#=*sWV#ao_1l)w2wmjX}~u?17*isxaqw#TJM>DtP%J-<@9)!=T3!)diacJ}z^TPNN0~2S2Rn&%d>8nAIAj#Z*3M9| z7z1cQEMOLltLb=_F}TiZSUX}_Cn+9%&%6yYTyWrlW0pCp83BxKcwcH?l{Q#WMzdmt z>Hm0YGVc@3GnN%e(Em*NH{Y&#+ES{VO)Bl|yU+xWm>NcnWsXw%A*ua{_&+2~ACfZo m|A_cL;d$!#8v(N7chT~XgB-115qMg=;@?5*?zICk_3|&IQBmLk literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..95a32e9837f02c6814e86a324dc6194fa7998b52 GIT binary patch literal 8766 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 aB0OuC3*}t-M0ufnHh(7nh5Taang0i;7IpOi literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e19efaaf60cd70f54d0d82db5e53c68ab931b2fb GIT binary patch literal 26910 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fb877a3f76c8565d06926f2e6e93b097e57d4d26 GIT binary patch literal 13981 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5e58ff1c6d22709f3ea075b08bd0cbbd74aefff5 GIT binary patch literal 1394 zcma)+&u`N(6vu5RO|y=z-7gRmg973Z2?i5FTo3~$5R+gWHi4)FCe&UrBu=rNZrv5G zh<{)gj{7V4Be?m5#EA>yJZZplx_0HqBy8pR>*uFu`(r!V*jNQY`S#(-%cmuTzPQeC z0Ux@75Z2E?FvM_zQry7UX_Alzf!0gB)NlCU^I3@n+k}-_ShORoQnaJnUZcdO**L4d z^&0`G8k+!B1~tj1K!uE~qG;wT%z`3pM`I~76C069y#oEorL?1NwmM5$n#Pi?3i+z# zLJ|7>u`d=QV!c#IF!5&Va#22g?2E zeqck8OAf>>5C*-(hA_;?JMzAUk@1-4HZSlSg3%*@<3oMpIZUf)0qNNdK<4UW_?#~a zR<>r>#U^Lx>oSuUc-l7II^!MDmcDlC?7-jV(6;1m$|iw?VsW zxHhovSZJZ&kfuf z=u*XB=R$E!V_Gbe_SRN~j5+{Q!!>;7r#cGqa8`F=^fy*h2dr3Nd{*A}4tXWY_kS*zy4!lg^Lv>qp#N4^?&?vVjmdoq_gUgtDU zl5B(K60&H{Xon|RQ<$!;_7zP;DvXXwYQm+TbY{vK2iH~~-oE!>X?g82y|;XSd2RK8 zbx+N>peqH_{Rszovkq5EFh0y!@i?LTdvd3yovC5sDuK@<*xwC)0P#5(IGp$$82yd- zM>50+`+EhUli-es8gX;Fzgsk7E0nJ1@VYsria5VJQhTcRm{<#L zgP0$@20g$3$cmFzCtSR{=LLG@vj1}{;k7hldBU$_h$znQ2nxv&68a$yaj;h@S4!j6 EU*_jhCIA2c literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5310bd1461f6f994862814697892b1cfa810e738 GIT binary patch literal 1897 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..28189143fe09dd3e3727504e695b1b961080dbc4 GIT binary patch literal 6910 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3388cd5f41a7282b9297e0d26addee5960fd947f GIT binary patch literal 3520 zcmbtWZ%iA>6`$EZ_JY^m;6M@+Fg}L_Ou*129IC{jrpqO5Q&IKOKRVKM<=fqGHvU6r z*CnJNCF!XJT_i5O>cXj&n$z8f(4=bnEnmU+tzXvh-5IT@M4KU5%F6xKAS$jhBKaX_#L;jM>2fO+^WG>1&=PN%BoyZ`CP zpjmXk%D7*Z{0Yd-;W=g&Ys>_m>_*67zJqS!Stdny*wr+)P6Cc>ciSZ)?4CzRAbDt8E2D2_8436!%WY$~?ftZFLFs>7G;8==(&Q`1|@k_X~U%+v_$J}9m z#q*%q^t$q?=NP$&w*1Qjd`_(fYgcF&?HC?QGN)XxbbEQBAokEL@%1j`YIqjY)SCNJw zVs10H#36L+ukaE0z6D?BG4wTL;AsY<*I^foZXLVIzK*6b%pH9hWqB25RA!EuMsqCa zV8P$Ru8ljRdoEX)i|9}6G>$cleNJN-Qc6%0QwcR`&e%diRrM)T*KF?_>6|*LYlJ#@ z&i1HI1}$To&m>Is&6r4Nc-yRD5}PyhEUZWvGg&p6vwbRLp{Xlo!kDyqBdzOGHmBvX zy3IRUY|qq;3E66UNFr(I2BDD$f{@fkjI%wUl4fzBYJozv>%{hwN|M`N4c29K&7c-U zpgj#!bw+70=sk{L-L8<78Pk~$dRh_7?*R}6C(~cR+bYfCBl=@wi>7@B)E^jK8 zgGgp4l%#1W)ve>ha43niF%owb2EYX4pV0jaZC^+0P0^1|7n{2N()3Bw>3Ppupm}j} zVREUb6gXZMj$6X<^-!c7>Me$P@BQe{mzFO*43+xiQfPEOupVex%q(P<&Xxk!BM)5xeUM>fRO2MJKKmN_oW)o^SvArRLw>Legp?!NpjBHoLEWiA8BRI5O z3=VzyC^$6lwSq%y(xD~KAH?^?-vun`ge9E#()ca7d9!=ekN=3}Htzme1mwy|EQh(3 z-jhJD9>#KnTRqZ2=^>BY$*#VF<)~+Mgaf+3h`=ud82JoWIKauLJcVW~4>T59SV|w^ zD8CI;nXuDGoIZ-_+D=~X^A=)WIy-=={3%T3PqzYx(7Hir5&A)lCp39&mgKW=>sdgB zJOs1oUh-7lG>8ZiF_whssy5mkfzE*9MuUm8!)U+qe}xH&f(}}m3>~`b=--Tw?|RaT z4z=I8bo@h8KpH&J=we7S{p&7Hov-pnxU^_*8>m1`v+>DhP)W;0(JD&a^>! zcrnhnb!T6U!7r%rC{;rl>R_eSG-9*C<<0J^MSB*MU#yQYeXhO~4U%2Wq{;3yjd{Sj zHJw)eXY8n9>i>cTsT$I_ns@N4wtfe3Uu<0stK9S4W!c^n+83o}5Mav+e0Smd=^p&K zT*II5!u(6S=Vwr5-a#2|%;v8oiD6!A)~_a1Gd&XlB#5Z+ddp{YLTH-A8XYhp6s5@l z=$eAY~6^j8VV5z<0g!HZ^Y>MVtyZ^I(zZ3qcY zUpaQb*<5k=vn4|ZTB|YY^4_TlRg9nzLUfhBGZSeS0IHaxB+}{Jw4qczQoy5{!o3*L z33eDZbafa}YKrY3L{}*gEelagh^~b?%Ar^>6f1>J0xYz&E{&90ddgDIy@^NCpd}1i z(%?pGq}AW5)&J^e{&VZW!%I>r*j*O8EwP&x`12SFzSQzu*y#Jz zAEzqrrhOd1f-Ma2^4GnEL4k5cFvxt2_|XRM$GjIf%M3gzbTdz~Ha7-$ z5@Q@RARS;_3_F)=&?iq_k^idqOa?&HG58D`mDkZ0Bl6u(+7K`P`ofJ1w}p2bKjn@R zDnR<6i#6DdigG0n*6E5upfaP1aWt^)HZ}8eqUhR$4sjnhNg``Z<%ns-C&-jqNe=o* za)8Czco|hxJ7^^gXL4FTt&fm1AVfVhD1Vd182=qbAEUry9fSY? literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..92982186e71a616923b8cc4e323602371c01064e GIT binary patch literal 1562 zcmb_c&5Ij16n{@M9?yEcOB3?3q@|SHd~v=Yhp;g@*)8Z#JPL0)5x1(!I>FC!g&1_mklX`)1fmXKCh4Y7Hp%UJ1 zRTx~GD)nfdlVRZq4#DCajtJr$PJ~1$q&G(C%hvB=;l0Jz_!Mi1;UQY@#Gm64Ebvg! z>XyU>?gei-h>c^Ii~F>X?2Pd@VXi-dS7_U>fxI_m_c&~3kI>xZ83`2Z)^K(yDYfbw zwO^?sQIRPtov3>^T+mpqohxJFv2vZNkSfVytt7G0O0CL`+bk&Zjs$g;U`~|GEoRo*F~>(p%(JH-BHX%2;!)DpGrm&3`7VKbgADZbm6h%6u|7 zPmac?M!dKJv*-(thL9+!brI27Hj0aA%}OMvMVzNeO-6q=E0Z`ggMLzGnM&+>V0{{? zEL_rSYPdbrERP2NIkH@P>CQh(U^_Vb{tSWri+fJnW9FX8VCJ8+nDIRz&gq8K;7-x` zoO8WRYZ|yU=L}P`-({+XGkG3n!?QrUX3YhS=>8M2z1v~-@X3fI=kl-U6dH^Cd{#aY(#T-ZEAda5t5_r~fDECnSr18-DSM|9`^Bj-zL zc)I{Y^ij~)LH|QH=z3k;5#OS|N|QaITYO}|cH4EL=xIgIp^75SdeC1}<1QrmfsT|M nt0+$U%ZGDKLcP&vvihVg7w+bztTHv=7sD{3E`;JMvF&{Xht;6- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..11ee504beca1a7e92a472a13c64cf0e22c6c0ede GIT binary patch literal 2831 zcmb_e|8EpU6rb7M+uOTqdoAU(2R7a~qK#>=BHOW2d@No{j3q!Gv6LBQtgR|#)IYf44E7U-cn)C*`BAZeknbPqNgz9olE&_dYT$L_&DyXj1WhD7kJ;0_ z!4veFn0L;}A?E)Y<*2xUpbeB`^+*+R5W~MNPohlM=+rZ&OG%cI_bbFPiO$>#HObHp zUky1wS|W@YdE&<_jzvt{pu~bYj}$yoVt&G{c;$+xmtE?a0zOV5N6;s{qE7&G4If1q z+RsH?T&rrX6b=!gesl%=;x)1pn$sDm*z3xy{9z=5>QLiQ8*O{h<>(&nJ9-!9;1*~S zeJfAmTX+xxYswc7J#F*4aC`p{3;TVsiNAs~>B(lfw^w zOb*}HhF5{CvMr!}t~-aXOWTzjO0PPHx5Ph}d)1rDcI8XltA2&ID_?6l?5kn4GWel1 zFp$Ah>$YwyS9fGsdSD>U*1o=@7i0VL#3854ba3kJ|04iv=6K&?{D?3FOUelReo{tO8Skp+dN$&zms^kl)Z%9v z@TEf_`!Z}Bm-GXm@wzV?whh+;`1<(NPg7tg(?z*C%HLhSYEoi&#E%-R>X-%BmubcE zqm(e$o&a$eI7Yc^etf804K8d@zYFrrwslI%)O1T_s7^noGh>`^iQh3C1Q4p=O-fzr zckAOWEg7B;u-S$ayte5kZnF(+iVSAH8YZRRqqCE4#kK<1@edrG#LZ^1OKJ0B(+d+A24hxyTVAE@3Xei8ueNUeN zbQf6U9{}t(m{zCJ-Q?!QaPwe+&qx!k?!qnEz17qr(_ zqDqH4edvB0jvU1IRn*lpo4A_z^zdgRi=DmGBg?7HOR233sjatr2NzQZmQsfoQirEU z?Eaa`2ZR3&UJ|x_JRI_%9hnUdN1g=kH(XKGdf`y{nXX73%oPsOykSyC2u} z@d`+Yu2WuczSj73W0dzLe}f*TUp9;`Bv+P_NeV9nKei^~xf{qR?@lz$H?HShRy5db z<2$ff1VJEfp%qX?VN|vKCD*FhWRM;J8E-s{7r>t~#`tIS;yr|B(7BF#Xym@yiH~49 H2#bFMn5L~~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ed5d0dbc5b00a29097ec107ce56aa0ed4844c2fb GIT binary patch literal 1747 zcmb_cL2MgU5dDAmd%cMhJ0)=y)K*wkYj6;DAuUuT3Q2@WohlK63yKu2+}&SevtEb) z*GV00)uItiN~BN_Nf1E_5`sn%f@6E+%*ClA#ae{}>H)YRl0(9Y`Ce~Ab41KxW_JGU z{Fygz_viNZRs_=j^+YxzBlK82cp|wD-n{_MGBS{XO_amq7z&QHm*lh}UD-38XA5Z>Q``-r{31$qBw`7Sb8(Et<}7n-!gI*6=9Pt`{<< zow2mZMgrH}`(5n^0}bNc5sc6sa>uufZ17y!XxcKeA;*;x+Gw6*R6<#w7eBHFHY7t% zV}lsJRcWL7=3U>M?*=xMS+$I9ace`es^=Y936-#Svv*fng1Z7oIi6==FC*LL#g6P< zQj%Uri?S}IkuFUH5h}|iIScl<+>;hM2;`Jy!=J|QJ%RYk?) z-MP6%cpo=vOru!K*vv^i&uE;}_Y2h0smAjKrqh8J9mVw=e~xmVN>it`U>Q_5Q;Zt0 zM_^eA~<7)^5iRN+kaBE)2=&;Y1&?59DFz5w} zeE=I-BFpkeJ|8N>4q^7TSI~n0Wzjo&8+2m|G>yKOr|~)-1G1^jUO6#pOTZw_~&|obzxOVwdwHg}Se-s+5 zs)Gk$4){69zTKI?OHvP6AyH)&pKJX}jw)-UhkS#h%D1?Oe5WR`qqs}Z|BbESbFaYv z3OFAAB{cCAunU)P7NW;xUYbHnco{zMg)V}M4P03}WC5~7EI1}RQv3o7LC6VuTxg8T zxwtP9sjv=l*wUA#7QH#i`e#P}4-5~>bwhQ23Y3dtHJE%@{PFVK&BgM3c`djVia#tK za02JIyHDX*3llCAAHSpMjHYbr_*1-S>6yGEvx4RL8RdC%3c@agoOOw0Ic1tL#ZldV z6Du;YJ*UNE6mLMlPKx3UatX3WKn4cf`LF!6Dc;NpYs2d$3+ zLsc?V4GirBf;E9MsI5S}gO=m!6wq8a2{XOJ(BCKmE~`8pE%nT}VYPl4@TrZxl@0&z^;tAUGEr Z`cuXj|ANjwM#0}ua!={NBbY_u;2)vaz)1iA literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9b312e71f7b41770485ccaa309ba939de3b4f762 GIT binary patch literal 10359 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 avm7`x0RF^<*vwvDPj#g-z^`Xh&;JkJ0@BI= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..22da0f3b7ca3c43ff2ef248a0feebb931a3e6e66 GIT binary patch literal 30479 zcmd^o33MFCd1m)a&rHwsUHWt);R9-;>%KwRn>ki-~_ zV2OzUS`Mkm4k?Sfh9V^aS4=|3`_@V}hbTLK&Tih@9sn}NV>pjqJ1?uex4V!)N3!BQ zzQ20=y=H$6f53(C;4b9QQET)h`Zsk9#v|e8+t( zjWpozcJSOl9`ggogY3JzKYyU$c)>v7@j~|Q=`R{6K3>dVvA<-X^mr-HIkSu4Udhb6)ACVgo)|3<0gs>HezXH!@Je%=s!LtR=l~G^c zr?g(M`hB0azG<+$Qf`%tdU<(Ok3(K9I{>X=&{_j(%Ru+Y#emk~>pe1s5wu?30B9qF zD2$*@EWgbR;t-}mTXe|5MOVvPU*)y(vqHD!gl?Dh^xJJy?J!E(VZ_`a>m}{5Ps2iU zOS)HXlXqTo$h%PM-7MEV4BCtOX;8ZXQT`o0j*}HS24#)RQF-45;nhq>A76tJ*^d!f zJL+h??=1>#<WFQ(Fj&w)y&DlOU z+UiJG>>D_v>_fN``P9i1$KpyfGH`5YMCp#E^Xb#EsB)&aJ9+qC9gv983NGXPo4hu(m<52|#7g-C= z1Xdu<5_%3Lr-DTjl2Qsdt1Rk}r>U%q0FH7zE3Xf5u8XT9`SQoOKXjhum2!M$aG#uBo;kzt~)wRbD-71x-0F{##0+kv)cxv=V-jLyG;wMMyavw z@ctY39_zVFV;9F33Y%1aQ!2msivKzPe120hziBqVdBU|Im0XTqdwfn>ohmGwIGPF; zPxyZxqoI4Moo{#YS=~*T%!alN01fRq-mW8MK9Prka)c2}bEA&y&aSs;$7K zN_bXWaBJ9{$Ttx|3u&T=H1cSR=${uWX2goC4<*G~U?0$eAw^fKpcGNAG)QT;5{-{2 zgK42_XfTR?TuEP0BTOX?rdSy`Eu0t{>c8%xIWUN+5e}z);qbtaJkn3FKOBByB+{>a zQD_z@Rpd32*Fqj4DXRk;8&(YS2*FvkFK|l^hpTo;nBa z43d5#@{GZ@{SRr5RIz6 za{jsV&p%02YM~k&C}r0zJNS`$8p8^n=Ti~q)EW~ zd}e$ddWzr5JzH!NGPkk?pVH1)G_GtVNH~kuDw_xl>|={ie^h3xZ0@vX6UC;pG)hGa zVr&;YjZ%sJOa4jB@baWsJ||YD0)>|bCkN*P^~pf}Y@l&oY*fX@g+R%CpeY$>QpKi4 zC+BT?KSr4K)VB5l{?GXKpzt3{0NInR6Nvb6$QHWVW`2CoI>B?J4rK+3!D`LO9QBv7 zgS=ccBI=PS&PF4R;!&DdGdw$rhfWGi2Ij@eq*ys8RvQ#hr){lBr_(&@Uxns|vYNcL zJr~-PH+sz=b{;j4qo`v^XbfZ7XW56NeEe=7oouRZ=-2eA&00OdE>P{oSnRWUHS2~jnS&hC?3>{U7t5wrgY z9?E|5a;XkO*-HDm5zand@7;#F3x%VvzysK)-&ATe^xE(qUQ?x7`27-{|m#BSK9F`r|cvI+P z5}KQlAy-zoS9U}BAjm%1LlWGm&?;plw`1{<6Ks+J$%0_Ym3X)(8kLFI#0*N4K@Ux$ zyU40wGDl+$JBiNn-74{AXeY;=1d@d_&C0w{Pcz598G;ELePYg;V{ab)O=q!0O-yt{ z((CV!rSro%En$puI-rFrBx6HB)ZgP8xr?PG?K_h#)4rkM=wL?9bTY9-c?iX(U95;0 zRXr5aWIBya)5J*XPh;wR8j)gzp5xpd9|uA5!1MjUEomXNVrpnMv}Hn=6KhiSjTDw@ zT}`i5sSTU7XH_*l%TtwAn6p7PQMxclo=kPS^{%ABp{{$H@Z;C;W-J2;%yo8bmTU~r zT^TK7pv?SCL7t^D2FQ$ISC)w^lnSDhAyt~`WJgAnG7tjvh)8{oi}~UtOE9M|Z^wvs z*~IN5eksT4Y0f+2#I`Uu=8O{^X3iq#Iw9veH95D{*_C!NL{W%cY4vHi<&F#kUu1=b zTzQ1N&ye>pymT-J9hExz6e6!49^^PGGwsPBrt&DgiN-XfZUCxf^as*0nxvWnC{e-$ zM}~8j;1fs?`!YO6&ldfhH}8@(DQW!E)va@4BNHmCl48}o*pL((rVzIJu3_afV)@nf zq*(JNurXLPA8eWlHZ2r2%oZ{6x~OcvXl1f!<$Te)WYN0Wq74%w3D~Dz$(xhfQiYWh zU8!K%L@*;@cdYJM%Nw|hI+Bd0$!XsOaE1HzyJ;$Ha4;KlflUU+P{x`wut}dUgdsNA z8k6F*NHQlob&|&PCN{DABt5SA)bZ&UMzkyKjxp67Lz2WAO#>A&G*BU;E>SrY84Qg; zE*OSd3U6AHR<|bC{~EGZK1<#)cmQgcd{Fg2$bu%nFduA41{-FBB(*T|g@%0&88d|iBc_R9d;~h7N1@qV z6KZQ~dt6ghv{K95%IA%^G$9iHp8g@QSYsj5aF{v`%$^=2BGY=%>VV|gY^An3)67s# zl9wYA5DTD?M-w(i{e6L&zozYNkL0SponN%*!gJBhRkkit2)_9OOJ0H+xcU`udxNg> z+XcmoEF49ePW zl+GMsEa4t7pI;*0*s8^_EU?@de}mVl)eU1g1|B-QlwPER=GP4-RYozitgucQ0}-mzy;uM&vg-*gYgi<+VMeVonQ( zHf-6{`PMk9{?=`*MXlbnAV=a6ESreco%WuNj)t|UuCu*z{1jEJ_v9()L}DZk$+5I+ zFfuq4Q~Ib%r{Nh~2u)9#cC0&o6Tn@%Lmcy3I>$Zc$?BX88L`CR$AmH0m}AU&!})54 z>oC7UqH{qJ?vceCUj6GBcfwr7lcEQZgxL;Xw#Cs-E`JVvWUOU>I`F6BH#* z-UxXLytE&@vC$=jm~fxls{wm;fIFNC89%)Dy6fcEULg0UdosY^>^ZrSxmi8r2YWI( z-QEK+)X{SeztOa#8_(|1w7YvK+S3!mycs}YX{mdtAGoKDiLZAsu0T24+Z_V|i#`z^ z9AX#)MSoC<4h+SkkPyK=L3OJ&hZ>;}$*?rc=nAtVn50H)nC5hqE+ngA?0iI(-#`w} z;urfoJfL1BXFApK20Z8cMN}5syCD7fbiYmyjXt*P}OMG_%TyZ0m zx$J)th)}9%4L%h676V*<-JRAF!rhV#?iyHy0@)-3X7NjZju-5M^0GrFSso*Q%{9jV z9)I3p!3YnSn9O2w-Z{pNIn8~HxWTpRivhl7FplGFzh>|VUkSIF>mw;C$6508=$~W} zxf=`=F(|FxuC%8cyFlS+?0r#Nbk?p<3mpePwe93DuD?BV|A`0pWPr~Ta){trhKDff zX|EimeWP%%oc0g*p2Hps^bZ4(c)G~YQ?Lz_-bjBqM%a*9%fbVZa|nPco3^C>L93gv zx7Djq%^ATV}+Sl+`C(4JV@hZ^fFuB*}R?Oomd5m%f~w} z+_zBO@ZzEI&N-oCp{DW0&k|I*P}B7F$6t8-#V~tIRoAiaDXC!menvC1@Qx@Nqh=3c zOrLQ-%w-qa?KtN+4fa8C%K>`ETvQ6rk&k>zb27neA8EOA z9P<8%F;6<=pqzh_m8rGpE~St%BgfsP6q}eRr9dwH2&ItrKF7&L#3S9Mth>fXiBL0q z!V-(AM7enAzi62sO$${J(R)3+(72)v%;ImmX$c`AAif&d9@X@U0o5GG|x2Uiti8~@`PBO>nXj|`lUBfAq1F#{Eoo5J2g zER6jga9IOGunig-?Cmyq?r>yK4i84pB7ijt*|zOukz>07Xj%R(D!{e}-y7$?5`1O< zbk%Ir<~Q@VF!rPO0@|`fZSCn*V)2LHB3AP)n(>ean1I5Vd4q+2i<-`?S~T$t1}zk8 zSaUA2Yw|oe{2K@YN#|?rxkyPb4Jm@NjA5DuH`9G%Z zGK;h|K}>ks6(Mv=yCc{Lj71?yevOjKl*Yw|kz_Rzo|P*Ii&2}2=hbm7n%Qvt72=wn zX$a@~g$q*G#xm1!Y(2o~xTJXqa#>l-A6bHYZD)Um2NxrGQe*jJSe>3^b(xM zlOH5M?Pl_Bj2W3}Bk^yLm-27Py8tg;plQ-9GfkO5ct4?U!?~wQB~BTn$dlxK0p4{% zd7NMx7881lP*b@KFN^V~TsG2_p_N(J*FP?T{e?Z1%#HBB!(RdO`K(WXF`QwPz?4?l zsCBfhQ$CM`RuxX#yYnP8isy!v-HA{A>S&6dr--R-ZtJRn&g6yzQ)eg6UX|yd<=b%J z=L)F|TSIAqY}-hg1FOcklFrk_!i;f|W-0U=h{?2I8lF3qkwGOyVI%;jh2bG3PIBp) zXg`#sX;D`=q2iJvN6ej4Q{giW>?0J-iOnUZ;BOVv5~wp2H`uxFK9%;d zS;#h@h~(2$WG~7$DHjqnl|LcxW%Ae@#Za?38Vc>*t1St8e#t>oi0=P2elZ2u^>Zju z++0zaTDE?+aKre~ln|U3%929af>@FggFh6vK$qfjLzhxmwICLx{J~T~MXIziRa}}X zEQZ#^>sxY)O!h70imI<2o{CKO&Q))lE!;kS^ruqs<>OOTv(nn}{dWYns|NCMN%fVZ z&mDb9eAzc!y!wmxj~|%W@>6DJ_t`nASrwX9shM^Kdaj+ClUh`vMU`4oQvRhKlRK`K ze!c32syS)hv^XQJQ-yU&=Bv`Wl)vCo*JRhczcJ}=oO5wWMQl&#i)D=npim5Yy@Z?KR&bPEDTiR8rBPAB$|DwxzkaxesIY|rTbdd+h z$9V&qZApDntXCU0CdExDF>on38Pv8$)W%IoaWfJlvYV3v6HiQ(zvFTG3n>%w0Erwb zx_ff>ywsGGn$#8BlG66q9yRMG6n96>qlm!E>-ACGGCRtjL0jK(#q+s8EJ`)|sy$18)+4bGRYNtUj;(Kjb;SB34WwB5+3J}K4DOU+5Cd3xoHv_Ta%sM3at6wxrZ{6Ybuj3R_fZi``cY(>7Jurb^p#zOG41YY=+ZZ}Sp5Yx^SS-i?8C z)4;iD;0OwE{?d7=At^OXMP^KlLKy4ECuXFTs<2X(R;Dx)l$pxCGtypF*sDr=5i~DV zC8esX@vlGm!jp5-#+&ss(neL-m}I^xZDdhulTz)}#%br&su|Q%KvmZ)NP%Ra^Jdfh zh66Jj4$KBR=cGnkMG#Hbm;qE)H@S4g{9IZpPPC4ukyuozA|t&W{GM)q0qwNGWE*^woto3QF@?9*1} zGy|`FcG?oBSqsQ2d0mO(cys`3Uql%V$-S|RcJ(bNJKb8Mxzg3cbJ@dx3x54 z{GKO-dk26GBW~fr^FWL*WP$&d5N-6w7p0IiENv8mtUJHQ+hW88>xp5D{Gl^EmpjbMz+2(IMYHw(9_DhS@p!&knAc*=huk4YnpSta%t zC`Ni3t)GI7ZA%CDX~!pw4d`q>PWekpz?db*HZcaOfWlrU?-qIAChxoCkr0XP2fvBw zWDRZLIO}vUftk}P*>X*WVbYFveDonvQYcfRQ4rukI9>o>6IR#mo2}VDTXbOjzJ>gX zYpqiq(~rIW@Lc7Q+5Ds9hZn%lC=_D z^vqq9AeKeRP=e^ohPmlL3)T%mpRqkWmM%gsxQo2(o8U7`6ZfG@(=r7Q&rS07OVdfK zs*1N=oU&usEKahRlgo!JJRshZCd_xfUy?EN=#9auBXR~bB66ZX%C_5;2hs8@YEA3` zqt@>Mu%XtxRkd-RGnG(wRxWW;J27YN_01f%fj)5Qx6oPcgKVVq@s(XS-1;~g>JF!F zV$Y67a{71Jg_GhVuaR4iQ!i(681!?f4~3p{evUiK6U}b*F!NS6^V#7nC5=|V%*}?6lH|HZ*VFFdQ6j}S?-GFxbxdJPBWW%blg<EVhF3OEm;Q0x^}5;C9kY%1zW&f$;lX+TLDhfoT_+c~k5}`Kru+xr zGhTkdbdgWDw(sC?Y-n$D-m2x>*SK%h2?RHK5qxV6PjH*7eXHlzMh`+X)x%}#5Jt^O zWeOWLX5W%Wp^R!Xdk@xYjA}Dl&8RlXxr~TIlle`8&Hl@f-E|+g>y*DoK26kHzYT!s zu2c+>^DC`a+n--EE7twgUzIAaN!4zKiAi2y(dnx2-EoP2_xS#w6+2yrc*v+O|1(Fw zc=S2(dEcB+MFbZ%GK2gl(;bF#w=T~vY`Yu=cCSY_c$!H(J4z4&J1nj%}rK2dIz?h z*YhyScT76%=pI;ZkCo*{EL+-VGNp)NOZjB4RYi|j#d>?qXBLa?uw%mQHc$I(SZCS7 zaD&qNyWXPijS*iF>4BzcEt|t@arjLcRsJ196J-`d$Sf+ni~!RxGXnfS0N4;apd7VL5XKb0-?BUTEk}M4{V;JrT25ybF=-B(g!o z%0gsGzl<|(^A)Yhiq;z|Zo0n}{6=tgUHj~+j_^0EAkt-aPByvf!_p_18cr zMwA-L7*Ohb7i=71X0@C*2C1H@g!ahL59zv>Eaq6J)LF_FVw!VVYa5PMUE4k*wW~sV zQfk+Td4?#XIqVd=M$CT;DEo~6nY?EL&jiWF>tdcmUt@?7ZlgYbX487w}Fy zJ?N5LXP!NgVKJ!dVzAHiVX@t?cA+T)2bRoOA}s7OEDU8Y>>h)!Z4{Y=``?*V=ZpTTS$bKBl*m=*Gr>{yc z%Y5p&Kj(P1Sm4f!=RI=KdGDBaKlgYI1eHJ>zUC$Z=#%0L;+VL6$64W@EkDAIiF$5N z@d6jG(^JYN7w~O)DytQUC3e}zie3KUv8n7!zD!?Wbvo~pE5>|hz>+^Jj``4X2`!h- z`{MO_jmIP%ORRO2E5{_6CoLG#GOGr;3L(|jkWns|*?(5mn0>khrrUAea~3{>d}py; zq}}q+trtvBn!82Bd=d9S`|y*Pt8^gbA|STc-phSu^I2^#3>$faLuawmW8GX2M&vU% zpo`;nVRmvdageP{nVqQ2dV>RbP}!V{;)D-fv;o7`{@$Kw_h@&2)a3PZGJ1@NBvlJU z^0pYbyf!J;VYiZ$FO2S-#3$xHW`i<@yw~^Wjq*nOBf~fp9==<<$OuyTRkVv`L|c>W z{2Oam!R3dOV#S0v6)ac9a@Mf4-?T-*u9|;1s`O|_qoT@NY-{cJ(C&v775-a9R+Q`H zF@CI2>+le4OksXLGK_N$%J0+H{{k=f#1>7tuq?Uh=+5-XD_3l%{VHbQ2zUA46YyHy&{BC!J}!mQ*2B zBn_m6;k)_JT)|fGeD#}W>+zfuYqQJ?0!5cQ4LY_EZlS`II6UI-Y?DQjTjq;bK%SsIx6Rcv(q9`oV76YOqr=g3JOrW z_bZHS$fP>NHl;(%&>>No{mH6!O&Fy!lW8168X6u(b_rdZrERZk;ZfP%j)9n8eaen; znkB;|?uR+UeSjr{Q~6UQOM5bQFVO9rQg9bW=4@p~w)itd$*A8y0R=aVA}$hQl_4(; zvDCs{-#9c~c6rOy&&(AyedCaJEdA+#%`FNWi6k{4WwxD$45lxub(%iQq5LzVb}>p0 z9TYWC0n3+VtD*dg+C=J!C2A8hzEJ~5Z>_v}Rt>gHZ=DNnxV2KV$zVEu`$78{jrxVr zD8>0N)S53NUxifj%#`GpuJQl+m#^_}QBPR<|H!TKnM0uMM=V>M{8IzoJfw6U?TTC&rm98bCGT3^O&esn*hBFhL&5U%h zmK7sZ`Wd*GvPeb!ck+0`i7S9&`S7qYXSls^n)OWE$6I)mGUWLP4c{!he`ZG=1Htjm zpWz;fQW%946ptS!xB{>%U$dOKmN)h5H=mpn4jQ&JM#w^O4K$jU8?J7fj?amkG`T)X5EO5Ef3Lzh|6@Jj1+$IZv)8aign_o+e&?J+&BHf^4jwm^^;L|5%% zG3U#_R5w|7t?cX7FI3+cm~Y#kY}-Fk_ony2f*7RZn9m)0{>YqI2UW4JlZRo-#_iu~ z`;)fWVEZ@Prrnp_^ZE73{Q5WZ8*jFaA5w$uiw?2etqR3ps*4)2lm-iDi2%ZTGq_6? z559J8Ry?Q%cj0oGqDB}F`GZNKCbNN~v(nM;K~jHE6&_Tj2buZ9T|WT-X9GuOr6b?N zX4nI&@PH~k@Z-9cC8wj>{e!xe%Lmofo!@JjS^dyO&((7%3f;@$6Dv;*HqJ^-SzBRs zYU9?|_+;Y_7|iHqE;(WN^6q--0@Zuq7w@>a{0{!-?}U(NtQM&9ZBJF_dgr&dm38hC z{(86fphNhsD3HJ2)wu)GA)@GxFbk45zjO|v+DV)bmFY=y!0EC#ctxbwoZSYEex8g@ z65;|qAAHF5a-JNJgE#VD4QMGZdQ7?B4 z?`6(TDQuC{sr@2Jj;Rc(SDFEv>af?6j`sjO)9oMAk5oN{N$rma zC5TVa7-|Sq^MTf6pcR&w3;z09f8&zN8T7r&IlVp@U3z`*v%7zuu5IOC;#-8@b>mLN zpCh#?eXxCiklvm|c?Q3B<>FZ)5XY6NIG#!e&SSeaCW2Yxy=y}v-#X&EHm_TkD6<>W zU7I!}aAp+8(s$9R^@NulNZ++()4GIDJCwd_)3$XQ&lNCvF5HiJjMWtzO|TYbf-~`5 z3T{B=6d!pKd2JMCa9fI>!t%%?OCXpj6%fq0s&7)vVtQp_H;D_Hs7(hdVj{k39FNZI zZ2!bFTOqrSE0ik<+COJ}kCnekZoO|#xPQT4G4F3q`kPZlD^i6;IQ^dr=BLUkQ$@w8 z(y~+u-AR)wZA{hFVboKBf>f~ZPE9b+J$~R$hzpc_<#W$J`Wio%w`08Xp9I0>!%5nY z$IjrYoSG|xzdg9*67tG6XpX~lGzX}WF9$u(1mX;_gqP2<}=s&J1g-IJ2au5N|tsVX$6QbS5Ay$buAdR3@brFzRZ#CW&S z(+SJCRv?44&l^dfHY zop59}IV`54eS}px&Tc;$wl)qslTq1XzK9qGn_*^|6C=amnBQ^S3x|=dd&yv7Y}gQM zKG*^FbQx-nZZfX1*h)e-Va=Y@e~cpk|88HSETAWpRlp|Y?+9WWmB#_;wvs=l(Emyv z6CxSS`XhQ}3P47aeu7u+xQTT=(bRn;;xXGDSVi{7qOhMC8jhUA&3BrD-PARie1*P< z{4FXKqpDccec47d*Y>Cz?*D#X*GIFVc>p#vUAheod-;X(Q)&ro^Wy}`ls(K8 z#!2#xDHq*^bS<-B2kCO!k}kpI{}IEEbY;h|Azd)1aaql2?n1iUW=W7H?TDKt28f&3 zmB`n$w&tWs6le#EEQBs=bpGEYa~5+@gXR5Y%n03@2_}x*mePGB=KRAsF__iIV~M&b zZW$--dB{G3MjMG1DYwb{3B2VLuZyu>_Bn(#lf*DHOk#VBpc6x9K`=Cv7mHk_)4#u? znNJkSLjP_?<_fkMMuZFg>Qv#ze{wkkzB?jURQX|sgkD6%sU&!~yz3DE-n&LXh7yu6 zW-<9DN~o+sHE^9uMyHptZO8S9MA&8B*4;w!1SA4Wnjv{Rf0;f}HlgevP&d$~AQ8ag znrlO9+uqsQ_Sxc&@%tA_YOg)1-qSH#w{Ny&|9BTumrZreN*jqJy2(H=f4PC+M<$8c z=Yq;@Y^*CX=cDRpq}8ghIw`GQP9mv87>T4UPckmKz*)(k-HpoNww75oKZZQc6wTz0hDV%ugMUNK4(ENN|Mv^`DkqXj+3=`FMFFB??JxIY{% zF|1Y04D__iIy`8w>`uVnm6if+YRqzU=1oqx%3=2g2V>^pPnutZz4!8%azl2Nn7lJtF&H&p3?_}=1 zB3ZcNmFRTm%LB88nz*xSDC-@=T`H>!CX-oH)fglM^Ys)OHO&A;K~_lv?CLK(qT{C=PCMs*9|Z#%2@ z2b|xo<@fvD->wq~Zmgp4-TeMa;oEzBfWPCT9KPe{@%1|a*ZwlkcZx)Wiga+U+XU zofJ1|AwD`hl&BjSJPpgF!H~Z1uI;U}o62Bpq$poPf<)0EE+!%N1V{e#+t(5f+*ZcK zm1ikRi}fzH(7;G69y$>XMM7puC<1}^%8w8mg)1Y`M3uq*SQ5~m{D?*dl`m7S6=p`m zLor-8e^58J3QPtxF4O~bxFn-I%_dF9#iZI0!te*MG?)>}64 z>?#S3;k<;Jf~L7ghH*`1RQWMp8O2Nc9*OjiXn)3u-J}Qe8w1Id7Z5G$3hMM$HZ?L9 z66U}d2p0=SG-K;ty0$(Hv!7lnP*x70w_I56W9`Tj3c({h*Vs0lQ0sS$A4v&;d7&gJ z;N1S4Q1er7@KViW%~ulBW#9L%Ul8l3R!(CRaw}~@ZkYG3PkPtCACr*FH}cv&&RZT= zdw#ZNixx6AVnoQTdM)dPi?ZjMVT{VosiJPVE^7FoED$b5evYui9 zuY^-xG+1ox62iK+4Vx3r4Q-p*wnsu(4-f>7rnNam7|OHRlfi9u53?1|ytVE{3R1*D zrEd-y{Z%UMlEr*WsCLU@8JlTlTwS_Jm+E%(w6S9`?BW?MW!90}f|zb+h=R$Eb7{C) zh!GDBIU=}>mdx|$&Y3Xo-{@6_1_y8>Mi|@nxOfO3WY{H+#+0K#ne~J+Bd9w#_Ppkl zn18#XG1b^g7brHjq*kncn_VH{@&??vFtL>L7iPHVrWvVC71~r>4J{R94)Awd&OaiI zol?a4M{7KG-@$(4eh9{ah3kso)F=%)V?bvOD7!?3{4UYh_NLklWVh;LF_w?GZr~g_`hGM9b#*{ zpWPwyAUo;CBDGRN63LJg?GGf%GcoTQ!XG$dSDSS9o}(L1w4H-4YJ`yziHJlb0x~A~ zqv(q@A>6F|I9r!2HKjsY+vra|^^8zf_){C0m6^Q?+P`3T!uD&Y;bPj&`Yx<$IBiEX zH|+*YD-LVhk13>CyK+=M8O7f=Ys+W@hm=@b<|@)QjV`epI<>#R7E_*~BH4uc3(BJI zL$YA=gq`+Fm*z&rfroDF#P}$mf}D@&DI!Uls*kawo9T5gc?Zd3nm=|n<2UG)@u`1H zP>Q_2C+{E0Ba0EXeZo|BOl)ObMp_t%D5uj>hSDm%iKs%iV$QQrO!FzXA0YUKYtaS5LSGRtB*9*JuZ~)&el=!N*apbw`-RhP6inlp< zizu?BB2`iKw#Uc2Z|4;(y6~hMP0JQ31X=^Pcgago2&Nz~v`m#&r3#8ug(bB6Q&5;H zE~nIG4ZIs0L5og2>0XrlMGrx^2_?AbCCJC+7cWW#`6(=qpa7SbzZfJ4_o)^v77$cO zUyBGT<^qL_B?OgHZK#CBa;i-Qprs>x0bjVdav#qhTtC2jqE#~>^q*B?|_z0@BzMc zsiBBpv$Up&4=t|Y9O5%oU#yzv%aeS0N~pN)%fH+-QL9$$P{n&wzLLvv)myFBZd7rD z%(Ay9RBwe^wN4e+r+npC8&q$NT6ahl57S#cOLtHe4^g@^s<&1R!6;`py=m#Tsp9sO zuWqVX^){<3_o?E3dMi-9jp~ZMs@P6%g{rqnZEjb^4!pfoGBxsYwd!qC*Bw>G`|Luh zu0EuC>(u(4s<XZJRDgREK8uF5E3LAR+Rd1`hS}Uz# zY9A}D6UIm>U+!Cn>TOY5_o(9D#qBjN_mV5*a4dH4BEK!=uUvHEc^jDJ!jlc)J03tw Q`*=2{$qrOSDz#4EPIrIj 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 GYVE(e$B#Dv literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1ddd4d58e285042e55f663a944de700fc6d9868c GIT binary patch literal 14788 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c6b70877a947bf246244d01e61627b81f4715614 GIT binary patch literal 12087 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..baa5b864877213a97b828fe85e26ba40f145593d GIT binary patch literal 3578 zcmbVOOLH5?5#AjvUJHOADJm8%%U(7HG{fJypbY@xTiXLcU_^((GaYy*DR zzq!BjWZp3TgTnN$2*SEynBrf+P=lI*5t`eki989&Hi360$b{CmrFB-24RhPMFu$D- z3)=IO& z)q7sxQ!TeRk9jSlUfuA8@S}b!+VgoFg)EZyJnnm)fT{T)_Nd#9qn_V);WKYmBiSRV za-7K_kCe3;N34tcj(;XFIr!g1+dTtDg6$}w8EVlioIdxQJTV>{JLV3d`CTFw|7iTf z1UW+s%f^mHi=caLMwg`obZ<{|Hq6V>66jV_U7nUfSD+Q3MLM@^P^)jk_&SbL18h^> z^agM;+Z*`q9^(Q`({#Gw9U6@ z#@cV{tMD|M*H*8sKCuV>fCYZU^wB7jtc$*oY!JmX(c@V~Jmg({VU!$aC}7O##+=m! z``eI3UFIfp)*JdLIO(A;A0%s`Wz=C z;o(-BAFi%`xTegjqkNBhA-mE5Pb^JYLzSnktV&aB9hxc+8=$j(n*py%d3|NJ%hIpOb%9 zO+#jA#)mL^PE6y^kn${eo@Ox7-8CA9Sbk`l20mZHUk6xrlVd&wn6jZKyAMYH{`pv& zSQS0#~Um7jRI2;<$F zVL)9!h8br&5OTr|lO*eMh$WvhIs@Wfnr~NO4N(R%kMNnKT=@RtOa2ZhPKWHTFggR- zg#s|`zu*MW>7$&s9Cy(nZ0CV64~cLK~cm~uKWxXKJL%hSMM zpGJ!qGdgbUgUl&$a_JZZrA<|Zeu#T6+@RxA%Ay}3XsfJFOWK)rTyO+~zYnCA;cM_x z_Ar7B57~lI?bxRq@ZD(xh7gE>NH?80anEM~6+Wyh7J;NDGv~pJp(3FFehg}er@}b> zs(i?V@cN7|!w6phGP=-*wgp<1q`h&{iG7zKJXBX2x4IQi_m~>#AUx%4T3)Jt*ufQ=raN-W;*Zcev zy#lDZ1i0}J`AJCr1%w3o$0jwQxSiL?LqGc1fKW{HB`r+QFP@72TgTA1H-7{hZ2Cwt z9(e(8!k#~aImcQ(1@`YybS6Yc%O@J_&d?8}A3@njTTQ4Wcyrf}x-r^PGB@U~7k2#q zFdhomqm%=lOzgpOi4QD7Nr8}=@_`nN)dUw@XNw`Vr)VUekEZvFd+NLup$EejlsjSfB zyQ-vHCMLN*VjRyoWfuoUZJff#;QqsaT{l4`FgcZt0x6qI#LnB7>@)U)eKuDircG>< RWRiY}WXTcvfxMWn{vRrXt6Km7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..64adfef5ac57da2cf045b795a08897cf54a0e678 GIT binary patch literal 7274 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 Re|^y2?uHp#WCo2^{tp^1jL-l8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..579c03a895f19bab749a1ec8cce66dab6d644e47 GIT binary patch literal 4766 zcmb_fTWl298J^o-X2!d|1cJf032}@U@Y?2LT$f^RjIqJkLB@sR2#v;jj+cSmneCZb zgSSv<6{*Tosw!%u$`9?E5j;f73oliZw@7)JMG>|}iDW5Hd0W?1k$CEV&g}XYr=^Hl z?f;+qoOAx~|Neizip3NTo-=>He)C=($9+dD)kh8r{QB2W_zQNoRAS83Bh=o+x($7(Vh>aN0o(U z>K25=zVB%*!TYLVS+;AqrfoTsP_MmWWKG2CBZSx_A^Ha;%!oB_65GmQ%e`U{ z)0oX-zbjqcJTzvNtoneFqD@IU7& z?7cPH=g}Hcx6O_3GNOjf1%&Q=P>l67>}-cs)1U7TPSavmg~^*#GbWXT7ape6t~W{?bq25ij_baxL=T$$3RQ&XcO z)6kpG8t$A;azC1V!*%m)_N?Jxy+E>b8<#Gorq8`MHKG4<>f%5b9RdsX9y^unfnV=H ze@}nUJtc4Ean`gj+Zw_yPE(8rFR*MBw9~yhc7deXfoVD~y|qBTu3U7Uki?+1swoSL|FaYR2qJu-1!DK_-}>{zn5ub*zNXW(Q{ z&&gvxf4mr(BSsE)^@4|~TW-Uz*?P;qx@E10ydQxVP$&EzoQU&7?8<#9L-J`qTD`>L z-d(T6mp5*~4+jVGLu}s(g;2lrBNgNO@$IJfsu*)p!N2|8+<&8`CL+plSo|Wddm8SN!h*YmUPwbC}ADk=qaWxGe zo%!(0vf5cvJC{;V)hiA~=IJ`Gle(6q0?O#?0OX}d~ zk&-(0<+U$i!c|YYT2imB!*RH63`%*8G3alhu$4h~<4JdeClUWxsIuxwj^|uzarPk5 z_esk$ zdGwoTV_8sRQn``ayYJskaas96gBim3xGZu!5ze3^l zpn}F?6h;w~hfE`iWTY$#86K&N0@Q03g-4ax{eK}QHd)+9@Ej(TRvoQi-LmZ4mPV~d z+F|Q2YIg)AKVn+*G$kQ>pxc)yJHLogBP6S&T#N;!q+M|H1-BThSR!d!P<<%)(j2jK zzTnyq1=2!S3DJP(K##g57806=J(NfXC~}Aj>O6${8-c`PD+mrV5LB|Bs{PrTx@+r| zL+K2X8?zV9VRpUGoj@YS}S)jch%&Fz1iU%Kyg43`DP=n}sE0CW zEJ5LQLIYp`009|U6siEBDJlbsfMN_N@mB;&?PRewS8!Zy7HdX&A)PhT3?SqPoC1PL z(9#8!Vl6|1oyWv*Z8FrG5d6@bNvJD37>tC9HLP02M@`Tgz=L$tC1RDP3WC&NJi`w; z1_Q_mD*C9{0foNZpg?zr9eox=fyH4T5q1hEYkgU{rlDlD2; zJC1tELGPWRl|x-#_mJ0-S__E)4~6SE@#IXY`NGOOC!S1zF4N{URc-^Is1zu13Y5A4 zC@BESP)Q!D0OjpeRQw_uO4a`aQ2qp4dYwQ)5eg5=BaBOwMe&umL!I<%;d_D@I$Z0l zi4e9nAu;WTSTtY`&bWe5aF8U3gh)<;@|A)GsRWQUM$FlFHE8EngXVyjvQFiF8;(6^ z;w*AV3f837b1s1R2@|fg9$eki^hstZ`f1;jLGReOcj%(mG66mgHj0F)jiNhkM7eyi z@fYGhLaA^BNGe(ln|>sR9mmLELi2l452DzTf&3bB>L3r-D#6FmD!G|NQ^8`}*!1+J zT?SPXw_+n5=0Fx*%^{Qcj*-h}@j19V_Od2;nJ>Cor`WoEG3KQ338^{Q)u2DU^>b~5 zd;Sd5Umj9-`Nw=U0ojqPz(?X;4mhV0oKSQFR`M&tN}1e55#0417Zc0Kl0kaKG+OKq zCelc|Xvhdq^e&VfdUQx^G*JE^!_jQP%(|upxj^MZ3DN_aF6gF}w&?}CShtC8EmpZ+c<1Y!aQ_FH+ zN$z{$FpM1-6#p4YMI%6%VS?QPv3zOLws0a$Xv8YkBQx#351e&WvuEk9uamRT?u*bw zDCo&Jh(x`w==xj%=3`wa5NbFUL*avD0U7W$nP!31e}hJLokmIBFq1*>43aDsVrf3g z(s-64v9QTVlDU~LCMeEh5M`0)`ER*{ z-*K^Tx%M9h($ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9be4a73a2b14b2c297d9899cadb76a7d32ec5e3d GIT binary patch literal 5692 zcmbtYTXP)66`t;yy=t{yT`h>P4H)o(Et^X!PGan}Y~w(%FeZV@f@&FUkJcm2?yP%S zvgE81gG|{}#o-T7DLWyl(m&vlN*?kTl73B9C{>cGq^P`zi}RhHy=ZNv49QH*nbW6x zx=)`z=k(VT4i085T(ACdcHv*6mh~4ZY`+pHlq`!ypMVjTV1ZRjUM1daj(S50(e(`EuUKQ^Br!pB-QW(2L*OQa^O{wBb`!I(icAlN&FG#oU zp}#TdHH0TC?EyVeS!jB-%@Xw3JVMG~sLQ@k7j^QY=hrSOooo6lUQn42W!+WUj+E54 ztj%qWs-+bGLj#3iMtmSjWHs#fus%K@c@OyW63i}@Uw78Y}n z%Vdi+63SAn#ny4JIf^U01v9qzsMWIZ@Rp6X{3aRDp3%;Qv(ram3`e-iHN%^cWdVIc zoG@DIwDgue8F|8Jq7!pu;9|nGlEY})`%>p36-pS!%zMLR(sCqgPxN}~h?YVG8rqE9A1JQ~3Aas>XgRgQBx)>fC582Y#kw#LmBy7Y4yJ^Eh ze5Pp2haorJ$Vb2xndzn@CsABU6E(Ay4}sI!-W;hM(XjcX+)s4_^{}z%tu|d%y|^{p zBx|avb4GUF$i5qUN8m^5&6qV4IJj?z*X-=UeWoFNYj(Cu)9PHE>EX?yw;`=r?{hr1 zvKFib_CBVIH-Ddr!gcF9pXU^@*K%u23|(i}ZIlN^9%bILZkcbyt`@tE9MLTgv^bS- z@Ed3igAK!_1mbIJq#J5eT~70rRY~rv9aq*Oom{+3Z#iZ}GhxH)E)FDSVQ6volJ+ZL zusE?(GjeI;@RN!QF6?Vo%Q;?o3wm=wj}{+7m;@4k?){~+3Xr4EfrI+<~n~JUUrEK>oseG z!IgB@kNifYTzIOSz&F~Vl|$CxQ%7{78B*lmR1qhiH=XjwNwEPqt*hiSJ~~iZ(8Is^xQM87CUXtCg2M3 zq>}NpL{Kl2SPJ(m~+QrxjUQW8GX8tiMzUx zxr~WZv5D7N&$vbl1m>e?Q{+Yuomh5*r5#5C!$b3-g3+4a1?#cOuUZu`H}^Z$-{ zzmISF%u<4&XB(Jfuxk!dL1&5HV}yLq2vmOpokXNpHnoRcIrO#XYevX)ZC%X9>s2J3 z%%V}Bv37=|I4Z}f8=6mF z^w91{*#hIUXcF6kU?y^qp0NM(HLjSjkv<*V3Eat>&N^SSffs-g=9x%|^i3Pd(`h*> zvCgAMUb5arB3(#|%p#A9?NSPo^gOb{LJAK!)P1L2m${V{+1o5S(Jc>%0hG^DS>29j!JR+FuRm+&rSoUbY8$|8lcIN%@+3i7cP=W98SG;~M2v0*P$>y{ z$_$9k);;076}8&*dXP-+HdWYmq>3*40HH^WKF3EByf@FWeUlWm~tME(^|rvR(~ z{FF}DJY@i#PR3}(WJwvW6jR1VI)@z!TMTd?z%Aew!8e`hqCA}fI3rN4Qvy2{F&Q+h z7$e-ZL+FzRA;w4>9l8NDh+9^g(u+K~>_wGo7=(nwMa7RQF0dAQb>{-l0P-13C88YQ zI5`6v^UV3GkjV2qQfdMBFt~A+W0PM_0$Orb_)0FId0+gHX~?K1etrdpu;Ds;i?1;g zJBT7-BaTcADVrjQVn$>rju2N_$Oc4?AQZ`r&jBQvhyo@TLt^+Qd{>OL*s>KpZ9Eaa z3EsqyN{BHr4q(G$U=vMK3rKhONzZjXcw$DLg_aw(BnY^T{E&(-gM5ky*J!ED?C&tg z+sxIIC3y}lwo>r+*8xh~4NrC&9_TRVI?U-(?eDLCyyv~YZJvqHtv!dQBVcu52x!;%W5&+yx~{m-^<`vR$J*=le5jNrMM){Jo=M61CcCF-*uLmA9* zjpRo`uAJKLU(#TwsN%WRi#^0%!STU3mpM(`4>N67ST17-DAvR)R!m-|k$2GOJ(80~ z#iYsRJHASNZWBdkOuQt;gm_?E6rDHHlz6aD`es}Dtv=~|TRPb%y+l&8XFN0$ zZ@r6{vZN@}sZ)Uu^eAd5;2Qm;&Ft?mKkDonGjXKp^o7pR+Jp!Ni%z|=q#~SWe*7? zQtsr+Eqrz#SN;tRXM*c|zvoq9kEjwZsY*DHpu!KAs2f|+BGvf~0Ck-B5pmwOqsz{x zlkMu=n;JSNf;cs--CymGI%@xS8@!7q28zOEF*iI)^%Qe* zg~<1atP<(ZGy3`eYdLP9$B42jvI4s+D|E7hc@HC7IG^Q5a(o;)!eohcd6ZEK_t;5SWWehEL>yh=rqcWLg2Z72j3g`;zPWSFBW`}~V98^mOAzaFpFmt_UP z?p0iW3Q$Q&x2mF8UsBLuviO(T!+mYLiF>N|=Q3W+J@narA~C+NyK+$vG+n||N&=G^ zoMKY2Bnxj_Z?p2|Fp)l155-d89VZ|VXLXCRcbXU21m2u}r+v;x^Iv2$g#$U3-J5^# EKaR}?ivR!s literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7e44e1d82b46701c15d91019fff6582614f6f6fb GIT binary patch literal 18402 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 PbU;UBK>B`vy4?Q{%dzg4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3377a95de841db5a6691382c3f682246c7b3f89a GIT binary patch literal 5391 zcmd5=O>7&-6`tk(aF-%QS(5da*j8*S@Y)e2#+GB%mSZ{bU(={T|Ii36wkytvT4l*) zXO@=5N`TSkQuI(C3gn_&dx&eJaBn#_w*tL5h*Jv-1u;+*ZEg}|)PPTYZphZ{|k8NVO@1QEBV6w+_qEwB+DVP~mDxRzXCUu}yaVzc$g`05E~m`=mpDaJ3Oej+)$?uN@v345>is3o zY@b<_&D(X?w>T?1!sp9oD(HR3sWI27vNya+&2iZU&OIJH~BT^Kyy~t>HT_tfg)>WqzBOFU~abrOH)aV6@B%wlj9M7#NiKtb`hC^p!l$ z3WEK(&Z;G539rse>|8O(T*2?Uh+hhdQyrgGM9?8h3#?*W)u>ge3nl_($?+E&ds-|D z6^}CaT(L0->I22sLqVgp8XH~qT<_|sbH&HTj5m6jxfSLI=_Tfr7kts^9D`*XMZGgx zsQIQ6s3E5y7qPZndyct+qtOPru*>pl;j%aS+tN9!!SY*WFah|CG^^TkE~5<{<@37B zzQiSR{tgQS5>gHgw1{t@SDdP~;P@gimYgc}maK{}Q@jJksraTaTJzgH1f!%&~8AbS3!{?P;udqLx){+ ztgDO*Shv84jaBe8%n+uQ!8t}s3Uw6DT;N6?9v*)D_Sk|~VPn_)_v&`FJoXOv7Qq6< z7@9^%EEHpbefUg3d%f!TVG;IG(^gdq_cCnnY>CUMxyn{xW-!7Vj|FXUbt+B$S# zf8XYLi5x86J$2=ifv-O}Jh^`GI0zja+ElgueOqrxNA`Db%14Jfx71N1vn`KhwM{an zYTGbrQbH0Z9SY&*{T3S%sqw9mBemrTQ)GV9EDTQ#V`*khG!*9G9Oag=Iv7G$B@nyWjm?^Ff2d=Ey5V89X! zUfrePP^;{EbG93TTVPnio@dA*=$sBG1x_~#7kaFDk5>R<*tME#SHr-=O!Fenq65W5 zAMt9kaw1uIEm=7kRU(#?(LAF{u_(~9gY(A3P2_(f{KPn3u}dXZ^M%!R{FY!YxOGOY zx#gzg_Bg);ubziTd;sJ+*~lN*(DOGsKGk1b*Lybf19#Xx{mgb+>C&!Wev~EZW$C_h z>3{kEOu595!Q4vlv{K6x{|1_po_G-v{E6f@y&i!e>vrl^bHoIZh>uDPQ`_ezJh

5)@W+;G^1Eb7GPRjT8UmZ|ff>T?Q^7_` z3(;oeqcDS;TA)Qn8fe^hAa>%29#0teO%kdiTJPgK)|;~svyxa-1qZ+_xg|Bnf$XcwV@AeWP5I0`82zKJ=?#LJ@nDYz3jP%X~oDql1b+5 zePikqW9qR$$XR=7T9ZH4)aiVB$I_rm!HY=S`++8xCJB-d>!xnYu_CFGfJ*gt+o1sm zO#cch7hB*HjYCS)gZKtS&5Pj)rrm?3RgfD&Xgvf_aotCU7^X!y=RBFH9>S-w6Fw8~ z>;j%`(0VmCrPh&cT$5$&Ta#0uewX{V&@6J9Dv^Lo{HDvTdS z5x5QcB$BToIg8|VB~ot%7y!1 z?f;o_;ThohTlk!$hyE{sr;PU(;K7@oo2GX_sQ#58)R<|!92uY(POM(xXmS%w!KDJ@ zSK!OSc=uvrZ-qe1-@>jLBp6jhP&&E8LSiUQ$N&q0iwrox$F@OZ+n5C=KY~Y~?X>`P z1Hz}ylmAfVjP_6`#vr2VTOYDN^xn%}d{IQ`OP-eU;Gxr7=%bKr43$rYaOA+XCe9KQ`be-Ybhns_*{>?O&bkg0ojeXlSDJ@-=wtF=x Rv)!#r7o~@NhLj1d7&-6`uXy<&vTz%KEh}$(D>zjwmaRTFW*aOO7SmZG{3&3q(M%U2{g%s>@w= zc4q9aLp<-K1LW}&PF9}h2vP8xA_$w zYQYvt;))pdiMCXdSLBkiqJ-a)t(LSE4can1$~Lc|_`xZxekiXba2M7-6jyYp6Hq6i z)}c;8orJn;lh3BVW(Ub~fo?jE>zlsiI^HsT_ZKKJePUp;YF2FDprmMdKHbdnLGO>P zGO;a(+;K}~%O-azb!pIDh`x++YxD^hbla|pje_f}T15k!zPb2>50a+un}s!Fm6|02 z``U#1hE+0)q`*a1K|Itcc)?0s^D#6&Mt(d7mcl$1iU^BX+UBtguYy&q6}c@AC$MgD zIJqr`{qUz-Jbb6OP?5vk_%+_8|Fy{e z6bvU>J}G&D?iJQZ$uyj(SFCu%3&ah}U#q@$tTI<}F|lvYR0qL)VE9V7(NwuirZ!#M zeRS>i%qwl#ow`Tt67d6NgIL8i->Y^_1B_G5?gZz`epU>mP*aeKR2#PWkk}q&t{J4l zA;T?)uvhy}cBiBa@Eb*N0r(AtIOao};Ht{@rxlxg%^7|@c7hTHOd z4H1E0`?j|HExH!N7(k>dUs$M80STPxPcp-prV%^;84;5qaj=x06e3-6%~ z4sfd~5Vd{=+yNs?CDSjgMIh2C*x`vB$mLCDBt2?|XSVRH2pCX}oL z0H-jqiVg(l(~vMEeh9C34Fo`R=4Ih?S(;BPr=U5e%0FRJJ2dT7fs2ga5n@AP zv5hd&j@)VJ4-n9V`EEz~Z`8rcAk124!Kb5OzS5%6chIJW+L9fr;PXTqkW!0C`7UK% z)P`!vyO4%ir31=9p%ursN`#KHHSD}WrY$7^I)^t8oq>^dD!zRR_Lz!oD?MXljX1|F zG@`77cv0Tr(YHb2Pnl8|#0e`u9e@7Lo;v?+W;TGCz5DubO&{LZ$7=f6A0})1)V_Yb zreA-KYx=^jxUj1))b-4%NtbGRc3;oe^!#VNHT~|cco*dVVDg{fH0|#4e_*n}G9_>) z77U?as z6Nt;ML$c{0VHm3w=plwd85ESoo?w7wl?F>r4AaV)<+5!R!Yn+xo6ejXZrwceBq#=% zjtMN)rtDsbE;x4gP!{b%f2YhcorYF#LKX-~&n@}@M%iuWy$hlta2)p~y6_cB{R7?n zJG%EJx&j;+8mSMQJJM!3x!#>=2v9>7lId?Ktfiu!z9t)MmN|vH$2EIpF462!xkc_} KpT;FZc>e>(FF>UL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4b77faf59dcb4c269b42755987458902165f65b7 GIT binary patch literal 11621 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 QKFIW^`_h}zcV)N#AN|2Ri~s-t literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c4dac3fe14d95618e9a8623b43ab5f8bd5ae1d9b GIT binary patch literal 50763 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�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

AxzWCzvu$~cQta&U{{uYS8YBP! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5040f897f0e777caa41327a0a73d566d899c1b30 GIT binary patch literal 6425 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d21dab4a20b1cf4cb67e7c313400dcf10e5b7009 GIT binary patch literal 12816 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0c7ff3ffa2fc09c26116fae2f39f8c95327bb36f GIT binary patch literal 8378 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9b3a253ea4056fdc7695862614c8e0f3b2d36223 GIT binary patch literal 9023 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7c82595a7330bb78aff0c9908fc9423e3a9a6d17 GIT binary patch literal 97862 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%0AqH;F3RCXDw7`|%Z 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eb810c7304e5ca65f0c2a9b4eabbf0419e0638d2 GIT binary patch literal 3315 zcmbtWU2hvj6y2HKwKuloI8jEhq*cwN8gcMHuzx7aLpgJ#g3YtCVA zRTM<=C$Cw86o@%UWk@AahE#!65%ZAd1^>jUFWe{lb!N&@lB7D)?KH_Y>%N(5h;4bJ zFZ?xlkRV&opyX+=C=C!W100k+7y)3eU%t*GfiYn zrcNFXfwp^%w)-^%!36)w89LfIVgu*4bIgWpz;?NC_jtDWf%7A2uJE8eI?_DhLwjPR z`F8B`NGpg!-^pGiO*q(Saq}Vb3>E{t_sINj5Po3V(d-=D&>Uu9_KrJr2F{Tuykqu0 zBJiH0{aso#H%}w%)W|c|IF0yIBSn1k@@OPDcJj5b@>t&H*u1~Zv6+q0yt$Kk%+aOY zvaZ0R!X5te5+k3+Uoz)2c;?}E+qEO}$Jp#kPS)NBYUuX`UVLHuaR~S6+)W{lx>7}V zneh@x_vtk5Q#8=ycOxC|)T@eguA?4D_wN^IW_Goo zwDnaILkT2?cFJn+zy$I;sbZ|Mh{@S`H2nKzcHaK3uqvzJS9%ce+8+Tw{KzgKRN4#E zj-bW%r0iv(-jQKT$!v$Z+FEwAFiJ$&?}-Rsgx0auw>xPZb+Wb96N_pA))b|%s-pR{ zUNprhJ4oVoYFyP%;9Fw-6ebXIBvr|JPl}NPrn=MK-U;_%M|C5$hgjI8s&jRb7JiHx zI)}V@0+M;_f9#@$1v2tBV3l%}S8+mBw#3vGbUl<26cP&gHOh$3A^bHVzjTLe$cOI0 z<_J)|Y|)Bb5uQcwiq+lSJTWhgG+z{e?B__Em5OAPjN4S)g)m7)!EbY$gfMZEP!`U7 zl9VHToN!PhN&iW-}wS)FnbRy<$%Mj zK=;78k(S>I7smke|AFhjgOO=~5q3vR&nK)6)&+{Sk5OXX+6ZLD2?(coHwnPFFPviB z!W0CHfE(Sm!KNh+Ldvd;wJ8E%TBRLDCuM>tj%80vp(trhkWZ4L9$4t97txzT?;J@) zeafe1j6}6U>ci||qxw4fN5EUM47tQs9-QTe4I9qfP}sBAcbHRjxZl=0Xs0%00x zvzSb~M;_S7vKiGo(EVu;4%iW3WGk5~)&=JXP|6F4AqPYQKqqV~1N7 zFm@MX+{%uOP+WjJQ_+v53ZoV-S;B4BN4XPrqdmIYdr>U^iecQplhd$J_NSmsEUM0s z>lJGg*EzP3;ZnFrBa<)RA$1mUuKhoU8(23hp$XV>rIv&JU8r!I%2+i=a@CZ=u+>M! zEyGYzrI&Ak=)x?<-CHWJrK0v~oGO_|n6e|M5i0ZSePN3OMPzLgN|B){R!PTC6Vo31Hh2wg3PC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8c70fde3064f35d4af188af12eee8bd18f67cf73 GIT binary patch literal 16860 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..05a674f8fd4cd654410e0c1fb6fd238bfc1c4f65 GIT binary patch literal 6456 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..edf798c0222d1cd735b1d7a071ce8b9f65603b32 GIT binary patch literal 9508 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 YUek1 zZ{}a-yX%ENtOYgiZ|ZI`&+p{B8?5uY1fusfApS-J5Bf%vrMumB<3Sr@3|LrWx!>{u zT2Ed^Wp+}7Y1SFHr77=nO zQD4=(`I)LcF1qhtG6j2R7*LZB#nq|#>lcxGeJv7kZf-iHZ)gB>Z*vOaz=s= zeNbvDk%1q&eTtW7KS9xPn^vFNOC4m5gYU zDuPml{ie(bHsc=U)uU8n6=O#!0X322e@4D+D5cCD8#f}8*C^r^1uF;!1O(b;1;LQz z7HX6+V7amkSOWY$@Fw>%c7EowF2yNVcIA@{u9-AGO%vI$5an|-1$yb5!L{kew&Snj z_NXJ2wcHC963c*T5Vnb@8=%Tz2jryXqz({9Y7HPxb8_MRQ(Q8AzDeNDTF+Vxc$w7w zYO*avqGi&mTA)v79An{Qof~7HW_xAV#5*QUw1;+0yfZguoLeKQd{&iIGBz2@8LrvA z+9^`U`F}(UykN=|8>fxkX{(&bN&n3VgtUN#OUKWQr!aGb%)C=msG%Vy8YV)m$+a%c z77=sv%^o5U=5&5wl%eIK+A&n~8N=bDz~MTkqucthlVD^J<{U93$|Iv}x-j5{GmJZ9 z2%^2a5;MQZe}~-V+Z23-g0CWIwq@zI_mbS8G#{cL5F*Myrhs%4X0O4VTjh)S|k&9#VDX2Rr~fXCADJ50`g?NN^8DkcTCwbpBSU*23?dc)kwDS{r0;| z8(}5tY8;Bfbk8QYWm~!mel8bkr)#g2eYN(*+LtR9H}x!R>Y1v2&C$DL_g-*KyDlC6 z!b7+0wUDfx2Y6T`%Bn9%e=WLf5xmwnOB<#RCSAeVgNqGY7aF!E8afwTop9TBc-8%t zuh|31(gyP5cBeV~fVQ@Hr_TJ!?wL(DT~&%urMRk+uBN&5*XyosSa5YHLWkn&NV>{r z4$XGG_|Sr@NfDY9S5vZ}R4IEvDQ#9<8|DVDhp(PoaBWwF?TTxAvcP{ar1%?=YE3o| z%pJPkbrsocQ-o~@-wDt+D30F${k9dgi4lr_JJ3^Mp5M^i(O+y^K4>85ko+&cv{~zzSiny+l?_L*;Re0QPs1}_k8aU%XkAy2f=(jBnfD6=8PkZIQ_ z$gS_Id1M@?#C?%kL{p6&mX4LuJj<^?hk5zusMo#crJ=uOxku|}utA-IL5yZPyD&3V zC+mC8OqjLwxvBjszk{^limlB^?VdZ|nX&A>=4#V&$-S@5)iAa}qAE0Mt5I4GqTN_J z%%4(pkVRD6K7zex*?YnA<}C`Veqqc01w)MA0b=|j{yxkkH+P?$Kne0+Q=sF7>_rbx z6lA!py6t1ai`}Q#Du~U4YJ?%=87njyP)XSyd0Yh&s0M31y-22F++Ot+&W|Rl`kXDV z&_+}o$^)B^x^_sC8`i_%+mTt+SPLn?O%v#Upg03k<|-0(TVDS7tLqY54=TY!iK4^z zMin@~8lv3XBZu$}baX?IU>+M}^1zi599-&C zc9_p|Llbp0FwyQh9NpV`Px2kR06IR6Htt$ZqSb&W1|9FkdOuN~IT~eDhSVM&#zyu5DPtm9nu6-2lsQfisiBC zuUP}r4dkL3zCL-G@mgOWIkQtUS7g`Lz?vfk)rl&OoCbvWAVx8kW9%V#fWvJ(;ACUp`QMx>qqIZJAD=@zOAkzz8#o6zn0) z00cBzJJA&yw@>1$^@+Y_FDP>($kjTa^&9d5+ZLMev@hD*7wqk67n`|oQrMVr5vx-^h#~;E+byjyp{5e~;hDY}K9kPsV^E{}A*<9vFS!jjOv5EupyBI%LaQ z7t&3!_4S?)y;7>!t7l6W>ozacZBEp6+_HDf!)JjE3+JmciWHtzop^!AcWfC$%-xs^<-Hu}+J>bROV>UdH1}UeOv;8s z zBy)eu`YElKMx{aM=n(a9qmY9W^=fM78+Wgi-$6>Y2L+w*cSO@qwQ^Sfa_=52-NF~x zC_d#;BUF>46q~49{hU>(c9O}R0m?t1eeyGKCB~69!A%=C24RE_Zr!mX@=*-y2{$-@ zYgk(i_G#(EK`kR@5J?GS9GOB!3nGOy-6ihe=pe zm|R0+I&0pi8X50b+nt^GgX8R&p@tSKwN0d=+=$=3Nx$8UTySPj8%>4k>FQsfm@E4m z$EGEF)lC1~hU+Jl=A*A3L~zS~j4Y&^7af}x9GmXO$d%JeG=exg7--t{Ru1P&<=Q6eT? z()5wpZ&*&O6&^qw64AHFT@;X)(}YiTCTV+^xx+om+~L@7YACtD)+*J*PqkA%b5E+v zI>pPGl{;IppdDs1nYPtx6Jp&h&y;vDX%Aooyy zI5|6$sZraVbZF}FG0F=^Y?>+AB?`WU0Ou^=q+az@LYID6FT7_+&r!Es5%Q+2*uf0G zsWu)BrqqUk;nQ+R5(lMFIMRmU62NKMs|)?=C#a5u+@j87)ky+sgtuzmg@?Aal^KObeB%%C#vp8v#4cC{?v80ZNU+u?NRt zv{AKXve>Z0)>7`raC{a{zfo-&AT32yHj z+|hSTHs16Ohu~idIey0*T>IZ~M^~(7zH_DpzK47#tRW`XJ0=UiJG-*&(H zcw+kz#r{y*Vg@_5bB^|O0cWaM=4>WYy0DS2N_t>rP4_z|>u&<^!Q=x;Yh0PgszWqg%&&9_wCc9zf7p;}4}d}I$N z@zkI=YL&W&6#LPnGcfan;;2^|jw$wslg_f467~*Xbntd?-F2KKwms?YK&Qbc*3RKX z;sY$Tnp5S>V>7*qqfS{zU7mDS&ODxZ-=)~Q>G>Cwsuso3s^UJLu-(n=&iST~`{u@{x literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..aea0571eb0dfd210f0289948f115e0462cbba4ec GIT binary patch literal 2649 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..27624e1b395d470bba82a23b9108a0fcefe0a84d GIT binary patch literal 4440 zcmb_fTWB1~6|H_u&!gw1$Bblav>I8q?8(}CIF5;5D_Qo)%93bFHUvDz4DG45)N;=w z)jhID9xY1n$BG1Gkp(Ltvis>^fA}*$Ase!RKz@2G5eDG+2 zS}$x;(%iaTb?a8u?do%G{XG!yAt-l1AQVc_N zR%KHx)SSv?`4n%p@v18;qy$TMsqU;N<*{wODKFRxsxK?1#H>H%x7yumAR9~tE#0Gr zvf)%1bT4MHZ zv-?Lzm?ao|Y@-jQ+p=D|Ch77khEB|y9!>svQO;##(<_mwVph)ST88$~LPP1T*Ua`PD36ft z^da32G=cz^QJE>TWp060P>v~cPuV9fFo%M_+hc zM6EfBn8EB6gE{!|tE>~L`KIVTv3+3I9BA{5Zbuek%a)m^!tNMWcRSO~&|vK=Wi%u1 zYl6oz`^0{uPCd_k8$ZpfYMhMSwY%rwgKezUnaio?`TucFPje(3uTDMhq9^lx^h-FI zGxTWeE7*JI1iWp>BDk*`9%sT|ZNt9CD80?;hZznU-|qA`YCY=c&KrE|3J&lv)9H9< z)Ma+&0L$XrcXz;unZpgAj)s}Wj=yG~F~_!1!-@`QZSutbq8@oyz5d3N(9`hkXwErv z-uO)1>1othY|L3g3IC+Y({JAl%2}CA0R)!Pnw~EZ`sD*Kh9p(Z-<5H?4oq63p$=Nw zzb*VD;!k;ir8?};@5}USvYw{Mn^E+V>B-dLS)1!P2(<@ihu^djc}C9YFqLC_>2#un zP=+J)TetAYbW2((mr?R2M~XSq4Fr&pHO=G;d7_&h2fIw64v(f!(UhE~OBC7N1*xRw zB}^zjm~ML(;2fJq(zjrpI+1K7o|Yw5pEd<7>yn}-1VWFQDcmmVMIxJFd-yI~njR^W zkqfYX(=FdCCu3PUuN9k6ZZb_-Vr7w4$YN!<^}in%87Z zPJ{Ik_-QMEQ445Oj8#3s7x5vZXSf0n^x7Q=dHP1Mhv70#1en7-c14>K1-r@&FUjIC+(|udzAPNBb zrTNLeauEGGWVMG>4ua@i0%)ObLBx7-`I1mO864b$y9wgwLdw^2v7@`RdOJtdv+uG+)+jo zoXO7KgI~#H@6IK-x;wf(;2%3bx9264RbNq-ut&cbaojj9#t3-Wn-IUEFT%F_&j_Jh znh2Rfp+qs+6o{l~vS#{zCaFa^Nl2dT;~^4<5u}$2ip->+ilbBE=4^YwiuOIvI!C?ag>OKAF$Sa1v!mH*#}P=N5aT`FeX3ZhlGdV)E2D(Qf+E z>3V*WP7|7!S;y22W%8;D$AjLg2c%mWLb2N9>4nhGDkQk(VgAh5EKo=N5{-M1g;yZP^7cwrkV$ZT5Dda`B3;ojat?GNQi9*K-=z*G2_h6dkK)% GQvM4IF~g+* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..83dafcd838806462b18b5cc6a30c5777d1738bd5 GIT binary patch literal 14539 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b334af063a21cc6d100573b7c91aad057b3d04c9 GIT binary patch literal 54721 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 literal 0 HcmV?d00001 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" }, +]