├── .clang-format ├── .cmake-format.yaml ├── .flexci ├── buildkitd.toml ├── config.pbtxt ├── env.sh ├── generate-bake.sh ├── launcher.sh ├── lint.sh ├── pyproject.toml └── script.sh ├── .github └── workflows │ └── lint.yaml ├── .gitignore ├── CODEOWNERS ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.ros2 ├── LICENSE ├── README.md ├── docs ├── FEATURES.md ├── GRPC.md ├── LINT.md ├── PLAYGROUND.md ├── PYTHON.md ├── QUICKSTART.md ├── ROS2.md ├── WEB.md ├── api │ ├── ERROR_HANDLING.md │ ├── SHELF_HANDLING.md │ └── images │ │ ├── dock_any_shelf_with_registration.png │ │ ├── dock_shelf.png │ │ ├── kachaka_errors.png │ │ ├── move_shelf.png │ │ ├── return_shelf.png │ │ ├── return_this_shelf.png │ │ └── undock_shelf.png ├── images │ ├── kachaka_api.webp │ ├── kachaka_api_logo.png │ ├── spapp_kachaka_api_enable_dialog.png │ ├── spapp_kachaka_api_screen.png │ ├── spapp_kachaka_app_info.png │ └── spapp_kachaka_app_info_screen.png ├── kachaka_api_client.ipynb ├── kachaka_api_client_async.ipynb ├── kachaka_api_workshop.ipynb ├── playground │ └── images │ │ ├── set_authorized_keys_from_github.png │ │ └── set_authorized_kyes.png ├── python │ └── images │ │ └── kachaka_api_client_docs.png ├── quickstart │ └── images │ │ ├── jupyter-clone-sample.png │ │ ├── jupyter-login.png │ │ ├── jupyter-readme.png │ │ ├── jupyter-restart-dialog.png │ │ ├── jupyter-sample-speak.png │ │ ├── jupyter-terminal.png │ │ ├── jupyter_url.png │ │ └── kachaka_api_client_docs.png └── web │ └── images │ └── web_sample_capture.png ├── protos └── kachaka-api.proto ├── pyproject.toml ├── python ├── demos │ ├── README_SMART_SPEAKER.md │ ├── command_gui.ipynb │ ├── emergency_stop.py │ ├── error_code.ipynb │ ├── feature_matching.ipynb │ ├── follow_path.ipynb │ ├── get_front_camera.ipynb │ ├── get_front_camera_continuous.ipynb │ ├── get_imu.ipynb │ ├── get_laser_scan.ipynb │ ├── get_object_detection.ipynb │ ├── grpc_samples │ │ ├── README.md │ │ ├── export_map_api_client.py │ │ ├── get_locations.py │ │ ├── get_map_list.py │ │ ├── import_map_api_client.py │ │ ├── move_to_location.py │ │ ├── sample_getter.py │ │ ├── set_auto_homing_enabled.py │ │ └── time_signal.py │ ├── http_server.ipynb │ ├── install_libraries.ipynb │ ├── plot_map_robot_lidar.ipynb │ ├── qrcode.ipynb │ ├── requirements.txt │ ├── robot_pose.ipynb │ ├── run_custom_object_detection.ipynb │ ├── sample_llm_speak.py │ ├── save_object_detection_features.ipynb │ ├── smart_speaker.py │ ├── speak.ipynb │ ├── switch_map.ipynb │ ├── teleop.ipynb │ ├── template.ipynb │ ├── undistort.ipynb │ └── url_kachaka_api.ipynb └── kachaka_api │ ├── __init__.py │ ├── aio │ ├── __init__.py │ └── base.py │ ├── base.py │ ├── generated │ ├── kachaka_api_pb2.py │ ├── kachaka_api_pb2.pyi │ └── kachaka_api_pb2_grpc.py │ ├── py.typed │ └── util │ ├── command.py │ ├── geometry.py │ ├── layout.py │ └── vision.py ├── ros2 ├── demos │ ├── kachaka_follow │ │ ├── README.md │ │ ├── kachaka_follow │ │ │ ├── __init__.py │ │ │ └── follow.py │ │ ├── package.xml │ │ ├── resource │ │ │ └── kachaka_follow │ │ ├── setup.cfg │ │ └── setup.py │ ├── kachaka_nav2_bringup │ │ ├── CMakeLists.txt │ │ ├── launch │ │ │ └── navigation_launch.py │ │ ├── package.xml │ │ ├── params │ │ │ └── nav2_params.yaml │ │ └── rviz │ │ │ └── kachaka-nav.rviz │ ├── kachaka_smart_speaker │ │ ├── README.md │ │ ├── kachaka_smart_speaker │ │ │ ├── __init__.py │ │ │ └── smart_speaker.py │ │ ├── package.xml │ │ ├── resource │ │ │ └── kachaka_smart_speaker │ │ ├── setup.cfg │ │ └── setup.py │ ├── kachaka_speak │ │ ├── README.md │ │ ├── kachaka_speak │ │ │ ├── __init__.py │ │ │ └── speak.py │ │ ├── package.xml │ │ ├── resource │ │ │ └── kachaka_speak │ │ ├── setup.cfg │ │ └── setup.py │ └── kachaka_vision │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── config │ │ └── .gitignore │ │ ├── image_view.png │ │ ├── launch │ │ └── hand_recognition_launch.py │ │ ├── package.xml │ │ └── src │ │ └── hand_recognition_node.cpp ├── kachaka_description │ ├── CMakeLists.txt │ ├── config │ │ ├── display.rviz │ │ └── kachaka.rviz │ ├── launch │ │ ├── robot_description.launch.py │ │ └── robot_display.launch.xml │ ├── meshes │ │ └── kachaka │ │ │ ├── body.stl │ │ │ ├── left_tire.stl │ │ │ ├── right_tire.stl │ │ │ └── solenoid.stl │ ├── package.xml │ ├── pyproject.toml │ ├── robot │ │ └── kachaka.urdf.xacro │ └── urdf │ │ ├── _kachaka.urdf.xacro │ │ ├── _materials.urdf.xacro │ │ └── _values.urdf.xacro ├── kachaka_grpc_ros2_bridge │ ├── .gitignore │ ├── CMakeLists.txt │ ├── launch │ │ └── grpc_ros2_bridge.launch.xml │ ├── package.xml │ ├── pyproject.toml │ └── src │ │ ├── camera_bridge.hpp │ │ ├── component │ │ ├── auto_homing_component.cpp │ │ ├── back_camera_component.cpp │ │ ├── diagnostics_component.cpp │ │ ├── dynamic_tf_component.cpp │ │ ├── front_camera_component.cpp │ │ ├── goal_pose_component.cpp │ │ ├── imu_component.cpp │ │ ├── kachaka_command_component.cpp │ │ ├── layout_component.cpp │ │ ├── lidar_component.cpp │ │ ├── manual_control_component.cpp │ │ ├── mapping_component.cpp │ │ ├── object_detection_component.cpp │ │ ├── odometry_component.cpp │ │ ├── robot_info_component.cpp │ │ ├── static_tf_component.cpp │ │ ├── tof_camera_component.cpp │ │ ├── torch_component.cpp │ │ └── wheel_odometry_component.cpp │ │ ├── converter │ │ ├── kachaka_command.cpp │ │ ├── kachaka_command.hpp │ │ ├── ros_header.cpp │ │ └── ros_header.hpp │ │ ├── dynamic_tf_bridge.cpp │ │ ├── dynamic_tf_bridge.hpp │ │ ├── error_code.hpp │ │ ├── grpc_bridge.hpp │ │ ├── ros2_topic_bridge.hpp │ │ ├── stub_util.cpp │ │ └── stub_util.hpp └── kachaka_interfaces │ ├── CMakeLists.txt │ ├── action │ └── ExecKachakaCommand.action │ ├── msg │ ├── KachakaCommand.msg │ ├── Location.msg │ ├── LocationList.msg │ ├── ObjectDetection.msg │ ├── ObjectDetectionListStamped.msg │ ├── Shelf.msg │ ├── ShelfList.msg │ └── ShelfSize.msg │ ├── package.xml │ └── pyproject.toml ├── tools ├── export_current_map.py ├── gen_proto │ ├── python_entrypoint.sh │ └── ros2_entrypoint.sh ├── generate_proto_for_ros2.sh ├── import_map.py ├── lint │ ├── Dockerfile │ ├── local_run.sh │ ├── requirements.txt │ └── run_docker.sh ├── ros2_bridge │ ├── .env │ ├── docker-compose.yaml │ ├── env.sh │ └── start_bridge.sh ├── sync_from_robot.sh ├── sync_to_robot.sh ├── update_docker_images.sh ├── update_kachaka_api_base.py ├── update_kachaka_api_generated.sh └── web_proxy │ ├── .gitignore │ ├── docker-compose.yaml │ ├── envoy.yaml.in │ └── start_proxy_remote.sh ├── uv.lock └── web └── demos └── kachaka_api_web_sample ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc.json ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.tsx ├── KachakaApiPanel.tsx ├── components │ └── PngImage.tsx ├── index.css ├── kachakaApi.ts ├── kachakaApiPanelType.ts ├── main.tsx ├── panels │ ├── AutoHomingPanel.tsx │ ├── CommandPanel.tsx │ ├── CommandStatePanel.tsx │ ├── ManualControlPanel.tsx │ ├── MapPanel.tsx │ ├── RobotPosePanel.tsx │ ├── RobotSerialNumberPanel.tsx │ ├── RobotVersionPanel.tsx │ └── index.ts ├── protos │ ├── kachaka-api.client.ts │ └── kachaka-api.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | AllowShortFunctionsOnASingleLine: Inline 5 | AllowShortIfStatementsOnASingleLine: Never 6 | AllowShortLambdasOnASingleLine: Inline 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | IncludeBlocks: Regroup 10 | IncludeCategories: 11 | # First-party header 12 | - Regex: '".*"' 13 | Priority: 3 14 | # Library header 15 | - Regex: '^<.*/.*>' 16 | Priority: 2 17 | # Standard library header 18 | - Regex: '^<.*>' 19 | Priority: 1 20 | IncludeIsMainRegex: (_test)?$ 21 | PointerAlignment: Left 22 | ... 23 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | parse: 2 | additional_commands: 3 | add_action_files: 4 | pargs: 5 | nargs: 0 6 | flags: 7 | - NOINSTALL 8 | kwargs: 9 | DIRECTORY: 1 10 | FILES: 11 | pargs: 12 | nargs: 1+ 13 | sortable: True 14 | add_message_files: 15 | pargs: 16 | nargs: 0 17 | flags: 18 | - NOINSTALL 19 | kwargs: 20 | DIRECTORY: 1 21 | BASE_DIR: 1 22 | FILES: 23 | pargs: 24 | nargs: 1+ 25 | sortable: True 26 | add_rostest: 27 | pargs: 28 | nargs: 1+ 29 | flags: [] 30 | kwargs: 31 | WORKING_DIRECTORY: 1 32 | ARGS: + 33 | DEPENDENCIES: + 34 | add_rostest_gtest: 35 | pargs: 36 | nargs: 2 37 | add_rostest_gmock: 38 | pargs: 39 | nargs: 2 40 | add_service_files: 41 | pargs: 42 | nargs: 0 43 | flags: 44 | - NOINSTALL 45 | kwargs: 46 | DIRECTORY: 1 47 | FILES: 48 | pargs: 49 | nargs: 1+ 50 | sortable: True 51 | catkin_install_python: 52 | pargs: 53 | nargs: 1+ 54 | flags: 55 | - OPTIONAL 56 | kwargs: 57 | DESTINATION: 1 58 | catkin_package: 59 | kwargs: 60 | INCLUDE_DIRS: "*" 61 | LIBRARIES: "*" 62 | CATKIN_DEPENDS: 63 | pargs: 64 | nargs: 1+ 65 | sortable: True 66 | DEPENDS: 67 | pargs: 68 | nargs: 1+ 69 | sortable: True 70 | CFG_EXTRAS: "*" 71 | # manually configure find_package to sort COMPONENTS 72 | find_package: 73 | pargs: 74 | nargs: 1 75 | flags: 76 | - REQUIRED 77 | kwargs: 78 | COMPONENTS: 79 | pargs: 80 | nargs: 1+ 81 | sortable: True 82 | generate_messages: 83 | pargs: 84 | nargs: "*" 85 | flags: [] 86 | kwargs: 87 | DEPENDENCIES: + 88 | LANGS: + 89 | format: 90 | line_width: 90 91 | max_pargs_hwrap: 3 92 | autosort: true 93 | layout_passes: 94 | ArgGroupNode: 95 | [[0, False], [1, False], [2, False], [3, False], [4, False], [5, False]] 96 | always_wrap: 97 | - add_action_files 98 | - add_message_files 99 | - add_rostest_gmock 100 | - add_service_files 101 | - catkin_install_python 102 | - generate_messages 103 | -------------------------------------------------------------------------------- /.flexci/buildkitd.toml: -------------------------------------------------------------------------------- 1 | [worker.oci] 2 | gc = false 3 | max-parallelism = 10 4 | 5 | [worker.containerd] 6 | gc = false 7 | max-parallelism = 10 8 | -------------------------------------------------------------------------------- /.flexci/config.pbtxt: -------------------------------------------------------------------------------- 1 | configs { 2 | key: "kachaka-api.lint" 3 | value { 4 | requirement { 5 | cpu: 2 6 | disk: 10 7 | memory: 5 8 | image: "docker" 9 | } 10 | time_limit { 11 | seconds: 1200 # 20min 12 | } 13 | checkout_strategy { 14 | include_dot_git: true 15 | } 16 | enable_ssh_agent: true 17 | command: "/bin/bash .flexci/lint.sh" 18 | } 19 | } 20 | configs { 21 | key: "kachaka-api.ros.aarch64" 22 | value { 23 | requirement { 24 | cpu: 5 25 | disk: 100 26 | memory: 32 27 | image: "docker" 28 | google_nvme_disk: 1 29 | } 30 | time_limit { 31 | seconds: 3600 # 60m 32 | } 33 | checkout_strategy { 34 | include_dot_git: true 35 | } 36 | enable_ssh_agent: true 37 | command: "/bin/bash .flexci/launcher.sh kachaka-api.ros.aarch64" 38 | } 39 | } 40 | configs { 41 | key: "kachaka-api.ros.x86_64" 42 | value { 43 | requirement { 44 | cpu: 5 45 | disk: 100 46 | memory: 32 47 | image: "docker" 48 | google_nvme_disk: 1 49 | } 50 | time_limit { 51 | seconds: 3600 # 60m 52 | } 53 | checkout_strategy { 54 | include_dot_git: true 55 | } 56 | enable_ssh_agent: true 57 | command: "/bin/bash .flexci/launcher.sh kachaka-api.ros.x86_64" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.flexci/env.sh: -------------------------------------------------------------------------------- 1 | BUILDKIT_VERSION="v0.12.1" 2 | -------------------------------------------------------------------------------- /.flexci/generate-bake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | TARGET_NAMES=("$@") 4 | 5 | cat << EOF 6 | group "default" { 7 | targets = [ 8 | EOF 9 | 10 | for name in "${TARGET_NAMES[@]}"; do 11 | echo " \"${name}\"," 12 | done 13 | 14 | cat << EOF 15 | ] 16 | } 17 | 18 | target "base" { 19 | dockerfile = "${DOCKERFILE}" 20 | platforms = ["${PLATFORM}"] 21 | EOF 22 | if [[ -n ${OUTPUT_TYPE-} ]]; then 23 | echo " output = [\"type=${OUTPUT_TYPE}\"]" 24 | fi 25 | cat << EOF 26 | args = { 27 | BUILD_VERSION = "${BUILD_VERSION}" 28 | SOFTWARE_VERSION = "${SOFTWARE_VERSION}" 29 | TAG = "${TAG}" 30 | } 31 | } 32 | 33 | EOF 34 | 35 | for name in "${TARGET_NAMES[@]}"; do 36 | if [[ ${USE_STAGE_NAME_AS_TAG:-} == "true" ]]; then 37 | tags="[\"${name}\"]" 38 | else 39 | tags="[\"asia-northeast1-docker.pkg.dev/pfr-flexci/tmp/${name}:${TAG}\"]" 40 | fi 41 | cat << EOF 42 | target "${name}" { 43 | inherits = ["base"] 44 | EOF 45 | cat << EOF 46 | target = "${name}" 47 | tags = ${tags} 48 | } 49 | EOF 50 | done 51 | -------------------------------------------------------------------------------- /.flexci/launcher.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | project="$1" 6 | timeout="${2:-1d}" 7 | no_output_timeout_sec="${3:-300}" 8 | gs_log_file="pfr-flexci-tmp2/baku/log/${project}/${FLEXCI_JOB_ID}-${FLEXCI_RUN_ID}.txt" 9 | gs_stats_file="pfr-flexci-tmp2/baku/log/${project}/${FLEXCI_JOB_ID}-${FLEXCI_RUN_ID}-stats.tar.gz" 10 | 11 | LOG_FILE=/tmp/log.txt 12 | STATS_DIR=/tmp/stats 13 | STOP_FILE=/tmp/stop-launcher 14 | 15 | on_exit() { 16 | readonly STATS_ARCHIVE=/tmp/stats.tar.gz 17 | if [[ -e "${LOG_FILE}" ]]; then 18 | gsutil -q cp "${LOG_FILE}" "gs://${gs_log_file}" 19 | fi 20 | tar -zcf "${STATS_ARCHIVE}" "${STATS_DIR}" 21 | gsutil -q cp "${STATS_ARCHIVE}" "gs://${gs_stats_file}" 22 | } 23 | trap on_exit EXIT 24 | 25 | init_kill_process_group() { 26 | set -m # Enable monitor mode to create process group for each job. 27 | } 28 | 29 | kill_process_group() { 30 | local pid="$1" 31 | # See https://stackoverflow.com/a/15139734 32 | kill -- "-$(ps -o pgid= "${pid}" | tr -d ' ')" || : 33 | } 34 | 35 | detect_no_output() { 36 | while true; do 37 | if [[ -e "${STOP_FILE}" ]]; then 38 | return 39 | fi 40 | if [[ -e "${LOG_FILE}" ]]; then 41 | stamp="$(stat -c %Y ${LOG_FILE})" 42 | now="$(date +%s)" 43 | if [[ $((now - stamp)) -gt "${no_output_timeout_sec}" ]]; then 44 | echo "No output for ${no_output_timeout_sec} sec!" 45 | return 1 46 | fi 47 | fi 48 | sleep 1s 49 | done 50 | } 51 | 52 | gather_stats() { 53 | mkdir -p "${STATS_DIR}" 54 | 55 | vmstat 1 -n -t > "${STATS_DIR}/vmstat.log" & 56 | vmstat_pid=$! 57 | trap 'kill "${vmstat_pid}"' EXIT 58 | 59 | while true; do 60 | if [[ -e "${STOP_FILE}" ]]; then 61 | return 62 | fi 63 | top -b -n1 >> "${STATS_DIR}/top-$(date +%s).log" 64 | sleep 1s 65 | done 66 | } 67 | 68 | init_kill_process_group 69 | 70 | echo 71 | echo "Full log is available here:" 72 | echo "https://storage.cloud.google.com/${gs_log_file}" 73 | echo "Stats are available here:" 74 | echo "https://storage.cloud.google.com/${gs_stats_file}" 75 | echo 76 | 77 | ( .flexci/script.sh "${project}" 2>&1 | ts | tee "${LOG_FILE}" ) & 78 | ( sleep "${timeout}" && echo timeout! && exit 1 ) & 79 | ( detect_no_output ) & 80 | ( gather_stats ) & 81 | 82 | ret=0 83 | wait -n || ret=$? 84 | 85 | touch "${STOP_FILE}" 86 | for pid in $(jobs -p); do 87 | kill_process_group "${pid}" 88 | done 89 | 90 | exit "${ret}" 91 | -------------------------------------------------------------------------------- /.flexci/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | docker run --rm \ 6 | -v "$(pwd)":"$(pwd)" \ 7 | --workdir="$(pwd)" \ 8 | asia-northeast1-docker.pkg.dev/pfr-flexci/tmp/kachaka-api.lint:KEEP-20230828 \ 9 | bash << EOF 10 | python3 -m venv /tmp/env 11 | source /tmp/env/bin/activate 12 | pip3 install -r tools/lint/requirements.txt 13 | pip3 install -e . 14 | ./tools/lint/local_run.sh 15 | EOF 16 | -------------------------------------------------------------------------------- /.flexci/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pysen] 2 | version = "0.9.1" 3 | 4 | [tool.pysen-cli] 5 | settings_dir = "." 6 | 7 | [tool.pysen.lint] 8 | enable_black = false 9 | enable_flake8 = false 10 | enable_isort = false 11 | enable_mypy = false 12 | 13 | [tool.pysen.lint.source] 14 | includes = [ 15 | "launcher.sh", 16 | "script.sh", 17 | ] 18 | 19 | [tool.pysen.plugin.shellcheck] 20 | function = "pysen_plugins::shellcheck" 21 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: '3.10' 20 | cache: 'pip' 21 | - run: pip install -r tools/lint/requirements.txt 22 | - run: pip install -e . 23 | - run: ./tools/lint/local_run.sh 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .ipynb_checkpoints/ 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pf-robotics/kachaka-api-maintainers 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # kachaka-api コントリビューションガイド 2 | 3 | このガイドは、kachaka-api への貢献に関心のあるすべての人を対象としています。私たちは、バグ報告、機能提案、コードの改善、ドキュメントの修正など、あらゆる形の貢献を歓迎します。 4 | 5 | ## はじめに 6 | 7 | kachaka-api への貢献は、プロジェクトをより良くするための貴重な手段です。このガイドラインに従うことで、あなたの貢献がスムーズに受け入れられ、プロジェクトに統合される可能性が高まります。ご協力ありがとうございます! 8 | 9 | ## コントリビューションの種類 10 | 11 | 私たちは、以下のようなコントリビューションを歓迎しています。 12 | 13 | * **バグ報告**: kachaka-api で発見したバグや不具合を報告してください。再現手順や環境情報などを詳しく記述していただけると助かります。 14 | * **コードの改善**: 既存のコードの改善、リファクタリング、パフォーマンス向上など、コードに関する貢献は歓迎です。 15 | * **ドキュメントの修正**: ドキュメントの誤字脱字の修正、内容の改善、不足している情報の追加など、ドキュメントに関する貢献も重要です。 16 | * **機能提案**: 新しい機能や改善案を提案してください。具体的なユースケースや期待される効果などを説明していただけると議論しやすくなります。 17 | 18 | ## コントリビューションの手順 19 | 20 | ### バグ報告や機能提案 21 | 22 | 1. **Issue の作成と確認**: 23 | * バグ報告や機能提案を行う場合は、GitHub IssuesにIssueを作成してください。 24 | * 既存のIssueがないか確認し、もし同じようなIssueがあれば、そちらに参加してください。 25 | * Issueを作成する際は、問題や提案の内容を明確かつ具体的に記述してください。 26 | 27 | ### コードの貢献 28 | 29 | 1. **リポジトリのフォーク**: GitHub (または他のプラットフォーム) 上のプロジェクトのリポジトリをあなたの個人アカウントにフォークしてください。 30 | 1. **ブランチの作成**: 作業を開始する前に、新しいブランチを作成してください。ブランチ名は、関連するIssue番号や変更内容を反映したものにすると分かりやすいです。 31 | ```bash 32 | git checkout -b feature-xyz-issue-123 33 | ``` 34 | 1. **コードの変更とコミット**: コードを変更し、コミットしてください。コミットメッセージは、変更内容を簡潔かつ分かりやすく記述すると良いですが、kachaka-api レポジトリではSquash Mergeを採用しているため、コミットメッセージの詳細度はあまり重要ではありません。 35 | 1. **プッシュ**: ローカルの変更をリモートリポジトリ (あなたのフォーク) にプッシュしてください。 36 | ```bash 37 | git push origin feature-xyz-issue-123 38 | ``` 39 | 1. **Pull Requestの作成**: GitHub (または他のプラットフォーム) 上で、あなたのフォークしたリポジトリから元のリポジトリの `main` (または開発ブランチ) に対してPull Requestを作成してください。 40 | * **重要**: Pull Requestを作成する前に、以下のコマンドを実行してコードのスタイルと品質をチェックしてください。 41 | ```bash 42 | ./tools/lint/run_docker.sh 43 | ``` 44 | このスクリプトは、linterを実行し、コードがプロジェクトの規約に準拠しているかを確認します。 `-i` オプションを追加すると、可能なものは自動修正されます。エラーや警告が出た場合は、修正してからPull Requestを作成してください。 45 | * Pull Requestのタイトルは、英語で変更内容を簡潔に記述してください。 46 | * Pull Requestの説明欄には、変更の目的、背景、関連するIssue番号などを詳しく記述してください。こちらは日本語でも構いません。 47 | 1. **コードレビュー**: あなたのPull Requestは、プロジェクトのメンテナーによってレビューされます。フィードバックがあった場合は、それに対応してコードを修正し、再度プッシュしてください。必要に応じて、再度リンターを実行してください。 48 | 1. **マージ**: レビューが完了し、承認された場合、あなたのPull Requestは `main` ブランチににSquashマージされます。 49 | 50 | ## コーディング規約 51 | 52 | linterチェックが通ることと、ファイル内では一貫したスタイルが使用されていることを確認してください。 53 | 54 | ## 謝辞 55 | 56 | kachaka-api に貢献してくださるすべての方々に感謝申し上げます。あなたの協力が、このプロジェクトをより良くします。 57 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================================================ 2 | FROM python:3.10-slim AS kachaka-api-gen-proto-python 3 | 4 | # install uv 5 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ 6 | 7 | # setup environment 8 | COPY pyproject.toml /src/pyproject.toml 9 | COPY uv.lock /src/uv.lock 10 | COPY tools/gen_proto/python_entrypoint.sh /src/entrypoint.sh 11 | 12 | ENTRYPOINT ["/src/entrypoint.sh"] 13 | 14 | # ================================================================ 15 | FROM ubuntu:22.04 AS kachaka-api-gen-proto-ros2 16 | 17 | RUN apt-get update && \ 18 | apt-get -y install --no-install-recommends \ 19 | protobuf-compiler-grpc \ 20 | && \ 21 | apt-get clean && \ 22 | rm -rf /var/lib/apt/lists/* 23 | 24 | # setup environment 25 | COPY ./tools/gen_proto/ros2_entrypoint.sh /src/entrypoint.sh 26 | 27 | ENTRYPOINT ["/src/entrypoint.sh"] 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | ## 10 | 11 | [スマートファニチャー・プラットフォーム「カチャカ」](https://kachaka.life/) のAPIを提供するリポジトリです。 12 | 13 | カチャカAPIは、カチャカのドッキングや移動を制御したり、カチャカの状態やセンサー値を取得したりするための機能を提供します。 14 | 15 | * ローカルネットワーク内の機器、あるいはカチャカ体内にあるユーザー環境(Playground)からのアクセスが可能です。 16 | * この公式リポジトリでは、PythonやROS 2で簡単にカチャカAPIを利用できるSDKを提供しています。 17 | 18 | * カチャカAPIで利用可能な機能一覧については、「[カチャカAPIでできること](./docs/FEATURES.md)」をご覧ください。 19 | 20 | ### 公式で提供するSDK 21 | 22 | * 🐍 Python 3.10+ 23 | * 🤖 ROS 2 Humble (Ubuntu 22.04 LTS) 24 | 25 | ### その他の言語 26 | カチャカAPIは[gRPC](https://grpc.io/)を使用した通信インターフェースとして提供されています。 27 | これ以外の言語でも、gRPCを直接利用してアクセスすることが可能です。 28 | 29 | ## はじめに 30 | ### カチャカAPIの有効化 31 | > [!IMPORTANT] 32 | > どんな使い方をする場合でも、まずはじめにスマートフォンアプリを使ってカチャカAPIを有効にする必要があります。 33 | 34 | * カチャカに接続し、[⚙設定]のタブから接続するロボットを選択、[カチャカAPI]ページを開いて「カチャカAPIを有効化する」をONにします。 35 | * ダイアログが表示されるので、「利用規約」を確認の上、「カチャカAPI利用規約に同意する」をチェックして「設定する」を押して下さい。 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | ### カチャカのIPアドレスの確認 45 | * またいずれの場合にも、カチャカのIPアドレスが必要になります。 46 | * [⚙設定] > [アプリ情報] から確認することができます。(以下のキャプチャは白塗りしてあります) 47 | * また、mDNSによる名前解決に対応しており、同画面の「シリアル番号」からなる 48 | * `kachaka-<シリアル番号>.local`というホスト名でもアクセス可能です。 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 | ## カチャカAPIマニュアル 58 | 59 | * 📖 [カチャカAPIでできること](./docs/FEATURES.md) 60 | * カチャカAPIでできることをまとめています。 61 | * 🚀 [カチャカAPIを簡単に試してみる (JupyterLab)](./docs/QUICKSTART.md) 62 | * Webブラウザから、JupyterLabを使ってカチャカAPIを利用する方法を説明します。 63 | * OS環境を問わず広く利用できるため、カチャカAPIの動作確認やサンプルコードの実行におすすめです。 64 | * 🐍 [PythonでカチャカAPIを利用する](./docs/PYTHON.md) 65 | * PythonでカチャカAPIを利用する方法を説明します。 66 | * 🤖 [ROS 2でカチャカAPIを利用する](./docs/ROS2.md) 67 | * ROS 2でカチャカAPIを利用する方法を説明します。 68 | * 🏠 [カチャカ体内 (Playground) で自作のプログラムを動かす](./docs/PLAYGROUND.md) 69 | * カチャカ内部には、Playgroundというユーザー用の環境があります。 70 | * 外部機器を用意せずとも、カチャカ体内で自作のプログラムを動かすことが可能です。 71 | * 🌐 [PythonやROS2以外の言語でカチャカAPIを利用する](./docs/GRPC.md) 72 | * PythonやROS2以外の言語でカチャカAPIを利用する方法を説明します。 73 | * 💻 [WebアプリでカチャカAPIを利用する](./docs/WEB.md) 74 | * WebアプリでカチャカAPIを利用する方法を説明します。 75 | 76 | ## 💬 要望・バグ報告・コントリビューション 77 | 78 | * カチャカAPIはOSSとして公開されています。要望やバグ報告など大歓迎です。[コントリビューションガイドライン](./CONTRIBUTING.md)をご覧ください。 79 | * 質問や要望などは、[GitHub Discussions](https://github.com/pf-robotics/kachaka-api/discussions) からお願いします。 80 | 81 | ## License 82 | Copyright 2023 Preferred Robotics, Inc. 83 | Licensed under [the Apache License, Version 2.0](LICENSE). 84 | -------------------------------------------------------------------------------- /docs/FEATURES.md: -------------------------------------------------------------------------------- 1 | # カチャカAPIでできること 2 | 3 | * カチャカAPIの各機能の簡易的な説明は、[カチャカAPIをひとつずつ実行して試す(Jupyter Notebook)](./kachaka_api_client.ipynb)としてまとめています。 4 | * すべてのAPIをひとつずつ実行しながら確認することができるドキュメントになっているので、まずはこちらをご覧ください。 5 | * こちらにないAPIについては、[APIとして欲しい機能](https://github.com/pf-robotics/kachaka-api/discussions/23)にてご要望をお寄せください。 6 | 7 | ## もっと詳しく 8 | 9 | さらにわかりやすく、図を交えて説明したドキュメントを以下に用意しています。 10 | 11 | - [カチャカによる家具の操作](./api/SHELF_HANDLING.md) 12 | - [エラー状態の検知・ハンドリング](./api/ERROR_HANDLING.md) 13 | -------------------------------------------------------------------------------- /docs/LINT.md: -------------------------------------------------------------------------------- 1 | # Lint 2 | 3 | ## Install requirements 4 | 5 | ```bash 6 | pip3 install -r /lint/requirements.txt 7 | pip3 install -e . 8 | ``` 9 | 10 | ## Run format 11 | 12 | ```bash 13 | ./tools/lint/run.sh -i 14 | ``` 15 | 16 | ## Run lint 17 | 18 | ```bash 19 | ./tools/lint/run.sh 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/PLAYGROUND.md: -------------------------------------------------------------------------------- 1 | # カチャカ体内 (Playground) で自作のプログラムを動かす 2 | 3 | カチャカAPIを使ったプログラムを、カチャカ本体の資源の一部(Playground)を利用して動かすことができます。 4 | Playgroundは、カチャカ体内で動くDockerコンテナで、Ubuntu 22.04 LTSをベースにしています。 5 | Playgroundを使えば、カチャカだけで簡潔するシステムを開発することができます。 6 | 7 | 8 | ## 目次 9 | - [Playgroundの仕様](#playgroundの仕様) 10 | - [ポート](#ポート) 11 | - [PlaygroundからカチャカAPIへのアクセス](#playgroundからカチャカapiへのアクセス) 12 | - [Playgroundのリソース制限](#playgroundのリソース制限) 13 | - [Playgroundにsshでログインする](#playgroundにsshでログインする) 14 | - [Playgroundでサンプルプログラムを実行する](#playgroundでサンプルプログラムを実行する) 15 | - [自作ソフトの自動起動](#自作ソフトの自動起動) 16 | - [例) 時報のサンプルを自動起動する](#例-時報のサンプルを自動起動する) 17 | 18 | ## Playgroundの仕様 19 | ### ポート 20 | * カチャカの公開ポートの中で、Playgroundに関連するものは以下の通りです。 21 | 22 | | ポート番号 | 用途 | 23 | | --- | --- | 24 | | 26400 | KachakaAPI サーバー (gRPC) | 25 | | 26500 | Playground の ssh | 26 | | 26501 | Playground の JupyterLab | 27 | | 26502~26509 | 割り当てなし(自由利用可能) | 28 | 29 | ### PlaygroundからカチャカAPIへのアクセス 30 | * Playground内部からカチャカAPIを利用する場合、サーバーのアドレスは `100.94.1.1:26400`になります。 31 | * pythonのkachaka_apiライブラリでは、`KachakaApiClient`のデフォルト値はこのアドレスになっています。 32 | 33 | ### Playgroundのリソース制限 34 | 35 | * ストレージ総計(/home, tmp) 3GB 36 | * メモリー 512MB 37 | 38 | 39 | ## Playgroundにsshでログインする 40 | 41 | * jupyterlabのterminalもしくは下記のnotebookいずれかを用いて公開鍵の設定を行います 42 | * utils/set_authorized_keys.ipynb 43 | * utils/set_authorized_keys_from_github.ipynb 44 | * githubに登録している鍵をカチャカでも利用したい場合、こちらのスクリプトが便利です 45 | * utils/set_authorized_keys.ipynbを使用した設定方法 46 | * 画面左上のFile Browserを選択します。 47 | * 画面左のファイル一覧からutils → set_authorized_keys.ipynbをダブルクリックします。 48 | * 画面中央のpublic_keysに公開鍵のテキストを貼り付けます。 49 | * 上部メニューの「▶▶」ボタンを押します。 50 | 51 | set-authorized-keys 52 | 53 | * utils/set_authorized_keys_from_github.ipynbを使用した設定方法 54 | * 画面左上のFile Browserを選択します。 55 | * 画面左のファイル一覧からutils → set_authorized_keys_from_github.ipynbをダブルクリックします。 56 | * 画面中央のuserにgithubのユーザ名を入力します。 57 | * 上部メニューの「▶▶」ボタンを押します 58 | 59 | set-authorized-keys-from-github 60 | 61 | 以下のコマンドを実行してPlaygroundにログインします 62 | 63 | ```bash 64 | ssh -p 26500 -i <登録した公開鍵に対応する秘密鍵> kachaka@ 65 | ``` 66 | 67 | ## Playgroundでサンプルプログラムを実行する 68 | 69 | * カチャカにsshでログインします。 70 | * 以下のコマンドを実行すると、カチャカが時報を1分間隔で発話します。 71 | 72 | ```bash 73 | cd ~ 74 | git clone https://github.com/pf-robotics/kachaka-api.git 75 | pip install -r /home/kachaka/kachaka-api/python/demos/requirements.txt 76 | python3 /home/kachaka/kachaka-api/python/demos/time_signal.py 100.94.1.1:26400 77 | ``` 78 | 79 | ## 自作ソフトの自動起動 80 | 81 | * カチャカが再起動したときに自動でプログラムが実行されてほしい場合には、自動起動の機能を使うことができます。 82 | * Playgroundの `/home/kachaka/kachaka_startup.sh` に自動起動したい処理を記述すると、カチャカ起動時に自動的に実行されます。 83 | * ログは `/tmp/kachaka_startup.log` に記録されます 84 | * python3 を自動起動する際は `-u` オプションを付けると良いです。そうでないと標準出力がバッファリングされてしまい、ログが確認できないことがあります。 85 | 86 | ### 例) 時報のサンプルを自動起動する 87 | 88 | * 例として、カチャカによる時報のサンプルを自動起動する場合を示します。 89 | * `/home/kachaka/kachaka_startup.sh` を以下のように編集します。 90 | 91 | ```bash 92 | #!/bin/bash 93 | 94 | jupyter-lab --port=26501 --ip='0.0.0.0' & 95 | 96 | # 以下の行を追加します 97 | python3 -u /home/kachaka/kachaka-api/python/demos/time_signal.py 100.94.1.1:26400 & 98 | ``` 99 | 100 | * 保存後、カチャカを再起動して暫くすると、1分間隔で現在時刻を発話します。 101 | -------------------------------------------------------------------------------- /docs/PYTHON.md: -------------------------------------------------------------------------------- 1 | # PythonでカチャカAPIを利用する 2 | 3 | * カチャカAPIをPythonで利用しやすいよう `kachaka_api` というPythonライブラリを公式で提供しています。 4 | * [JupyterLabを使ってカチャカAPIを試してみる](./QUICKSTART.md) では、このライブラリを利用しています。このドキュメントでは、ご自身のPCでPythonを実行してカチャカAPIを利用する方法を説明します。 5 | 6 | ## 目次 7 | - [インストール方法](#インストール方法) 8 | - [基本的な使い方](#基本的な使い方) 9 | - [サンプルコード](#サンプルコード) 10 | - [非同期ライブラリ (aio)](#非同期ライブラリ-aio) 11 | 12 | 13 | ## インストール方法 14 | 15 | ### pip 16 | 以下のコマンドでインストールできます: 17 | 18 | ```bash 19 | pip install kachaka-api 20 | ``` 21 | 22 | ### uv 23 | uvの場合は以下でインストールできます。 24 | 25 | ```bash 26 | uv add kachaka-api 27 | ``` 28 | 29 | ### リリースとバージョン番号 30 | kachaka_apiライブラリは、カチャカのSWアップデートの公開のタイミングでリリースされます (数日から1週間程度遅れる場合があります)。 31 | バージョンは4つの番号からなり、カチャカSWバージョンの3つの番号に、kachaka_apiライブラリのバージョン番号が続きます。 32 | たとえばカチャカSW3.10.6のリリース時にはkachaka_apiライブラリ3.10.6.0がリリースされ、ここからライブラリのみに重要な変更があった場合には、4つ目の番号がインクリメントされた3.10.6.1、3.10.6.2といったリリースが行われます。 33 | 34 | ## 基本的な使い方 35 | * kachaka_apiライブラリでは、 `KachakaApiClient` クラスが1台のカチャカに対応します。 36 | * ひとつひとつのAPIは、このクラスのメソッドとして実行することができます。 37 | 38 | ```python 39 | from kachaka_api import KachakaApiClient 40 | 41 | client = KachakaApiClient(target="192.168.1.100:26400") 42 | 43 | # 状態の取得 44 | current_pose = client.get_robot_pose() 45 | print(f"current pose: {current_pose}") 46 | 47 | # 操作や命令 48 | client.speak("カチャカです、よろしくね") 49 | ``` 50 | 51 | * すべてのAPIをひとつずつ説明とともに実行できるようにしたノートブックを公開しています。 52 | * [kachaka_api_client.ipynb (カチャカAPIドキュメント)](./kachaka_api_client.ipynb) 53 | 54 | 55 | 56 | 57 | > [!CAUTION] 58 | > jupyterをご自身のPCで実行する場合は、先頭のセルでKachakaApiClientにカチャカのIPアドレスを指定してください。 59 | > ```diff 60 | > import kachaka_api 61 | > 62 | > -client = kachaka_api.KachakaApiClient() 63 | > +client = kachaka_api.KachakaApiClient(target="<ご自身のカチャカのIPアドレス>:26400") 64 | > ``` 65 | 66 | ## サンプルコード 67 | 68 | * [sample_llm_speak.py](../python/demos/sample_llm_speak.py) ... ChatGPT を使って、コマンド終了時にお喋りをするサンプル 69 | * その他、[python/demos/](../python/demos) 以下に、JupyterLab で利用できる Notebook 形式が多数あります 70 | 71 | ## 非同期ライブラリ (aio) 72 | > [!NOTE] 73 | > Pythonにまだあまり慣れていない方は、この節は読み飛ばして大丈夫です。 74 | 75 | * カチャカAPIは、同期、非同期の2つのインターフェースを用意しています。 76 | * インターフェースはどちらも基本的に同じで、インポート元をaioに切り替えるだけで、async版のインターフェースを利用できます。 77 | 78 | ```python 79 | import asyncio 80 | 81 | from kachaka_api.aio import KachakaApiClient 82 | 83 | async def main() -> None: 84 | client = KachakaApiClient(target="192.168.1.100:26400") 85 | 86 | # 状態の取得 87 | current_pose = await client.get_robot_pose() 88 | print(f"current pose: {current_pose}") 89 | 90 | # 操作や命令 91 | await client.speak("カチャカです、よろしくね") 92 | 93 | if __name__ == "__main__": 94 | asyncio.run(main()) 95 | ``` 96 | 97 | 98 | * また非同期ライブラリは、同期ライブラリの機能に加えてcallback登録をサポートしています。 99 | * callback機能については[sample_llm_speak.py](../python/demos/sample_llm_speak.py)をご参照ください。 100 | 101 | * 非同期も同様にAPIをひとつずつ実行するドキュメントを用意しています。 102 | * [kachaka_api_client_async.ipynb](./kachaka_api_client_async.ipynb) 103 | 104 | -------------------------------------------------------------------------------- /docs/QUICKSTART.md: -------------------------------------------------------------------------------- 1 | # カチャカAPIを簡単に試してみる (JupyterLab) 2 | 3 | カチャカ本体内で動作するJupyterLabを利用することで、OSを問わずWebブラウザのみでカチャカAPI (Python) を実行することができます。カチャカAPIの動作確認やサンプルコードの実行におすすめです。 4 | 5 | > [!NOTE] 6 | > 対応ブラウザについては、[JupyterLab公式ドキュメント](https://jupyterlab.readthedocs.io/en/stable/getting_started/installation.html#supported-browsers)をご確認ください。 7 | 8 | ## 目次 9 | - [JupyterLabを開く](#JupyterLabを開く) 10 | - [サンプルコードを動かしてみる](#サンプルコードを動かしてみる) 11 | - [サンプルコードのダウンロード](#サンプルコードのダウンロード) 12 | - [サンプル実行のための依存ライブラリのインストール](#サンプル実行のための依存ライブラリのインストール) 13 | - [サチャカが発話するサンプルコード](#カチャカが発話するサンプルコード) 14 | - [カチャカAPIのメソッドを試してみる](#カチャカAPIのメソッドを試してみる) 15 | 16 | ## JupyterLabを開く 17 | 18 | 1. カチャカのIPアドレスを確認します。 19 | * スマートフォンアプリの「⚙設定」>「アプリ情報」>「IPアドレス」にカチャカのIPアドレスが記載されています。 20 | 2. ブラウザを起動し、以下のURLにアクセスします。 21 | * `http://<カチャカのIPアドレス>:26501/` (例: `http://192.168.0.20:26501/`) 22 | 23 | * ログイン画面が表示されるので、以下のパスワードを入力してください。 24 | * パスワード:kachaka 25 | 26 | ![jupyter-login](./quickstart/images/jupyter-login.png) 27 | 28 | * パスワードを変更する場合は、以下をご覧ください。 29 |
30 | パスワードの変更方法 31 | 32 | * パスワードを変更する場合は、まずLauncherから「Terminal」を選択します。 33 | 34 | 35 | 36 | * Terminalで、以下のコマンドを入力します。 37 | 38 | ```console 39 | $ jupyter lab password 40 | Enter password: <新しいパスワード> 41 | Verify password: <新しいパスワード> 42 | ``` 43 | 44 | * カチャカ本体を再起動すると、新しいパスワードが反映されます。 45 | 46 |
47 | 48 | ## サンプルコードを動かしてみる 49 | ### サンプルコードのダウンロード 50 | 51 | * 左側のファイル一覧から README.ipynb をダブルクリックしてください。 52 | * 上部メニューの「▶▶」をクリックしてください。 53 | 54 | 55 | 56 | * 以下のダイアログが表示された場合は「Restart」ボタンを押してください。 57 | 58 | 59 | 60 | * サンプルコードのダウンロードが完了すると、以下のようなメッセージが表示され、左側のファイル一覧に kachaka-apiフォルダが作成されます。 61 | 62 | 63 | 64 | ### サンプル実行のための依存ライブラリのインストール 65 | 66 | サンプルを実行するために必要な依存ライブラリをインストールします。 67 | ダウンロード後初回の一回だけでOKです。 68 | 69 | * 左側のファイル一覧からkachaka-api/python/demosフォルダを選択します。 70 | * install_libraries.ipynbをダブルクリックすると、右側にソースコードが表示されます。 71 | * 上部メニューの「▶▶」ボタンを押して実行します。 72 | 73 | ### カチャカが発話するサンプルコード 74 | 75 | * 左側のファイル一覧からkachaka-api/python/demosフォルダを選択します。 76 | * 例えばspeak.ipynbをダブルクリックして開いてみましょう。 77 | * すると、右側にソースコードが表示されます。 78 | * 上部メニューの「▶▶」ボタンを押すと、コード全体が実行されます。 79 | 80 | 81 | 82 | 実行結果 83 | 84 | 「カチャカです、よろしくね」とカチャカが発話します。 85 | 86 | 87 | ## カチャカAPIのメソッドを試してみる 88 | * 今度は、`kachaka-api/docs/kachaka_api_client.ipynb` を開いてみましょう。 89 | * すべてのAPIを1つずつ説明付きで実行するドキュメントになっています。 90 | 91 | 92 | 93 | * 上部メニューで今度は「▶」ボタンを押すと、コードが一つずつ実行されます。 94 | * 一番上から順番に実行して、ひとつひとつのAPIの動作を確認してみましょう。 95 | -------------------------------------------------------------------------------- /docs/WEB.md: -------------------------------------------------------------------------------- 1 | # WebアプリでカチャカAPIを利用する 2 | 3 | * ブラウザで動作するwebアプリからも、カチャカAPIを利用することができます。 4 | * ただし、現状webアプリから直接gRPC (HTTP/2) を使うことはできないため、プロキシサーバーを立ててgrpc-webのプロトコルで通信する必要があります。 5 | * gRPCの通信に必要なHTTP/2の通信をjavascriptから自由に取り扱うことが可能な環境は限られており、HTTP/1.1で表現するgrpc-webプロトコルが必要です。 6 | * ここでは、プロキシをenvoyを使って立てる方法を紹介し、webアプリからカチャカAPIを利用するサンプルをご紹介します。 7 | 8 | ## 目次 9 | - [プロキシサーバ](#プロキシサーバ) 10 | - [Webサンプル(React + TypeScript)](#webサンプル-react--typescript) 11 | 12 | 13 | ## プロキシサーバ 14 | 15 | * プロキシサーバーの起動 16 | 17 | ```bash 18 | $ ./tools/web_proxy/start_proxy_remote.sh <カチャカのIPアドレス> 19 | ``` 20 | 21 | * プロキシサーバーは、webアプリからアクセスでき、またカチャカにアクセスできるネットワーク接続を備えた場所であればどこで起動しても構いません。 22 | * このスクリプトでは、`localhost:50000`にプロキシサーバを立てる例を示しています。 23 | 24 | ## Webサンプル (React + TypeScript) 25 | 26 | ![](web/images/web_sample_capture.png) 27 | 28 | * Reactを利用して、カチャカAPIと連携するwebアプリのサンプルです。 29 | * プラスボタンを押すとパネルが追加され、パネルの種類を選ぶと対応するAPIを利用した表示がなされるデモです。 30 | * 起動するには、以下のコマンドを実行してください。 31 | 32 | ```bash 33 | $ cd web/demos/kachaka_api_web_sample 34 | $ npm install 35 | $ npm run dev 36 | ``` 37 | 38 | * npmの環境がない方は、適宜インストール作業を行って下さい。 39 | * 以下にインストール方法の例を示します。 40 | * aptでインストールされるnpmは古い可能性があります。nで最新のstableをインストールすることを推奨します。 41 | * サンプルは nodejs v18.17.1、npm 9.6.7 で動作を確認しています。 42 | 43 | ```bash 44 | $ sudo apt install nodejs npm 45 | $ sudo npm install -g n 46 | $ sudo n stable 47 | ``` 48 | 49 | ### カチャカAPIとReact hook 50 | * [Cursorによるロングポーリング](./GRPC.md#cursorによるロングポーリング)で紹介していますが、カチャカAPIではcursorという概念を用いることで、無駄のない値の更新を行っています。 51 | * React hookでは、このカーソルの更新をつかってGet系のAPIを呼び出し、レスポンスが返ってくるたびにstateを更新することで、値が変更されるたびに最低限の計算でレンダリングを行うことができます。 52 | * 具体的な処理については `web/demos/kachaka_api_web_sample/src/kachakaApi.ts` を参照してください。 53 | -------------------------------------------------------------------------------- /docs/api/ERROR_HANDLING.md: -------------------------------------------------------------------------------- 1 | # エラー状態の検知・ハンドリング 2 | 3 | カチャカが何かの動作に失敗したり、機器の不具合が生じたときなどには、カチャカAPI経由でそのエラー情報が伝達されます。こうして伝達されるエラーには、かならずエラーコード(番号)が割り当てられています。このエラー番号を参照することで、どんなエラーが起きているのか、どう対処すればいいのかを知ることができます。 4 | 5 | ![カチャカのエラー状態](./images/kachaka_errors.png) 6 | 7 | ## エラー発生の系統 8 | カチャカがエラーを発行するタイミングは大きくわけて2つあります。 9 | 10 | ### 同期エラー 11 | 同期エラーとは、API経由でユーザーがカチャカに対してなにかを指令した際に、その返答として返ってくるエラーのことです。たとえば、音量を指定しようとしてSetVolume APIを呼び出したときに、その設定しようとしている音量が範囲外の値の場合には、レスポンスにそのエラー情報が含まれて返ってきます。 12 | 13 | このような同期のAPIには、レスポンスに以下のResultメッセージが含まれています。 14 | `success`がfalseの場合にはエラーを示しており、`error_code`にエラーコードが含まれています。 15 | 16 | ```protobuf 17 | message Result { 18 | bool success = 1; 19 | int32 error_code = 3; 20 | } 21 | ``` 22 | 23 | たとえば音量を設定するSetVolume APIは、以下のようなレスポンスになっています。 24 | 25 | ```protobuf 26 | message SetSpeakerVolumeResponse { 27 | Result result = 1; 28 | } 29 | ``` 30 | 31 | 32 | ### 非同期エラー 33 | 非同期エラーとは、なにかのAPI呼び出しとは関係なく発生しているエラーのことです。 34 | たとえば、「一時停止している」「障害物があって動けない」といったものから、カメラの不調など機器的な不具合まで、幅広いカチャカの状態について扱います。 35 | このような非同期エラーは、`GetError` APIを呼び出すことで取得できます。 36 | 37 | ```protobuf 38 | message GetErrorResponse { 39 | Metadata metadata = 1; 40 | repeated int32 error_codes = 2; 41 | } 42 | ``` 43 | 44 | レスポンスは上の通りで、`error_codes`という配列が現在のエラー状態を示すエラーコードをすべて含んでいます。 45 | 一時停止のような持続的な状態については、それが解消されるまで含まれます。 46 | 一方、「障害物があって動けない」といったようなイベント的なエラーについては、そのエラーが発生した時点で含まれます。 47 | 48 | ## エラーの種類 49 | エラーコードとその意味の対応表はロボットがマスターデータを持っており、`GetRobotErrorCodeJson` APIで取得できます。 50 | 原則として番号が別の意味を持つようになることはなく、バージョンアップに対しても一貫性を持ちます。(同じ系のエラーが細分化されることはあります) 51 | 52 | ### エラーコードの抜粋 53 | 部分的に抜粋すると、以下のようになっています。たとえば一時停止状態かどうかは、21051がエラーとして含まれているかどうかで取得することができます。 54 | 55 |
56 | エラーコードの抜粋 57 | 58 | ```json 59 | [ 60 | ... 61 | { 62 | "code": 14605, 63 | "title": "充電ドック上に家具を置くことはできません", 64 | "description": "", 65 | "title_en": "Furniture cannot be placed on the charging dock", 66 | "description_en": "", 67 | "error_type": "Error", 68 | "ref_url": "" 69 | }, 70 | { 71 | "code": 14606, 72 | "title": "家具を載せていません", 73 | "description": "", 74 | "title_en": "Kachaka is not docked with a furniture", 75 | "description_en": "", 76 | "error_type": "Error", 77 | "ref_url": "" 78 | }, 79 | ... 80 | { 81 | "code": 21051, 82 | "title": "一時停止しています", 83 | "description": "電源ボタンを押して解除してください。", 84 | "title_en": "Kachaka is paused", 85 | "description_en": "Please press the power button to resume from the pause state.", 86 | "error_type": "Warn", 87 | "ref_url": "" 88 | }, 89 | { 90 | "code": 21052, 91 | "title": "段差を検知しました", 92 | "description": "段差を検知したため、一時停止しました。段差のない場所にカチャカを移動した上で、電源ボタンを押して解除してください。", 93 | "title_en": "Step detected", 94 | "description_en": "Kachaka has paused because a step was detected. Please move Kachaka to a flat surface and press the power button to resume from the pause state.", 95 | "error_type": "Error", 96 | "ref_url": "" 97 | }, 98 | ... 99 | ``` 100 |
101 | 102 | ### エラーの深刻さ 103 | 上で取得されるjsonに、"error_type"というフィールドが含まれています。これはエラーの深刻さを示しており、同期エラーと非同期エラーでそれぞれ以下の種類があります。 104 | 105 | #### 同期エラー(API呼び出し時) 106 | | エラータイプ | 説明 | 107 | |------------|------| 108 | | Ignore | 成功ではないが、無視できる程度 | 109 | | Warn | 一応伝えておいたほうがいい程度 | 110 | | Error | 失敗だが、再度試すと成功する可能性がある | 111 | | Bug | 異常系の発生 (アップデートが必要) | 112 | 113 | #### 非同期エラー(ロボットの状態) 114 | | エラータイプ | 説明 | LED | 115 | |------------|------|------| 116 | | Warn | 伝えておいたほうがいい程度の情報 | | 117 | | Error | 再起動以外で復帰可能 | 黄色 | 118 | | Recoverable | 一定時間で復帰可能 | 白色が回転 | 119 | | Fatal | 再起動で復帰可能 | 赤色 | 120 | | CallForSupport | 深刻な故障 (カスタマーサポートにご連絡ください) | 赤色点滅 | 121 | -------------------------------------------------------------------------------- /docs/api/SHELF_HANDLING.md: -------------------------------------------------------------------------------- 1 | # 家具の移動 2 | 3 | ## 基本のAPI 4 | 家具の移動を行うAPIは複数あります。基本となるのは、以下の4つです。 5 | 6 | | API | 役割 | 7 | | --- | --- | 8 | | `move_shelf` | 指定した家具を指定した場所に移動する | 9 | | `return_shelf`| 指定した家具を家具のホームに片付ける | 10 | | `dock_shelf` | カチャカの正面にある家具をドッキングする | 11 | | `undock_shelf`| 載せている家具をそこに置いて脱出する | 12 | 13 | 14 | ### move_shelf 15 | * 呼び出し: `move_shelf(shelf_id, location_id)` 16 | * 家具(ID: shelf_id)を指定した場所(ID: location_id)に移動します。 17 | 18 | ![](./images/move_shelf.png) 19 | 20 | 21 | ### return_shelf 22 | * 呼び出し: `return_shelf(shelf_id)` 23 | * 家具(ID: shelf_id)を家具のホームに片付けます。 24 | 25 | * 現在載せている家具を片付ける場合は、shelf_idを空にして `return_shelf("")` とします。 26 | 27 | ![](./images/return_this_shelf.png) 28 | 29 | * 現在載せていない家具を指定すると、その家具の場所に行ってドッキングし、片付けます。 30 | 31 | ![](./images/return_shelf.png) 32 | 33 | ### dock_shelf 34 | * 呼び出し: `dock_shelf()` 35 | * カチャカの正面にある家具をドッキングします。 36 | 37 | ![](./images/dock_shelf.png) 38 | 39 | ### undock_shelf 40 | * 呼び出し: `undock_shelf()` 41 | * 載せている家具をそこに置き、家具下から脱出します。前後どちらに抜けるかは、空き具合によって判断されます。 42 | 43 | ![](./images/undock_shelf.png) 44 | 45 | ## 高度なAPI 46 | * 加えて、より高度なユースケースに対応するために、以下のAPIが提供されています。 47 | 48 | | | | 49 | | --- | --- | 50 | | `dock_any_shelf_with_registration` | 指定した場所に登録されている家具をドッキングする | 51 | 52 | ### dock_any_shelf_with_registration 53 | 54 | * 呼び出し: `dock_any_shelf_with_registration(location_id)` 55 | * 指定した場所に置かれている任意の家具をドッキングします。 56 | * **家具が未登録の場合は、ドッキング後に家具を登録します。** 57 | 58 | ![](./images/dock_any_shelf_with_registration.png) 59 | -------------------------------------------------------------------------------- /docs/api/images/dock_any_shelf_with_registration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/api/images/dock_any_shelf_with_registration.png -------------------------------------------------------------------------------- /docs/api/images/dock_shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/api/images/dock_shelf.png -------------------------------------------------------------------------------- /docs/api/images/kachaka_errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/api/images/kachaka_errors.png -------------------------------------------------------------------------------- /docs/api/images/move_shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/api/images/move_shelf.png -------------------------------------------------------------------------------- /docs/api/images/return_shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/api/images/return_shelf.png -------------------------------------------------------------------------------- /docs/api/images/return_this_shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/api/images/return_this_shelf.png -------------------------------------------------------------------------------- /docs/api/images/undock_shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/api/images/undock_shelf.png -------------------------------------------------------------------------------- /docs/images/kachaka_api.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/images/kachaka_api.webp -------------------------------------------------------------------------------- /docs/images/kachaka_api_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/images/kachaka_api_logo.png -------------------------------------------------------------------------------- /docs/images/spapp_kachaka_api_enable_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/images/spapp_kachaka_api_enable_dialog.png -------------------------------------------------------------------------------- /docs/images/spapp_kachaka_api_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/images/spapp_kachaka_api_screen.png -------------------------------------------------------------------------------- /docs/images/spapp_kachaka_app_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/images/spapp_kachaka_app_info.png -------------------------------------------------------------------------------- /docs/images/spapp_kachaka_app_info_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/images/spapp_kachaka_app_info_screen.png -------------------------------------------------------------------------------- /docs/playground/images/set_authorized_keys_from_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/playground/images/set_authorized_keys_from_github.png -------------------------------------------------------------------------------- /docs/playground/images/set_authorized_kyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/playground/images/set_authorized_kyes.png -------------------------------------------------------------------------------- /docs/python/images/kachaka_api_client_docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/python/images/kachaka_api_client_docs.png -------------------------------------------------------------------------------- /docs/quickstart/images/jupyter-clone-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/jupyter-clone-sample.png -------------------------------------------------------------------------------- /docs/quickstart/images/jupyter-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/jupyter-login.png -------------------------------------------------------------------------------- /docs/quickstart/images/jupyter-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/jupyter-readme.png -------------------------------------------------------------------------------- /docs/quickstart/images/jupyter-restart-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/jupyter-restart-dialog.png -------------------------------------------------------------------------------- /docs/quickstart/images/jupyter-sample-speak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/jupyter-sample-speak.png -------------------------------------------------------------------------------- /docs/quickstart/images/jupyter-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/jupyter-terminal.png -------------------------------------------------------------------------------- /docs/quickstart/images/jupyter_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/jupyter_url.png -------------------------------------------------------------------------------- /docs/quickstart/images/kachaka_api_client_docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/quickstart/images/kachaka_api_client_docs.png -------------------------------------------------------------------------------- /docs/web/images/web_sample_capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/docs/web/images/web_sample_capture.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "kachaka-api" 3 | version = "3.11.11" 4 | authors = [{name="Preferred Robotics inc."}] 5 | dependencies = [ 6 | "grpcio==1.66.1", 7 | "numpy>=2.2.0", 8 | "protobuf>=5.27.2", 9 | ] 10 | 11 | 12 | readme = "README.md" 13 | 14 | [project.urls] 15 | Repository = "https://github.com/pf-robotics/kachaka-api" 16 | 17 | [tool.setuptools] 18 | package-dir = {"" = "python"} 19 | 20 | [tool.mypy] 21 | files = [ 22 | "python/kachaka_api/base.py", 23 | "python/kachaka_api/aio/", 24 | "python/kachaka_api/util/", 25 | "python/demos/sample_llm_speak.py", 26 | ] 27 | ignore_missing_imports = true 28 | 29 | [tool.pysen] 30 | version = "0.10.5" 31 | 32 | [tool.pysen.lint] 33 | enable_black = true 34 | enable_flake8 = true 35 | enable_isort = true 36 | enable_mypy = false 37 | line_length = 80 38 | py_version = "py310" 39 | 40 | [tool.pysen.lint.source] 41 | excludes = [ 42 | ".flexci/env.sh", 43 | "python/kachaka_api/generated", 44 | ] 45 | 46 | [tool.pysen.plugin.clang_format] 47 | function = "pysen_plugins::clang_format" 48 | 49 | [tool.pysen.plugin.clang_format.config] 50 | extensions = [".cc", ".cpp", ".h", ".hpp"] 51 | 52 | [tool.pysen.plugin.cmake_format] 53 | function = "pysen_plugins::cmake_format" 54 | 55 | [tool.pysen.plugin.shellcheck] 56 | function = "pysen_plugins::shellcheck" 57 | -------------------------------------------------------------------------------- /python/demos/README_SMART_SPEAKER.md: -------------------------------------------------------------------------------- 1 | # スマートスピーカー連携サンプル 2 | 3 | ## はじめに 4 | * このサンプルは、カチャカ、Google Home、IFTTT、Beebotteのサービス連携を行います。 5 | * Google Nest Miniなどのデバイス、およびIFTTT、Beebotteサービスへの登録が必要になります。 6 | 7 | ## Beebotteの設定 8 | 9 | * Beebotteのサイトにログインします: https://beebotte.com/ 10 | * 新しいチャンネル/リソースを作ります 11 | * 左メニューのChannelsを選択 12 | * My Channels右の「Create New」ボタンを押します 13 | * Channel Name: test 14 | * Resource name: sample 15 | * 「Create Channel」ボタンを押します 16 | * 以下の場所にあるトークンを保存しておきます 17 | * 左メニューのChannelsを選択 18 | * My Channelsからtestを選択 19 | * Channel Token: token_xxxxxxx 20 | 21 | ## IFTTTの設定 22 | 23 | * IFTTTのサイトにログインします: https://ifttt.com/ 24 | * 「create」ボタンを押します 25 | * 「If This」を押して、以下の設定にします 26 | * Choose a serviceからGoogle Assistant V2を選択します 27 | * 「activate scene」を押します 28 | * 「シェルフを持ってきて」など、サービスを呼び出す時にGoogle Homeに呼びかける言葉を設定します 29 | * 「Create Trigger」を押します 30 | * 「Then That」を押して、以下の設定にします 31 | * Choose a serviceからWebhooksを選択します 32 | * 「Make a web request」を押して、以下のように設定します 33 | * URL: https://api.beebotte.com/v1/data/publish/test/sample?token= 34 | * Method: POST 35 | * Content Type: application/json 36 | * Body: {"data": {}} 37 | * 「Create action」を押します 38 | 39 | ## Google Homeの設定 40 | 41 | * 以下を参考に設定します 42 | * https://support.google.com/googlenest/answer/7194656?hl=en&co=GENIE.Platform%3DAndroid 43 | * TIPS 44 | * 標準的なIFTTTの使用法の場合「OKグーグル、シェルフを持ってきてを有効にして」のように発話しないと実行できません。 45 | * Google Assistantアプリのルーティン設定で「OKグーグル、シェルフを持ってきて」の発話で実行できるように変更出来ます。 46 | * 「オートメーション」を選択 47 | * 「+」で新しいルーティンを追加します 48 | * 「開始条件を追加」→「Googleアシスタントに話しかけたとき」→「シェルフを持ってきて」を設定し、「条件を追加」を押します 49 | * 「アクションを追加」→「カスタムアクションの追加」→「シェルフを持ってきてを有効にして」を設定し、「完了」を押します 50 | 51 | ## 準備 52 | 53 | ``` 54 | git clone https://github.com/pf-robotics/kachaka-api.git # 本リポジトリ 55 | python3 -m venv venv 56 | source venv/bin/activate 57 | cd kachaka-api/python/demos 58 | pip install -r requirements.txt 59 | 60 | python -m grpc_tools.protoc -I../../protos --python_out=. --pyi_out=. --grpc_python_out=. ../../protos/kachaka-api.proto 61 | 62 | wget https://beebotte.com/certs/mqtt.beebotte.com.pem 63 | ``` 64 | 65 | ## 実行 66 | 67 | ``` 68 | export TOKEN= 69 | python smart_speaker.py <カチャカのIDアドレス>:26400 70 | ``` 71 | 72 | Google Homeに対して、「OKグーグル、シェルフを持ってきて」と発話すると、カチャカがシェルフを運びます。 73 | 74 | -------------------------------------------------------------------------------- /python/demos/emergency_stop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | from __future__ import annotations 5 | 6 | import argparse 7 | import asyncio 8 | 9 | from kachaka_api.aio import KachakaApiClient # noqa: E402 10 | 11 | 12 | def parse_args() -> argparse.Namespace: 13 | parser = argparse.ArgumentParser(description="import map data from file") 14 | 15 | parser.add_argument( 16 | "--ip_address", 17 | type=str, 18 | help="IP Address of Kachaka", 19 | ) 20 | return parser.parse_args() 21 | 22 | 23 | async def main() -> None: 24 | args = parse_args() 25 | 26 | client = ( 27 | KachakaApiClient() 28 | if args.ip_address is None 29 | else KachakaApiClient(f"{args.ip_address}:26400") 30 | ) 31 | 32 | result = await client.set_emergency_stop() 33 | print(f"emergency stopped: {result}") 34 | 35 | 36 | if __name__ == "__main__": 37 | asyncio.run(main()) 38 | -------------------------------------------------------------------------------- /python/demos/error_code.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6ded05f1-a5f2-49d3-9db3-0a42d250a315", 6 | "metadata": {}, 7 | "source": [ 8 | "# カチャカのエラー取得\n", 9 | "\n", 10 | "カチャカのエラーには2種類あります。\n", 11 | "\n", 12 | "* API呼び出しに対する同期エラー\n", 13 | "* API呼び出しに対応しない非同期エラー ... 故障検知、一時停止状態、など\n", 14 | "\n", 15 | "ここでは、エラーコードの詳細テーブルと、非同期エラーの取得方法を示します。\n", 16 | "\n", 17 | "このNotebookを実行すると、エラーコードの詳細テーブルが表示され、その下に現在の非同期エラーの値(通常は \"No error\")が表示されます。\n", 18 | "その状態でカチャカの電源ボタンを押すと一時停止状態を切り替えることができるので、非同期エラーの値が変わるのが確認できると思います。" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "id": "b66b9497-256c-462d-8e81-55e099d890f9", 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import kachaka_api\n", 29 | "\n", 30 | "client = kachaka_api.aio.KachakaApiClient()\n", 31 | "error_code = await client.get_robot_error_code()" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "id": "39fd97e9-6dc3-4a26-b461-680fc19ac953", 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "from IPython.display import HTML, display\n", 42 | "\n", 43 | "html = \"
\"\n", 44 | "for code, value in error_code.items():\n", 45 | " html += f\"\"\n", 46 | "html += \"
codeerror typetitle (ja)description (ja)title (en)description (en)
{code}{value.error_type}{value.title}{value.description}{value.title_en}{value.description_en}
\"\n", 47 | "\n", 48 | "display(HTML(html))" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "id": "71206e4b-2ea4-4edf-bb92-1a94d16726b0", 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "async for errors in client.error.stream():\n", 59 | " if errors:\n", 60 | " for code in errors:\n", 61 | " err = error_code[code]\n", 62 | " print(f\" * error_code = {err.code}\")\n", 63 | " print(f\" title = {err.title} ({err.title_en})\")\n", 64 | " print(f\" description = {err.description} ({err.description_en})\")\n", 65 | " else:\n", 66 | " print(\" * No error\")\n", 67 | " print(\"Waiting for next error...\")" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "id": "375417be-44b7-4368-9719-8d1348d5211c", 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [] 77 | } 78 | ], 79 | "metadata": { 80 | "kernelspec": { 81 | "display_name": "Python 3 (ipykernel)", 82 | "language": "python", 83 | "name": "python3" 84 | }, 85 | "language_info": { 86 | "codemirror_mode": { 87 | "name": "ipython", 88 | "version": 3 89 | }, 90 | "file_extension": ".py", 91 | "mimetype": "text/x-python", 92 | "name": "python", 93 | "nbconvert_exporter": "python", 94 | "pygments_lexer": "ipython3", 95 | "version": "3.10.12" 96 | } 97 | }, 98 | "nbformat": 4, 99 | "nbformat_minor": 5 100 | } 101 | -------------------------------------------------------------------------------- /python/demos/get_front_camera.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "4bcba01b-7d02-4d53-84b1-e5d71ae5b8f5", 6 | "metadata": {}, 7 | "source": [ 8 | "# カメラ画像の取得" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "8d08024f-7938-4a12-8e9b-a24229816b10", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import cv2\n", 19 | "import kachaka_api\n", 20 | "import numpy as np\n", 21 | "from IPython.display import Image, display" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "ad074c66-1ac6-4888-8536-31da6c52a647", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "client = kachaka_api.aio.KachakaApiClient()" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "id": "25850926-7e13-4b7f-81c2-ab982220da3b", 37 | "metadata": {}, 38 | "source": [ 39 | "# 圧縮画像の取得" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "id": "6180794e-d907-49f4-8906-650c10f32e15", 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "compressed_stream = client.front_camera_ros_compressed_image.stream()\n", 50 | "compressed_image = await compressed_stream.__anext__()\n", 51 | "display(Image(data=compressed_image.data, format=\"jpeg\"))" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "id": "60040386-520e-4bc2-82a4-d69e36b33c29", 57 | "metadata": {}, 58 | "source": [ 59 | "# 生画像の取得" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "id": "19779c34-80bc-4391-9ba3-cc7c4508eee2", 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "stream = client.front_camera_ros_image.stream()\n", 70 | "image = await stream.__anext__()\n", 71 | "np_image = np.ndarray(\n", 72 | " shape=(image.height, image.width, 3),\n", 73 | " dtype=np.uint8,\n", 74 | " buffer=image.data,\n", 75 | ")\n", 76 | "# 圧縮画像として表示\n", 77 | "_, ret = cv2.imencode(\".jpg\", np_image[..., ::-1])\n", 78 | "display(Image(data=ret, format=\"jpeg\"))" 79 | ] 80 | } 81 | ], 82 | "metadata": { 83 | "kernelspec": { 84 | "display_name": "Python 3 (ipykernel)", 85 | "language": "python", 86 | "name": "python3" 87 | }, 88 | "language_info": { 89 | "codemirror_mode": { 90 | "name": "ipython", 91 | "version": 3 92 | }, 93 | "file_extension": ".py", 94 | "mimetype": "text/x-python", 95 | "name": "python", 96 | "nbconvert_exporter": "python", 97 | "pygments_lexer": "ipython3", 98 | "version": "3.10.12" 99 | } 100 | }, 101 | "nbformat": 4, 102 | "nbformat_minor": 5 103 | } 104 | -------------------------------------------------------------------------------- /python/demos/get_front_camera_continuous.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "4bcba01b-7d02-4d53-84b1-e5d71ae5b8f5", 6 | "metadata": {}, 7 | "source": [ 8 | "# カメラ画像の連続取得" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "8d08024f-7938-4a12-8e9b-a24229816b10", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import kachaka_api\n", 19 | "from IPython.display import Image, clear_output, display" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "id": "ad074c66-1ac6-4888-8536-31da6c52a647", 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "client = kachaka_api.aio.KachakaApiClient()" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "b712840e", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "async for image in client.front_camera_ros_compressed_image.stream():\n", 40 | " clear_output(wait=True)\n", 41 | " display(Image(data=image.data, format=\"jpeg\"))" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3 (ipykernel)", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.10.12" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 5 66 | } 67 | -------------------------------------------------------------------------------- /python/demos/get_imu.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "d69298cb-ac99-40ef-8231-830d975ead4a", 6 | "metadata": {}, 7 | "source": [ 8 | "# IMUの取得" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 3, 14 | "id": "3414574d-061c-4f2f-aa0a-0f352a5dd623", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "application/vnd.jupyter.widget-view+json": { 20 | "model_id": "740a5232236348dcba1b670b444064c7", 21 | "version_major": 2, 22 | "version_minor": 0 23 | }, 24 | "text/plain": [ 25 | "FigureWidget({\n", 26 | " 'data': [{'type': 'scatter',\n", 27 | " 'uid': 'b141bafa-1ea7-4acb-9bc8-bf4c6ced492b',\n", 28 | " 'y': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 29 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 30 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 31 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 32 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}],\n", 33 | " 'layout': {'template': '...', 'title': {'text': 'IMU acceleration x'}}\n", 34 | "})" 35 | ] 36 | }, 37 | "execution_count": 3, 38 | "metadata": {}, 39 | "output_type": "execute_result" 40 | } 41 | ], 42 | "source": [ 43 | "import kachaka_api\n", 44 | "import plotly.graph_objects as go\n", 45 | "\n", 46 | "MAX_POINTS = 100\n", 47 | "\n", 48 | "f = go.FigureWidget()\n", 49 | "data = [0] * MAX_POINTS\n", 50 | "f.add_scatter(y=data)\n", 51 | "f.layout.title = \"IMU acceleration x\"\n", 52 | "f" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 4, 58 | "id": "7a026ab6-a718-48a2-a69a-e56df51ffb93", 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "client = kachaka_api.aio.KachakaApiClient()\n", 63 | "for i in range(100):\n", 64 | " imu = await client.get_ros_imu()\n", 65 | "\n", 66 | " data.append(imu.linear_acceleration.x)\n", 67 | " del data[0]\n", 68 | " f.data[0].y = data" 69 | ] 70 | } 71 | ], 72 | "metadata": { 73 | "kernelspec": { 74 | "display_name": "Python 3 (ipykernel)", 75 | "language": "python", 76 | "name": "python3" 77 | }, 78 | "language_info": { 79 | "codemirror_mode": { 80 | "name": "ipython", 81 | "version": 3 82 | }, 83 | "file_extension": ".py", 84 | "mimetype": "text/x-python", 85 | "name": "python", 86 | "nbconvert_exporter": "python", 87 | "pygments_lexer": "ipython3", 88 | "version": "3.10.12" 89 | } 90 | }, 91 | "nbformat": 4, 92 | "nbformat_minor": 5 93 | } 94 | -------------------------------------------------------------------------------- /python/demos/get_laser_scan.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "4bcba01b-7d02-4d53-84b1-e5d71ae5b8f5", 6 | "metadata": {}, 7 | "source": [ 8 | "# LiDAR の取得\n", 9 | "\n", 10 | "Matplotlib のインストールを行うコマンドが入っています。一度実行した後はこの処理はスキップ可能です。初回インストール後にkernelの再起動が必要です。" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "8d08024f-7938-4a12-8e9b-a24229816b10", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import math\n", 21 | "\n", 22 | "import kachaka_api\n", 23 | "import matplotlib.patches as patches\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "from IPython.display import Image, clear_output\n", 26 | "\n", 27 | "client = kachaka_api.aio.KachakaApiClient()\n", 28 | "await client.set_manual_control_enabled(True)\n", 29 | "\n", 30 | "\n", 31 | "async def get_and_show_laser_scan_loop():\n", 32 | " async for scan in client.ros_laser_scan.stream():\n", 33 | " clear_output(wait=True)\n", 34 | " fig = plt.figure(figsize=(5, 5))\n", 35 | "\n", 36 | " n = len(scan.ranges)\n", 37 | " x = list(range(n))\n", 38 | " y = list(range(n))\n", 39 | " for i in range(n):\n", 40 | " theta = scan.angle_min + scan.angle_increment * i\n", 41 | " x[i] = scan.ranges[i] * math.cos(theta)\n", 42 | " y[i] = scan.ranges[i] * math.sin(theta)\n", 43 | "\n", 44 | " plt.plot(0, 0, \"o\", color=\"black\")\n", 45 | " plt.plot(x, y, \".\")\n", 46 | " plt.xlim(-6.0, 6.0)\n", 47 | " plt.ylim(-6.0, 6.0)\n", 48 | " plt.grid(True)\n", 49 | " plt.gca().set_aspect(\"equal\", adjustable=\"box\")\n", 50 | " plt.show()\n", 51 | "\n", 52 | "\n", 53 | "await get_and_show_laser_scan_loop()" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "id": "d43ec70a-3f57-4b72-9ba8-097f9d87b361", 59 | "metadata": {}, 60 | "source": [ 61 | "上記の方法は、手動操縦モードをONにすることでLiDARを動かしており、カチャカが動かずしばらく経つとスキャンが止まります。\n", 62 | "以下のようにActivatorを使うことで、カチャカが静止している状態でもスキャンを継続することができます。\n", 63 | "\n", 64 | "ただし**必要以上にスキャンを動かし続けると、LiDARの製品寿命を大幅に縮めることになります**ので、ご注意下さい。" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "id": "4c65434b-bc3b-4d19-9c7f-247e7668dcf1", 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "from kachaka_api.util.vision import LaserScanActivator\n", 75 | "\n", 76 | "activator = LaserScanActivator()\n", 77 | "with activator.activate():\n", 78 | " await get_and_show_laser_scan_loop()" 79 | ] 80 | } 81 | ], 82 | "metadata": { 83 | "kernelspec": { 84 | "display_name": "Python 3 (ipykernel)", 85 | "language": "python", 86 | "name": "python3" 87 | }, 88 | "language_info": { 89 | "codemirror_mode": { 90 | "name": "ipython", 91 | "version": 3 92 | }, 93 | "file_extension": ".py", 94 | "mimetype": "text/x-python", 95 | "name": "python", 96 | "nbconvert_exporter": "python", 97 | "pygments_lexer": "ipython3", 98 | "version": "3.10.12" 99 | } 100 | }, 101 | "nbformat": 4, 102 | "nbformat_minor": 5 103 | } 104 | -------------------------------------------------------------------------------- /python/demos/get_object_detection.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "4bcba01b-7d02-4d53-84b1-e5d71ae5b8f5", 6 | "metadata": {}, 7 | "source": [ 8 | "# 画像による物体検出" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "8d08024f-7938-4a12-8e9b-a24229816b10", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import asyncio\n", 19 | "\n", 20 | "import kachaka_api\n", 21 | "from IPython.display import clear_output, display\n", 22 | "from kachaka_api.util.vision import OBJECT_LABEL, get_bbox_drawn_image\n", 23 | "\n", 24 | "client = kachaka_api.aio.KachakaApiClient()\n", 25 | "stream_i = client.front_camera_ros_compressed_image.stream()\n", 26 | "stream_d = client.object_detection.stream()\n", 27 | "while True:\n", 28 | " image, (header, objects) = await asyncio.gather(anext(stream_i), anext(stream_d))\n", 29 | " img = get_bbox_drawn_image(image, objects)\n", 30 | " clear_output(wait=True)\n", 31 | " display(img)\n", 32 | " for object in objects:\n", 33 | " display(f\"{OBJECT_LABEL[object.label]}, score={object.score:.2f}\")" 34 | ] 35 | } 36 | ], 37 | "metadata": { 38 | "kernelspec": { 39 | "display_name": "Python 3 (ipykernel)", 40 | "language": "python", 41 | "name": "python3" 42 | }, 43 | "language_info": { 44 | "codemirror_mode": { 45 | "name": "ipython", 46 | "version": 3 47 | }, 48 | "file_extension": ".py", 49 | "mimetype": "text/x-python", 50 | "name": "python", 51 | "nbconvert_exporter": "python", 52 | "pygments_lexer": "ipython3", 53 | "version": "3.10.13" 54 | } 55 | }, 56 | "nbformat": 4, 57 | "nbformat_minor": 5 58 | } 59 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/README.md: -------------------------------------------------------------------------------- 1 | # PythonでgRPC APIを実行するサンプル 2 | 3 | ## 準備 4 | ``` 5 | git clone https://github.com/pf-robotics/kachaka-api.git # 本リポジトリ 6 | python3 -m venv venv 7 | source venv/bin/activate 8 | cd kachaka-api/python/demos 9 | pip install -r requirements.txt 10 | cd grpc_samples 11 | python -m grpc_tools.protoc -I../../../protos --python_out=. --pyi_out=. --grpc_python_out=. ../../../protos/kachaka-api.proto 12 | ``` 13 | 14 | ## 実行 15 | ``` 16 | python <サンプルコード>.py <カチャカのIPアドレス>:26400 17 | ``` 18 | 19 | 20 | ### 地図のインポート・エクスポートの例 21 | 22 | マップの一覧を取得 23 | ``` 24 | % ./get_map_list.py XX.XX.XX.XX:26400 25 | metadata { 26 | cursor: 42676578825899 27 | } 28 | map_list_entries { 29 | id: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" 30 | name: "Map 1" 31 | } 32 | map_list_entries { 33 | id: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" 34 | name: "Map 2" 35 | } 36 | ``` 37 | 38 | エクスポート 39 | ``` 40 | ./export_map_api_client.py -s XX.XX.XX.XX:26400 -m XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -o my-map1.kmap 41 | ``` 42 | 43 | インポート 44 | ``` 45 | ./import_map_api_client.py -s XX.XX.XX.XX:26400 -i my-map1.kmap 46 | ``` 47 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/export_map_api_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Usage: 4 | # export_map_api_client.py -s XX.XX.XX.XX:26400 -m XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -o out.profile 5 | 6 | import argparse 7 | 8 | import grpc 9 | import kachaka_api_pb2 10 | import kachaka_api_pb2_grpc 11 | 12 | 13 | def parse_args() -> argparse.Namespace: 14 | parser = argparse.ArgumentParser( 15 | description="Export a map from the Baku server" 16 | ) 17 | parser.add_argument("--server_address", "-s", type=str, required=True) 18 | parser.add_argument("--map_id", "-m", type=str, required=True) 19 | parser.add_argument("--output_path", "-o", type=str, required=True) 20 | return parser.parse_args() 21 | 22 | 23 | def export_map( 24 | stub: kachaka_api_pb2_grpc.KachakaApiStub, 25 | map_id: str, 26 | export_path: str, 27 | ) -> bool: 28 | print("Exporting map: {}".format(map_id)) 29 | with open(export_path, "wb") as fout: 30 | stream = stub.ExportMap(kachaka_api_pb2.ExportMapRequest(map_id=map_id)) 31 | for response in stream: 32 | if response.HasField("end_of_stream"): 33 | print() 34 | if response.end_of_stream.result.success: 35 | print(f"Map exported successfully as {export_path}") 36 | return True 37 | else: 38 | print( 39 | f"Error exporting map (code={response.end_of_stream.result.error_code}): {response.end_of_stream.result.message}" 40 | ) 41 | return False 42 | elif response.HasField("middle_of_stream"): 43 | fout.write(response.middle_of_stream.data) 44 | print(".", end="") 45 | else: 46 | print("Received invalid response") 47 | return False 48 | print("Map export failed unexpectedly") 49 | return False 50 | 51 | 52 | def main() -> None: 53 | args = parse_args() 54 | 55 | channel = grpc.insecure_channel(args.server_address) 56 | stub = kachaka_api_pb2_grpc.KachakaApiStub(channel) 57 | export_map(stub, args.map_id, args.output_path) 58 | 59 | 60 | if __name__ == "__main__": 61 | main() 62 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/get_locations.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import grpc 4 | import kachaka_api_pb2 5 | from kachaka_api_pb2_grpc import KachakaApiStub 6 | 7 | # gRPCの初期化 8 | stub = KachakaApiStub(grpc.insecure_channel(sys.argv[1])) 9 | 10 | # GetLocationsを実行します 11 | response = stub.GetLocations(kachaka_api_pb2.GetRequest()) 12 | 13 | # 実行結果を表示 14 | print(response) 15 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/get_map_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Usage: 4 | # get_map_list.py XX.XX.XX.XX:26400 5 | 6 | import sys 7 | 8 | import grpc 9 | import kachaka_api_pb2 10 | from kachaka_api_pb2_grpc import KachakaApiStub 11 | 12 | stub = KachakaApiStub(grpc.insecure_channel(sys.argv[1])) 13 | response = stub.GetMapList(kachaka_api_pb2.GetRequest()) 14 | print(response) 15 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/import_map_api_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Usage: 4 | # import_map_api_client.py -s XX.XX.XX.XX:26400 -k XXXXXXXXXXXXXXXXXXXXXXXX -i out.profile 5 | 6 | import argparse 7 | from typing import Iterator 8 | 9 | import grpc 10 | import kachaka_api_pb2 11 | import kachaka_api_pb2_grpc 12 | 13 | 14 | def parse_args() -> argparse.Namespace: 15 | parser = argparse.ArgumentParser( 16 | description="Export a map from the Baku server" 17 | ) 18 | parser.add_argument("--server_address", "-s", type=str, required=True) 19 | parser.add_argument("--input_path", "-i", type=str, required=True) 20 | return parser.parse_args() 21 | 22 | 23 | def import_map( 24 | stub: kachaka_api_pb2_grpc.KachakaApiStub, input_path: str 25 | ) -> bool: 26 | print(f"Importing map: {input_path}") 27 | with open(input_path, "rb") as fin: 28 | 29 | def request_messages() -> Iterator[kachaka_api_pb2.ImportMapRequest]: 30 | while True: 31 | data = fin.read(64 * 1024) 32 | if not data: 33 | break 34 | yield kachaka_api_pb2.ImportMapRequest(data=data) 35 | 36 | response = stub.ImportMap(request_messages()) 37 | if response.result.success: 38 | print(f"Map imported: {response.map_id}") 39 | return True 40 | else: 41 | print( 42 | f"Map import failed (code={response.result.error_code}): {response.result.message}" 43 | ) 44 | return False 45 | 46 | 47 | def main() -> None: 48 | args = parse_args() 49 | 50 | channel = grpc.insecure_channel(args.server_address) 51 | stub = kachaka_api_pb2_grpc.KachakaApiStub(channel) 52 | import_map(stub, args.input_path) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/move_to_location.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import grpc 4 | import kachaka_api_pb2 5 | from kachaka_api_pb2_grpc import KachakaApiStub 6 | 7 | # gRPCの初期化 8 | stub = KachakaApiStub(grpc.insecure_channel(sys.argv[1])) 9 | 10 | # 現在のカーソルを取得します 11 | req = kachaka_api_pb2.GetRequest() 12 | resp = stub.GetLastCommandResult(req) 13 | last_cursor = resp.metadata.cursor 14 | 15 | # move_to_locationのコマンドを準備します 16 | req = kachaka_api_pb2.StartCommandRequest( 17 | command=kachaka_api_pb2.Command( 18 | move_to_location_command=kachaka_api_pb2.MoveToLocationCommand( 19 | target_location_id="L01" 20 | ) 21 | ) 22 | ) 23 | 24 | # コマンドを実行します 25 | resp = stub.StartCommand(req) 26 | if not resp.result.success: 27 | print("Sending MoveToLocation command failed: " + resp.result.error_code) 28 | sys.exit(1) 29 | 30 | print("MoveToLocation command sent") 31 | 32 | # コマンドの実行が完了するのを待ちます 33 | req = kachaka_api_pb2.GetRequest() 34 | while True: 35 | resp = stub.GetLastCommandResult(req) 36 | if last_cursor != resp.metadata.cursor: 37 | break 38 | req.metadata.cursor = resp.metadata.cursor 39 | if not resp.result.success: 40 | print("MoveToLocation command failed :", resp.result.error_code) 41 | sys.exit(1) 42 | 43 | print("MoveToLocation command completed") 44 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/sample_getter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import grpc 4 | import kachaka_api_pb2 5 | from kachaka_api_pb2_grpc import KachakaApiStub 6 | 7 | stub = KachakaApiStub(grpc.insecure_channel(sys.argv[1])) 8 | 9 | response = stub.GetRobotVersion(kachaka_api_pb2.GetRequest()) 10 | print("---------- robot version ----------") 11 | print(response) 12 | 13 | response = stub.GetRobotSerialNumber(kachaka_api_pb2.GetRequest()) 14 | print("---------- serial number ----------") 15 | print(response) 16 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/set_auto_homing_enabled.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import grpc 4 | import kachaka_api_pb2 5 | from kachaka_api_pb2_grpc import KachakaApiStub 6 | 7 | # gRPCの初期化 8 | stub = KachakaApiStub(grpc.insecure_channel(sys.argv[1])) 9 | 10 | # リクエストを準備します 11 | req = kachaka_api_pb2.SetAutoHomingEnabledRequest(enable=False) 12 | 13 | # SetAutoHomingEnabledを実行します 14 | response = stub.SetAutoHomingEnabled(req) 15 | if not response.result.success: 16 | print("Sending SetAutoHomingEnabled failed") 17 | 18 | # 実行結果を表示 19 | print(response) 20 | -------------------------------------------------------------------------------- /python/demos/grpc_samples/time_signal.py: -------------------------------------------------------------------------------- 1 | # 時報サンプル 2 | 3 | import datetime 4 | import sys 5 | import time 6 | 7 | import grpc 8 | import kachaka_api_pb2 9 | import schedule 10 | from kachaka_api_pb2_grpc import KachakaApiStub 11 | 12 | stub = KachakaApiStub(grpc.insecure_channel(sys.argv[1])) 13 | 14 | 15 | def speak(speak_text: str) -> None: 16 | print(f"speak: {speak_text}") 17 | req = kachaka_api_pb2.StartCommandRequest( 18 | command=kachaka_api_pb2.Command( 19 | speak_command=kachaka_api_pb2.SpeakCommand(text=speak_text) 20 | ) 21 | ) 22 | 23 | resp = stub.StartCommand(req) 24 | if not resp.result.success: 25 | print("Sending speak command failed: " + resp.result.error_code) 26 | return 27 | print("Speak command sent") 28 | 29 | 30 | def time_signal() -> None: 31 | now = datetime.datetime.now() 32 | if now.minute == 0: 33 | text = f"カチャカです。{now.hour}時をお知らせします。" 34 | else: 35 | text = f"カチャカです。{now.hour}時{now.minute}分をお知らせします。" 36 | speak(text) 37 | 38 | 39 | print("start time_signal") 40 | speak("時報を起動します") 41 | 42 | schedule.every().minute.at(":00").do(time_signal) 43 | 44 | while True: 45 | schedule.run_pending() 46 | time.sleep(1) 47 | -------------------------------------------------------------------------------- /python/demos/http_server.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "62432d31-6c66-49f7-b229-77693bb53a9c", 6 | "metadata": {}, 7 | "source": [ 8 | "# HTTPサーバー\n", 9 | "\n", 10 | "HTTPサーバーを実行し、GETリクエストを受信したときにカチャカに対して指令を送るサンプルです。\n", 11 | "以下のアドレスにカチャカボタンHubなどからGETを送信することで、希望の指令を送ることができます。\n", 12 | "```\n", 13 | "http://<カチャカのIPアドレス>:26502/run\n", 14 | "```" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "278e20f6-a311-41cd-9fa0-3532dae6947c", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import asyncio\n", 25 | "\n", 26 | "import kachaka_api\n", 27 | "import nest_asyncio\n", 28 | "import uvicorn\n", 29 | "from fastapi import BackgroundTasks, FastAPI\n", 30 | "\n", 31 | "app = FastAPI()\n", 32 | "client = kachaka_api.aio.KachakaApiClient()\n", 33 | "lock = asyncio.Lock()\n", 34 | "\n", 35 | "\n", 36 | "async def send_command():\n", 37 | " await client.speak(\"カチャカです、よろしくね!\")\n", 38 | " print(\"send_command finished.\")\n", 39 | "\n", 40 | "\n", 41 | "async def run_task():\n", 42 | " if lock.locked():\n", 43 | " print(\"Another task is already running. Skip.\")\n", 44 | " return\n", 45 | " async with lock:\n", 46 | " await send_command()\n", 47 | "\n", 48 | "\n", 49 | "@app.get(\"/run\")\n", 50 | "async def handle_get_run(background_tasks: BackgroundTasks):\n", 51 | " background_tasks.add_task(run_task)\n", 52 | " return {\"success\": True}\n", 53 | "\n", 54 | "\n", 55 | "nest_asyncio.apply() # JupyterLab内でuvicornを走らせるために必要\n", 56 | "uvicorn.run(app, host=\"0.0.0.0\", port=26502)" 57 | ] 58 | } 59 | ], 60 | "metadata": { 61 | "kernelspec": { 62 | "display_name": "Python 3 (ipykernel)", 63 | "language": "python", 64 | "name": "python3" 65 | }, 66 | "language_info": { 67 | "codemirror_mode": { 68 | "name": "ipython", 69 | "version": 3 70 | }, 71 | "file_extension": ".py", 72 | "mimetype": "text/x-python", 73 | "name": "python", 74 | "nbconvert_exporter": "python", 75 | "pygments_lexer": "ipython3", 76 | "version": "3.10.12" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 5 81 | } 82 | -------------------------------------------------------------------------------- /python/demos/install_libraries.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "da6044b1-aa88-485d-8208-69a2dbbcbaf9", 6 | "metadata": {}, 7 | "source": [ 8 | "# 依存ライブラリのインストール\n", 9 | "\n", 10 | "demoのプログラムが依存しているライブラリを一括でインストールします。 \n", 11 | "すでに実行中のnotebookがある場合は実行後にkernelを再起動してください。" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "9da010bc-3388-4621-b45e-5f52e50f4c4d", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "!pip install -U pip\n", 22 | "!pip install -r requirements.txt" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "kernelspec": { 28 | "display_name": "Python 3 (ipykernel)", 29 | "language": "python", 30 | "name": "python3" 31 | }, 32 | "language_info": { 33 | "codemirror_mode": { 34 | "name": "ipython", 35 | "version": 3 36 | }, 37 | "file_extension": ".py", 38 | "mimetype": "text/x-python", 39 | "name": "python", 40 | "nbconvert_exporter": "python", 41 | "pygments_lexer": "ipython3", 42 | "version": "3.10.13" 43 | } 44 | }, 45 | "nbformat": 4, 46 | "nbformat_minor": 5 47 | } 48 | -------------------------------------------------------------------------------- /python/demos/qrcode.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "316a3adc-c895-4b07-8bab-f5585005d2d3", 6 | "metadata": {}, 7 | "source": [ 8 | "# QRコード検出\n", 9 | "\n", 10 | "Opencvのインストールを行うコマンドが入っています。一度実行した後はこの処理はスキップ可能です。初回インストール後にkernelの再起動が必要です。" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "fb7cf7ce-1fe1-46e8-b849-38da9800a6b9", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import cv2\n", 21 | "import kachaka_api\n", 22 | "import numpy as np\n", 23 | "from IPython.display import Image, clear_output, display" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "11501be0-bf41-43a2-98aa-bd0e0e93d913", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "client = kachaka_api.aio.KachakaApiClient()" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "fb403f0a-34c4-4d69-9aac-44646b419d24", 39 | "metadata": {}, 40 | "source": [ 41 | "以下実行後、スマートフォンなどで https://pf-robotics.github.io/textcode/ にアクセスし、好きな英数字を入力して表示されたQRコードをカチャカの前カメラの前に近付けて下さい。" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "id": "32e4e1a0-f7d9-431f-bcf4-59cb3c604860", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "qcd = cv2.QRCodeDetector()\n", 52 | "\n", 53 | "async for image in client.front_camera_ros_compressed_image.stream():\n", 54 | " cv_image = cv2.imdecode(np.frombuffer(image.data, dtype=np.uint8), flags=1)\n", 55 | " decoded_info, corners, _ = qcd.detectAndDecode(cv_image)\n", 56 | " if corners is not None:\n", 57 | " cv_image = cv2.polylines(cv_image, corners.astype(int), True, (0, 0, 255), 2)\n", 58 | " if decoded_info != \"\":\n", 59 | " cv_image = cv2.putText(\n", 60 | " cv_image,\n", 61 | " decoded_info,\n", 62 | " corners[0][0].astype(int),\n", 63 | " cv2.FONT_HERSHEY_SIMPLEX,\n", 64 | " 1,\n", 65 | " (0, 0, 255),\n", 66 | " 2,\n", 67 | " cv2.LINE_AA,\n", 68 | " )\n", 69 | " _, ret = cv2.imencode(\n", 70 | " \".jpg\",\n", 71 | " cv2.resize(cv_image, (int(cv_image.shape[1] / 2), int(cv_image.shape[0] / 2))),\n", 72 | " )\n", 73 | " clear_output(wait=True)\n", 74 | " display(Image(data=ret, format=\"jpeg\"))" 75 | ] 76 | } 77 | ], 78 | "metadata": { 79 | "kernelspec": { 80 | "display_name": "Python 3 (ipykernel)", 81 | "language": "python", 82 | "name": "python3" 83 | }, 84 | "language_info": { 85 | "codemirror_mode": { 86 | "name": "ipython", 87 | "version": 3 88 | }, 89 | "file_extension": ".py", 90 | "mimetype": "text/x-python", 91 | "name": "python", 92 | "nbconvert_exporter": "python", 93 | "pygments_lexer": "ipython3", 94 | "version": "3.10.12" 95 | } 96 | }, 97 | "nbformat": 4, 98 | "nbformat_minor": 5 99 | } 100 | -------------------------------------------------------------------------------- /python/demos/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.11.11 2 | aiosignal==1.3.2 3 | async-timeout==5.0.1 4 | attrs==23.2.0 5 | certifi==2024.7.4 6 | charset-normalizer==3.3.2 7 | fastapi==0.115.6 8 | frozenlist==1.5.0 9 | grpcio-tools==1.65.2 10 | grpcio==1.66.1 11 | idna==3.7 12 | ipympl==0.9.5 13 | ipywidgets==8.1.3 14 | matplotlib==3.10.0 15 | multidict==6.1.0 16 | numpy==2.2.1 17 | onnxruntime==1.20.1 18 | openai==1.58.1 19 | opencv-python-headless==4.10.0.84 20 | paho-mqtt==2.1.0 21 | plotly==5.24.1 22 | protobuf==5.27.2 23 | requests==2.32.3 24 | schedule==1.2.2 25 | tqdm==4.67.1 26 | urllib3==2.2.2 27 | uvicorn==0.34.0 28 | yarl==1.18.3 29 | -e ../../ 30 | -------------------------------------------------------------------------------- /python/demos/robot_pose.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "bff64587-9e3b-497d-b84a-efe7e7f44542", 6 | "metadata": {}, 7 | "source": [ 8 | "# ロボットの姿勢の取得と設定" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 14, 14 | "id": "4285980c-e213-41e8-9c49-d8e6b464551c", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "# カチャカAPI初期設定\n", 19 | "import asyncio\n", 20 | "import time\n", 21 | "\n", 22 | "import kachaka_api\n", 23 | "\n", 24 | "client = kachaka_api.aio.KachakaApiClient()" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "cca755ac-89ab-41f9-a45b-003716eb673a", 30 | "metadata": {}, 31 | "source": [ 32 | "## ロボットの現在姿勢の取得" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 12, 38 | "id": "d091f8bf-0dbd-4823-a27e-84030942c165", 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "pose.x=2.00, pose.y=1.00, pose.theta=-0.00\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "pose = await client.get_robot_pose()\n", 51 | "print(f\"{pose.x=:.2f}, {pose.y=:.2f}, {pose.theta=:.2f}\")" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "id": "02f5c354-4e20-464c-969f-e220f8ff2e8e", 57 | "metadata": {}, 58 | "source": [ 59 | "## ロボットの現在姿勢の設定\n", 60 | "反映までに少し時間がかかります。" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 18, 66 | "id": "f77cb3c0-4b53-41d4-b480-bdec47dcd5e6", 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "name": "stdout", 71 | "output_type": "stream", 72 | "text": [ 73 | "result.success=True\n" 74 | ] 75 | } 76 | ], 77 | "source": [ 78 | "result = await client.set_robot_pose({\"x\": 0.0, \"y\": 1.0, \"theta\": 0.0})\n", 79 | "print(f\"{result.success=}\")" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 16, 85 | "id": "023b0afb-9f77-47b5-a320-252618e8739f", 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "result.success=True\n", 93 | "pose.x=0.00, pose.y=-0.00, pose.theta=-0.00\n" 94 | ] 95 | } 96 | ], 97 | "source": [ 98 | "result = await client.set_robot_pose({\"x\": 2.0, \"y\": 1.0, \"theta\": 0.0})\n", 99 | "print(f\"{result.success=}\")\n", 100 | "\n", 101 | "# wait for requested pose to be reflected\n", 102 | "time.sleep(5)\n", 103 | "\n", 104 | "pose = await client.get_robot_pose()\n", 105 | "print(f\"{pose.x=:.2f}, {pose.y=:.2f}, {pose.theta=:.2f}\")" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "id": "155af42d-a70a-49a7-9703-21ace7bb8c3e", 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "codemirror_mode": { 125 | "name": "ipython", 126 | "version": 3 127 | }, 128 | "file_extension": ".py", 129 | "mimetype": "text/x-python", 130 | "name": "python", 131 | "nbconvert_exporter": "python", 132 | "pygments_lexer": "ipython3", 133 | "version": "3.10.12" 134 | } 135 | }, 136 | "nbformat": 4, 137 | "nbformat_minor": 5 138 | } 139 | -------------------------------------------------------------------------------- /python/demos/save_object_detection_features.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "a2e66355-2c68-41ec-97d7-dd788eec9c47", 6 | "metadata": {}, 7 | "source": [ 8 | "# 転移学習用のデータ保存" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "bfd3e2a3-3dd5-429e-bfca-dc9da018e9b3", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import io\n", 19 | "import os\n", 20 | "import pickle\n", 21 | "import time\n", 22 | "\n", 23 | "import kachaka_api\n", 24 | "import numpy as np\n", 25 | "from IPython.display import Image, display\n", 26 | "from PIL import Image as PILImage" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "1a608464-ed30-4a8a-9bda-b7f2f1501133", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "client = kachaka_api.KachakaApiClient()\n", 37 | "\n", 38 | "os.makedirs(\"data\", exist_ok=True)\n", 39 | "data_num = 0" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "id": "d2f894dc-5e61-4977-983a-ff06c1bb29b8", 45 | "metadata": {}, 46 | "source": [ 47 | "撮影と保存 (繰り返し実行して下さい)" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "id": "3824c19c-f960-4bdb-936d-fdfe82a14d87", 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "now = time.time()\n", 58 | "\n", 59 | "while True:\n", 60 | " image = client.get_front_camera_ros_compressed_image()\n", 61 | " features_header, features = client.get_object_detection_features()\n", 62 | " if now < image.header.stamp_nsec / 10e8 and now < features_header.stamp_nsec / 10e8:\n", 63 | " break\n", 64 | "\n", 65 | "inputs = {\n", 66 | " feature.name.replace(\"_out\", \"\"): np.array(feature.data, dtype=np.float32).reshape(\n", 67 | " feature.shape\n", 68 | " )\n", 69 | " for feature in features\n", 70 | "}\n", 71 | "\n", 72 | "with open(f\"data/{data_num}.pkl\", \"wb\") as f:\n", 73 | " pickle.dump(inputs, f)\n", 74 | "\n", 75 | "pil_image = PILImage.open(io.BytesIO(image.data))\n", 76 | "pil_image.save(f\"data/{data_num}.png\")\n", 77 | "\n", 78 | "Image(data=image.data, format=\"jpeg\")\n", 79 | "display(Image(data=image.data, format=\"jpeg\"))\n", 80 | "\n", 81 | "data_num += 1" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "id": "bf1e7345-428f-427a-a0c6-49d5109b184d", 87 | "metadata": {}, 88 | "source": [ 89 | "データ保存後、転移学習を行う方法については以下READMEをご覧下さい https://github.com/pf-robotics/kachaka-transfer-learning" 90 | ] 91 | } 92 | ], 93 | "metadata": { 94 | "kernelspec": { 95 | "display_name": "Python 3 (ipykernel)", 96 | "language": "python", 97 | "name": "python3" 98 | }, 99 | "language_info": { 100 | "codemirror_mode": { 101 | "name": "ipython", 102 | "version": 3 103 | }, 104 | "file_extension": ".py", 105 | "mimetype": "text/x-python", 106 | "name": "python", 107 | "nbconvert_exporter": "python", 108 | "pygments_lexer": "ipython3", 109 | "version": "3.10.12" 110 | } 111 | }, 112 | "nbformat": 4, 113 | "nbformat_minor": 5 114 | } 115 | -------------------------------------------------------------------------------- /python/demos/smart_speaker.py: -------------------------------------------------------------------------------- 1 | # スマートスピーカー連携サンプル 2 | # README_SMART_SPEAKER.mdを参考にセットアップしてください。 3 | 4 | import os 5 | import sys 6 | 7 | import grpc 8 | import kachaka_api_pb2 9 | import paho.mqtt.client as mqtt 10 | from kachaka_api_pb2_grpc import KachakaApiStub 11 | 12 | CHANNEL = "test" # Beebotteのチャンネル名 13 | RESOURCE = "sample" # Beebotteのリソース名 14 | PEM = "./mqtt.beebotte.com.pem" # Beebotteの証明書ファイル 15 | 16 | # APIの初期化 17 | stub = KachakaApiStub(grpc.insecure_channel(sys.argv[1])) 18 | 19 | # 環境変数TOKENからトークンを取得します 20 | try: 21 | token = os.environ["TOKEN"] 22 | except KeyError: 23 | print("TOKEN is not set.") 24 | sys.exit(1) 25 | 26 | if not os.path.exists(PEM): 27 | print(f"{PEM} file not found.") 28 | sys.exit(1) 29 | 30 | 31 | def on_connect(client, userdata, flag, rc): 32 | if rc == 0: 33 | print("Connection succeeded.") 34 | client.subscribe(f"{CHANNEL}/{RESOURCE}", 1) 35 | else: 36 | print("Connection failed. code=" + str(rc)) 37 | 38 | 39 | def on_message(client, data, msg): 40 | print("on_message: " + str(msg.payload)) 41 | 42 | # move_shelfコマンドを実行 43 | req = kachaka_api_pb2.StartCommandRequest( 44 | command=kachaka_api_pb2.Command( 45 | move_shelf_command=kachaka_api_pb2.MoveShelfCommand( 46 | target_shelf_id="S01", # shelf ID S01を持って 47 | destination_location_id="L01", # location ID L01に移動 48 | ) 49 | ) 50 | ) 51 | response = stub.StartCommand(req) 52 | if not response.result.success: 53 | print("Sending move shelf command failed") 54 | sys.exit(1) 55 | 56 | 57 | # mqtt clientの初期化 58 | mqtt_client = mqtt.Client() 59 | mqtt_client.on_connect = on_connect 60 | mqtt_client.on_message = on_message 61 | 62 | mqtt_client.username_pw_set(f"token:{token}") 63 | mqtt_client.tls_set(PEM) 64 | mqtt_client.connect("mqtt.beebotte.com", 8883, 60) 65 | mqtt_client.loop_forever() 66 | -------------------------------------------------------------------------------- /python/demos/speak.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6ded05f1-a5f2-49d3-9db3-0a42d250a315", 6 | "metadata": {}, 7 | "source": [ 8 | "# カチャカによる発話\n", 9 | "\n", 10 | "カチャカが指定した言葉を喋るサンプルプログラムです。" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "b66b9497-256c-462d-8e81-55e099d890f9", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import kachaka_api\n", 21 | "\n", 22 | "client = kachaka_api.KachakaApiClient()\n", 23 | "client.speak(\"カチャカです、よろしくね!\")" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3 (ipykernel)", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.10.12" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 5 48 | } 49 | -------------------------------------------------------------------------------- /python/demos/teleop.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "fd77c3fb-92e8-44ce-9b76-b17750f8c8dc", 6 | "metadata": {}, 7 | "source": [ 8 | "# 十字キーボタンによる操縦\n", 9 | "\n", 10 | "JupyterLab上に表示されるボタンを使って、カチャカを任意の方向に操縦できます。" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "31ed16f3-8330-4618-b9f5-561783ab93f7", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import ipywidgets as widgets\n", 21 | "import kachaka_api\n", 22 | "from IPython.display import display\n", 23 | "\n", 24 | "client = kachaka_api.KachakaApiClient()\n", 25 | "\n", 26 | "\n", 27 | "def move(linear, angular):\n", 28 | " client.set_robot_velocity(linear=linear, angular=angular)\n", 29 | "\n", 30 | "\n", 31 | "def add_button(label: str, linear: float, angular: float):\n", 32 | " button = widgets.Button(description=label)\n", 33 | " button.on_click(lambda _: move(linear, angular))\n", 34 | " return button\n", 35 | "\n", 36 | "\n", 37 | "items = [\n", 38 | " add_button(\"\", 0.5, 0.5),\n", 39 | " add_button(\"Forward\", 0.5, 0),\n", 40 | " add_button(\"\", 0.5, -0.5),\n", 41 | " add_button(\"Turn left\", 0, 0.5),\n", 42 | " add_button(\"Stop\", 0, 0),\n", 43 | " add_button(\"Turn right\", 0, -0.5),\n", 44 | " add_button(\"\", -0.5, 0.5),\n", 45 | " add_button(\"Backward\", -0.5, 0),\n", 46 | " add_button(\"\", -0.5, -0.5),\n", 47 | "]\n", 48 | "widgets.GridBox(items, layout=widgets.Layout(grid_template_columns=\"repeat(3, 150px)\"))" 49 | ] 50 | } 51 | ], 52 | "metadata": { 53 | "kernelspec": { 54 | "display_name": "Python 3 (ipykernel)", 55 | "language": "python", 56 | "name": "python3" 57 | }, 58 | "language_info": { 59 | "codemirror_mode": { 60 | "name": "ipython", 61 | "version": 3 62 | }, 63 | "file_extension": ".py", 64 | "mimetype": "text/x-python", 65 | "name": "python", 66 | "nbconvert_exporter": "python", 67 | "pygments_lexer": "ipython3", 68 | "version": "3.10.12" 69 | } 70 | }, 71 | "nbformat": 4, 72 | "nbformat_minor": 5 73 | } 74 | -------------------------------------------------------------------------------- /python/demos/template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "a5a4f017-d544-4ed3-98c6-7f319dbb4e9f", 6 | "metadata": {}, 7 | "source": [ 8 | "# カチャカAPI用テンプレート\n", 9 | "プログラム作成用のテンプレートです。コピーしてご利用下さい。" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "id": "b4cb3148-60d1-4cde-9d4e-b5afff245374", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "# カチャカAPI初期設定\n", 20 | "import asyncio\n", 21 | "\n", 22 | "import kachaka_api\n", 23 | "\n", 24 | "client = kachaka_api.aio.KachakaApiClient()\n", 25 | "await client.update_resolver()\n", 26 | "await client.set_auto_homing_enabled(False)" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "0c4b8d62-4115-4071-8ff4-39f0610ae432", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "# ここにユーザプログラムを記述します\n", 37 | "await client.speak(\"こんにちは\")" 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "kernelspec": { 43 | "display_name": "Python 3 (ipykernel)", 44 | "language": "python", 45 | "name": "python3" 46 | }, 47 | "language_info": { 48 | "codemirror_mode": { 49 | "name": "ipython", 50 | "version": 3 51 | }, 52 | "file_extension": ".py", 53 | "mimetype": "text/x-python", 54 | "name": "python", 55 | "nbconvert_exporter": "python", 56 | "pygments_lexer": "ipython3", 57 | "version": "3.10.12" 58 | } 59 | }, 60 | "nbformat": 4, 61 | "nbformat_minor": 5 62 | } 63 | -------------------------------------------------------------------------------- /python/demos/undistort.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "cfc9dc37-01ca-420e-9a90-f42210787fd0", 6 | "metadata": {}, 7 | "source": [ 8 | "# 画像の歪み補正\n", 9 | "\n", 10 | "opencvのインストールを行うコマンドが入っています。一度実行した後はこの処理はスキップ可能です。初回インストール後にkernelの再起動が必要です。" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "0d32c6e2-7a7b-465e-9936-fe59b1082d47", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import cv2\n", 21 | "import kachaka_api\n", 22 | "import numpy as np\n", 23 | "from IPython.display import Image, display" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "f19a6d81-71ae-4da1-9f1f-3ee5bae1b576", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "client = kachaka_api.KachakaApiClient()" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "id": "39ba023d-a371-45a0-8766-7c0a61d57f48", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "image = client.get_front_camera_ros_compressed_image()\n", 44 | "camera_info = client.get_front_camera_ros_camera_info()" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "id": "a576f88f-b9b9-47e3-a6bc-ae341019c77b", 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "cv_image = cv2.imdecode(np.frombuffer(image.data, dtype=np.uint8), flags=1)\n", 55 | "\n", 56 | "mtx = np.array(camera_info.K, dtype=float).reshape(3, 3)\n", 57 | "dist = np.array(camera_info.D)\n", 58 | "height = camera_info.height\n", 59 | "width = camera_info.width\n", 60 | "new_camera_mtx, roi = cv2.getOptimalNewCameraMatrix(\n", 61 | " mtx, dist, (width, height), 0, (width, height)\n", 62 | ")\n", 63 | "map_x, map_y = cv2.initUndistortRectifyMap(\n", 64 | " mtx, dist, None, new_camera_mtx, (width, height), 5\n", 65 | ")\n", 66 | "\n", 67 | "undistorted_image = cv2.remap(cv_image, map_x, map_y, cv2.INTER_LINEAR)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "id": "c3904fc2-847c-4d51-a950-fa8f7d29c39f", 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "_, ret = cv2.imencode(\".png\", cv_image)\n", 78 | "display(Image(data=ret, format=\"png\"))" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "id": "ee3ae6fe-8293-4990-9f22-982355b4ce6f", 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "_, ret = cv2.imencode(\".png\", undistorted_image)\n", 89 | "display(Image(data=ret, format=\"png\"))" 90 | ] 91 | } 92 | ], 93 | "metadata": { 94 | "kernelspec": { 95 | "display_name": "Python 3 (ipykernel)", 96 | "language": "python", 97 | "name": "python3" 98 | }, 99 | "language_info": { 100 | "codemirror_mode": { 101 | "name": "ipython", 102 | "version": 3 103 | }, 104 | "file_extension": ".py", 105 | "mimetype": "text/x-python", 106 | "name": "python", 107 | "nbconvert_exporter": "python", 108 | "pygments_lexer": "ipython3", 109 | "version": "3.10.12" 110 | } 111 | }, 112 | "nbformat": 4, 113 | "nbformat_minor": 5 114 | } 115 | -------------------------------------------------------------------------------- /python/kachaka_api/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Preferred Robotics, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | from . import aio # noqa: F401 14 | from .base import KachakaApiClientBase 15 | from .generated import kachaka_api_pb2 as pb2 # noqa: F401 16 | from .util import command as command_util # noqa: F401 17 | from .util import geometry as geometry_util # noqa: F401 18 | from .util import layout as layout_util # noqa: F401 19 | 20 | 21 | class KachakaApiClient(KachakaApiClientBase): 22 | def __init__(self, target="100.94.1.1:26400") -> None: 23 | super().__init__(target) 24 | -------------------------------------------------------------------------------- /python/kachaka_api/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/python/kachaka_api/py.typed -------------------------------------------------------------------------------- /python/kachaka_api/util/command.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Preferred Robotics, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | from ..generated import kachaka_api_pb2 14 | from .layout import ShelfLocationResolver 15 | 16 | 17 | class CommandTextFormatter: 18 | def __init__( 19 | self, 20 | resolver: ShelfLocationResolver, 21 | ) -> None: 22 | self._resolver = resolver 23 | 24 | def gen_command_text(self, command: kachaka_api_pb2.Command) -> str: 25 | if command.HasField("move_shelf_command"): 26 | shelf_name = self._resolver.get_shelf_name_by_id( 27 | command.move_shelf_command.target_shelf_id 28 | ) 29 | location_name = self._resolver.get_location_name_by_id( 30 | command.move_shelf_command.destination_location_id 31 | ) 32 | return f"{shelf_name}を{location_name}に移動" 33 | elif command.HasField("return_shelf_command"): 34 | if command.return_shelf_command.target_shelf_id != "": 35 | shelf_name = self._resolver.get_shelf_name_by_id( 36 | command.return_shelf_command.target_shelf_id 37 | ) 38 | return f"{shelf_name}を片付ける" 39 | return "家具を片付ける" 40 | elif command.HasField("move_to_location_command"): 41 | location_name = self._resolver.get_location_name_by_id( 42 | command.move_to_location_command.target_location_id 43 | ) 44 | return f"{location_name}に移動" 45 | elif command.HasField("return_home_command"): 46 | return "充電ドックに戻る" 47 | return "" 48 | -------------------------------------------------------------------------------- /python/kachaka_api/util/geometry.py: -------------------------------------------------------------------------------- 1 | from math import atan2, cos, pi, sin 2 | from typing import Tuple 3 | 4 | import numpy as np 5 | 6 | from ..generated.kachaka_api_pb2 import Map, Pose, Quaternion 7 | 8 | 9 | def calculate_yaw_from_quaternion(q: Quaternion) -> float: 10 | # カチャカの姿勢はz軸周りの回転のみ含むため、他の軸は無視して算出する 11 | yaw = atan2(q.z, q.w) * 2 12 | if abs(yaw) > pi: 13 | yaw -= np.sign(yaw) * 2 * pi 14 | return yaw 15 | 16 | 17 | def calculate_2d_transform_matrix( 18 | tx: float, ty: float, theta: float = 0 19 | ) -> np.ndarray: 20 | c, s = cos(theta), sin(theta) 21 | return np.array(((c, -s, tx), (s, c, ty), (0, 0, 1))) 22 | 23 | 24 | def calculate_2d_scale_matrix(sx: float, sy: float) -> np.ndarray: 25 | return np.array(((sx, 0, 0), (0, sy, 0), (0, 0, 1))) 26 | 27 | 28 | class MapImage2DGeometry: 29 | def __init__(self, png_map: Map) -> None: 30 | resolution = png_map.resolution 31 | origin = png_map.origin 32 | self._map_to_image_origin = ( 33 | calculate_2d_transform_matrix(origin.x, origin.y, origin.theta) 34 | @ calculate_2d_scale_matrix(resolution, -resolution) 35 | @ calculate_2d_transform_matrix(0.5, -png_map.height + 0.5) 36 | ) 37 | self._image_origin_to_map = np.linalg.inv(self._map_to_image_origin) 38 | 39 | def calculate_robot_pose_matrix_in_pixel( 40 | self, robot_pose: Pose 41 | ) -> np.ndarray: 42 | """ロボットの姿勢から画像上の姿勢を表す行列に変換する 43 | 44 | :param robot_pose: ロボット姿勢 [m, m, rad] 45 | :returns 3x3 行列 46 | """ 47 | return self._image_origin_to_map @ calculate_2d_transform_matrix( 48 | robot_pose.x, robot_pose.y, robot_pose.theta 49 | ) 50 | 51 | def calculate_robot_pose_matrix_from_pixel( 52 | self, pixel_xy: Tuple[float, float], angle: float = 0 53 | ) -> np.ndarray: 54 | """画像上の姿勢からロボットの姿勢を表す行列に変換する 55 | 56 | :param pixel_xy: 画像原点からの位置 [px, px] 57 | :param angle: 半時計周りを正とする角度 [radian] 58 | :returns 3x3 行列 59 | """ 60 | return self._map_to_image_origin @ calculate_2d_transform_matrix( 61 | *pixel_xy, angle 62 | ) 63 | -------------------------------------------------------------------------------- /python/kachaka_api/util/layout.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Preferred Robotics, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | from typing import Tuple 14 | 15 | from ..generated import kachaka_api_pb2 as pb2 16 | 17 | 18 | class ShelfLocationResolver: 19 | def __init__( 20 | self, shelves: list[pb2.Shelf] = [], locations: list[pb2.Location] = [] 21 | ) -> None: 22 | self.shelves = shelves 23 | self.locations = locations 24 | 25 | def set_shelves(self, shelves: list[pb2.Shelf]) -> None: 26 | self.shelves = shelves 27 | 28 | def set_locations(self, locations: list[pb2.Location]) -> None: 29 | self.locations = locations 30 | 31 | def get_shelf_list(self) -> list[Tuple[str, str]]: 32 | return [(shelf.id, shelf.name) for shelf in self.shelves] 33 | 34 | def get_location_list(self) -> list[Tuple[str, str]]: 35 | return [(location.id, location.name) for location in self.locations] 36 | 37 | def get_shelf_name_by_id(self, shelf_id: str) -> str: 38 | for shelf in self.shelves: 39 | if shelf.id == shelf_id: 40 | return shelf.name 41 | print(f"Failed to get shelf name of {shelf_id}") 42 | return shelf_id 43 | 44 | def get_location_name_by_id(self, location_id: str) -> str: 45 | for location in self.locations: 46 | if location.id == location_id: 47 | return location.name 48 | print(f"Failed to get location name of {location_id}") 49 | return location_id 50 | 51 | def get_shelf_id_by_name(self, shelf_name: str) -> str: 52 | for shelf in self.shelves: 53 | if shelf.name == shelf_name: 54 | return shelf.id 55 | print(f"Failed to get shelf id of {shelf_name}") 56 | return shelf_name 57 | 58 | def get_location_id_by_name(self, location_name: str) -> str: 59 | for location in self.locations: 60 | if location.name == location_name: 61 | return location.id 62 | print(f"Failed to get location id of {location_name}") 63 | return location_name 64 | 65 | def resolve_location_id_or_name(self, location_id_or_name: str) -> str: 66 | location_id_with_name = None 67 | for location in self.locations: 68 | if location.id == location_id_or_name: 69 | return location.id 70 | 71 | if location.name == location_id_or_name: 72 | location_id_with_name = location.id 73 | 74 | return location_id_with_name or location_id_or_name 75 | 76 | def resolve_shelf_id_or_name(self, shelf_id_or_name: str) -> str: 77 | shelf_id_with_name = None 78 | for shelf in self.shelves: 79 | if shelf.id == shelf_id_or_name: 80 | return shelf.id 81 | 82 | if shelf.name == shelf_id_or_name: 83 | shelf_id_with_name = shelf.id 84 | 85 | return shelf_id_with_name or shelf_id_or_name 86 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_follow/README.md: -------------------------------------------------------------------------------- 1 | # Follow Example 2 | 3 | # Overview 4 | 5 | * This is an example that uses LIDAR to move towards the nearest object. It includes instructions on how to use `cmd_vel` and `LaserScan`. 6 | 7 | # Building 8 | 9 | *Copy the necessary folders to workspace and build the project. 10 | 11 | ``` 12 | mkidr -p ~/ros2_ws/src 13 | cd ~/ros2_ws/src 14 | cp -r ~/kachaka-api/ros2/kachaka_interfaces . 15 | cp -r ~/kachaka-api/ros2/demos/kachaka_follow . 16 | 17 | cd ~/ros2_ws 18 | colcon build 19 | ``` 20 | 21 | # Execution 22 | 23 | * Open another terminal and start the ROS2 bridge. 24 | 25 | ``` 26 | cd ~/kachaka-api/ros2_bridge 27 | ./start_bridge.sh 28 | 29 | ``` 30 | 31 | * Next, run the following command. 32 | 33 | ``` 34 | cd ~/ros2_ws 35 | source install/setup.bash 36 | ros2 run kachaka_follow follow 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_follow/kachaka_follow/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/demos/kachaka_follow/kachaka_follow/__init__.py -------------------------------------------------------------------------------- /ros2/demos/kachaka_follow/kachaka_follow/follow.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import angles 4 | import rclpy 5 | from geometry_msgs.msg import Twist 6 | from kachaka_interfaces.msg import ObjectDetection, ObjectDetectionListStamped 7 | from rclpy.node import Node 8 | from sensor_msgs.msg import LaserScan 9 | 10 | MAX_RANGE_FOR_FOLLOW = 1.0 11 | ANGULAR_TOLERANCE = 0.8 12 | 13 | 14 | class Follower(Node): 15 | def __init__(self) -> None: 16 | super().__init__("follow") 17 | self._publisher = self.create_publisher( 18 | Twist, "/kachaka/manual_control/cmd_vel", 10 19 | ) 20 | self._lidar_subscriber = self.create_subscription( 21 | LaserScan, "/kachaka/lidar/scan", self._laser_scan_callback, 10 22 | ) 23 | self._object_detection_subscriber = self.create_subscription( 24 | ObjectDetectionListStamped, 25 | "/kachaka/object_detection/result", 26 | self._object_detection_callback, 27 | 10, 28 | ) 29 | self._timer = self.create_timer(0.1, self._publish_cmd_vel) 30 | self._cmd_vel = Twist() 31 | self._closest_distance = float("inf") 32 | self._closest_angle = 0.0 33 | self._person_in_detection = False 34 | 35 | def _publish_cmd_vel(self) -> None: 36 | if not self._person_in_detection: 37 | self.get_logger().info("no person") 38 | self._cmd_vel.linear.x = 0.0 39 | self._cmd_vel.angular.z = 0.0 40 | self._publisher.publish(self._cmd_vel) 41 | return 42 | self.get_logger().info("publish") 43 | self.get_logger().info(f"{self._closest_angle=}") 44 | self._cmd_vel.linear.x = 0.0 45 | self._cmd_vel.angular.z = 0.0 46 | if 0.3 < self._closest_angle < ANGULAR_TOLERANCE: 47 | self.get_logger().info("turn ringht") 48 | self._cmd_vel.angular.z = 1.0 49 | elif -0.3 > self._closest_angle > -ANGULAR_TOLERANCE: 50 | self.get_logger().info("turn left") 51 | self._cmd_vel.angular.z = -1.0 52 | else: 53 | if self._closest_distance < MAX_RANGE_FOR_FOLLOW: 54 | self.get_logger().info("go foward") 55 | self._cmd_vel.linear.x = 0.3 56 | self._publisher.publish(self._cmd_vel) 57 | 58 | def _laser_scan_callback(self, msg: LaserScan) -> None: 59 | ranges = msg.ranges 60 | valid_ranges = [r for r in ranges if r > 0] 61 | if valid_ranges: 62 | min_range = min(valid_ranges) 63 | min_index = ranges.index(min_range) 64 | angle_increment = msg.angle_increment 65 | self._closest_distance = min_range 66 | self._closest_angle = angles.normalize_angle( 67 | msg.angle_min + (min_index * angle_increment) + (math.pi / 2) 68 | ) 69 | 70 | def _object_detection_callback( 71 | self, detections: ObjectDetectionListStamped 72 | ) -> None: 73 | self._person_in_detection = any( 74 | obj.label == ObjectDetection.PERSON for obj in detections.detection 75 | ) 76 | self.get_logger().info(f"{self._person_in_detection=}") 77 | 78 | 79 | def main(args=None): 80 | rclpy.init(args=args) 81 | follower = Follower() 82 | rclpy.spin(follower) 83 | follower.destroy_node() 84 | rclpy.shutdown() 85 | 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_follow/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_follow 5 | 0.0.0 6 | ROS2 follow sample 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | std_msgs 11 | rclpy 12 | common_interfaces 13 | sensor_msgs 14 | kachaka_interfaces 15 | 16 | ament_copyright 17 | ament_flake8 18 | ament_pep257 19 | python3-pytest 20 | 21 | 22 | ament_python 23 | 24 | 25 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_follow/resource/kachaka_follow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/demos/kachaka_follow/resource/kachaka_follow -------------------------------------------------------------------------------- /ros2/demos/kachaka_follow/setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script_dir=$base/lib/kachaka_follow 3 | [install] 4 | install_scripts=$base/lib/kachaka_follow 5 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_follow/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | package_name = "kachaka_follow" 4 | 5 | setup( 6 | name=package_name, 7 | version="0.0.0", 8 | packages=[package_name], 9 | data_files=[ 10 | ( 11 | "share/ament_index/resource_index/packages", 12 | ["resource/" + package_name], 13 | ), 14 | ("share/" + package_name, ["package.xml"]), 15 | ], 16 | install_requires=["setuptools"], 17 | zip_safe=True, 18 | maintainer="Kachaka Customer Support", 19 | maintainer_email="support@kachaka.life", 20 | description="TODO: Package description", 21 | license="TODO: License declaration", 22 | tests_require=["pytest"], 23 | entry_points={ 24 | "console_scripts": ["follow = kachaka_follow.follow:main"], 25 | }, 26 | ) 27 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_nav2_bringup/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(kachaka_nav2_bringup) 3 | 4 | find_package(ament_cmake REQUIRED) 5 | find_package(nav2_common REQUIRED) 6 | find_package(navigation2 REQUIRED) 7 | 8 | nav2_package() 9 | 10 | install(DIRECTORY launch DESTINATION share/${PROJECT_NAME}) 11 | install(DIRECTORY rviz DESTINATION share/${PROJECT_NAME}) 12 | install(DIRECTORY params DESTINATION share/${PROJECT_NAME}) 13 | 14 | if(BUILD_TESTING) 15 | find_package(ament_lint_auto REQUIRED) 16 | ament_lint_auto_find_test_dependencies() 17 | endif() 18 | 19 | ament_package() 20 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_nav2_bringup/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_nav2_bringup 5 | 1.0.12 6 | Bringup scripts and configurations for the Nav2 stack 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | 11 | ament_cmake 12 | nav2_common 13 | 14 | navigation2 15 | launch_ros 16 | 17 | launch_ros 18 | navigation2 19 | nav2_common 20 | 21 | ament_lint_common 22 | ament_lint_auto 23 | ament_cmake_gtest 24 | ament_cmake_pytest 25 | launch 26 | launch_testing 27 | 28 | 29 | ament_cmake 30 | 31 | 32 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_smart_speaker/README.md: -------------------------------------------------------------------------------- 1 | # スマートスピーカー連携サンプル 2 | 3 | ## はじめに 4 | * このサンプルは、カチャカ、Google Home、IFTTT、Beebotteのサービス連携を行います。 5 | * Google Nest Miniなどのデバイス、およびIFTTT、Beebotteサービスへの登録が必要になります。 6 | 7 | ## Beebotteの設定 8 | 9 | * Beebotteのサイトにログインします: https://beebotte.com/ 10 | * 新しいチャンネル/リソースを作ります 11 | * 左メニューのChannelsを選択 12 | * My Channels右の「Create New」ボタンを押します 13 | * Channel Name: test 14 | * Resource name: sample 15 | * 「Create Channel」ボタンを押します 16 | * 以下の場所にあるトークンを保存しておきます 17 | * 左メニューのChannelsを選択 18 | * My Channelsからtestを選択 19 | * Channel Token: token_xxxxxxx 20 | 21 | ## IFTTTの設定 22 | 23 | * IFTTTのサイトにログインします: https://ifttt.com/ 24 | * 「create」ボタンを押します 25 | * 「If This」を押して、以下の設定にします 26 | * Choose a serviceからGoogle Assistant V2を選択します 27 | * 「activate sentence」を押します 28 | * 「シェルフを持ってきて」など、サービスを呼び出す時にGoogle Homeに呼びかける言葉を設定します 29 | * 「Create Trigger」を押します 30 | * 「Then That」を押して、以下の設定にします 31 | * Choose a serviceからWebhooksを選択します 32 | * 「Make a web request」を押して、以下のように設定します 33 | * URL: https://api.beebotte.com/v1/data/publish/test/sample?token= 34 | * Method: POST 35 | * Content Type: application/json 36 | * 「Create action」を押します 37 | 38 | ## Google Homeの設定 39 | 40 | * 以下を参考に設定します 41 | * https://support.google.com/googlenest/answer/7194656?hl=en&co=GENIE.Platform%3DAndroid 42 | * TIPS 43 | * 標準的なIFTTTの使用法の場合「OKグーグル、シェルフを持ってきてを有効にして」のように発話しないと実行できません。 44 | * Google Assistantアプリのルーティン設定で「OKグーグル、シェルフを持ってきて」の発話で実行できるように変更出来ます。 45 | * 「オートメーション」を選択 46 | * 「+」で新しいルーティンを追加します 47 | * 「開始条件を追加」→「Googleアシスタントに話しかけたとき」→「シェルフを持ってきて」を設定し、「条件を追加」を押します 48 | * 「アクションを追加」→「カスタムアクションの追加」→「シェルフを持ってきてを有効にして」を設定し、「完了」を押します 49 | 50 | ## ビルド 51 | 52 | ``` 53 | cd ~ 54 | git clone https://github.com/pf-robotics/kachaka-api.git 55 | pip install paho-mqtt 56 | 57 | mkdir -p ~/ros2_ws/src 58 | cd ~/ros2_ws/src 59 | 60 | ln -s ~/kachaka-api/ros2/kachaka_interfaces/ kachaka_interfaces 61 | ln -s ~/kachaka-api/ros2/demos/kachaka_smart_speaker/ kachaka_smart_speaker 62 | 63 | cd ~/ros2_ws 64 | colcon build 65 | ``` 66 | 67 | ## 実行 68 | 69 | 70 | * ros2_bridgeを起動した上で、以下のコマンドを実行します 71 | ``` 72 | cd ~/ros2_ws 73 | source install/setup.bash 74 | 75 | export TOKEN= 76 | wget https://beebotte.com/certs/mqtt.beebotte.com.pem 77 | 78 | ros2 run kachaka_smart_speaker smart_speaker 79 | ``` 80 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_smart_speaker/kachaka_smart_speaker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/demos/kachaka_smart_speaker/kachaka_smart_speaker/__init__.py -------------------------------------------------------------------------------- /ros2/demos/kachaka_smart_speaker/kachaka_smart_speaker/smart_speaker.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import paho.mqtt.client as mqtt 5 | import rclpy 6 | from kachaka_interfaces.action import ExecKachakaCommand 7 | from kachaka_interfaces.msg import KachakaCommand 8 | from rclpy.action import ActionClient 9 | from rclpy.node import Node 10 | 11 | CHANNEL = "test" 12 | RESOURCE = "sample" 13 | PEM = "./mqtt.beebotte.com.pem" 14 | 15 | 16 | class SmartSpeaker(Node): 17 | def __init__(self) -> None: 18 | super().__init__("smart_speaker") 19 | self._action_client = ActionClient( 20 | self, 21 | ExecKachakaCommand, 22 | "/kachaka/kachaka_command/execute", 23 | ) 24 | self._action_client.wait_for_server() 25 | 26 | self._mqtt_client = mqtt.Client() 27 | self._mqtt_client.on_connect = self._on_connect 28 | self._mqtt_client.on_message = self._on_message 29 | 30 | try: 31 | token = os.environ["TOKEN"] 32 | except KeyError: 33 | self.get_logger().info("TOKEN is not set.") 34 | sys.exit() 35 | 36 | if not os.path.exists(PEM): 37 | self.get_logger().info(f"{PEM} file not found.") 38 | sys.exit() 39 | 40 | self._mqtt_client.username_pw_set(f"token:{token}") 41 | self._mqtt_client.tls_set(PEM) 42 | self._mqtt_client.connect("mqtt.beebotte.com", 8883, 60) 43 | self._mqtt_client.loop_start() 44 | 45 | def _on_connect(self, client, userdata, flag, rc): 46 | if rc == 0: 47 | self.get_logger().info("Connection succeeded.") 48 | client.subscribe(f"{CHANNEL}/{RESOURCE}", 1) 49 | else: 50 | self.get_logger().info("Connection failed. code=" + str(rc)) 51 | 52 | def _on_message(self, client, data, msg): 53 | self.get_logger().info("on_message: " + str(msg.payload)) 54 | self._send_shelf_frame() 55 | 56 | def _send_shelf_frame(self) -> None: 57 | command = KachakaCommand() 58 | command.command_type = KachakaCommand.MOVE_SHELF_COMMAND 59 | command.move_shelf_command_target_shelf_id = "S01" 60 | command.move_shelf_command_destination_location_id = "L01" 61 | command.move_shelf_command_undock_on_destination = False 62 | goal_msg = ExecKachakaCommand.Goal() 63 | goal_msg.kachaka_command = command 64 | self._action_client.send_goal_async(goal_msg) 65 | 66 | 67 | def main(args=None): 68 | rclpy.init(args=args) 69 | smart_speaker = SmartSpeaker() 70 | rclpy.spin(smart_speaker) 71 | smart_speaker.destroy_node() 72 | rclpy.shutdown() 73 | 74 | 75 | if __name__ == "__main__": 76 | main() 77 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_smart_speaker/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_smart_speaker 5 | 0.0.0 6 | ROS2 sample smart_speaker 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | rclpy 11 | kachaka_interfaces 12 | ament_copyright 13 | ament_flake8 14 | ament_pep257 15 | python3-pytest 16 | 17 | ament_python 18 | 19 | 20 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_smart_speaker/resource/kachaka_smart_speaker: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/demos/kachaka_smart_speaker/resource/kachaka_smart_speaker -------------------------------------------------------------------------------- /ros2/demos/kachaka_smart_speaker/setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script_dir=$base/lib/kachaka_smart_speaker 3 | [install] 4 | install_scripts=$base/lib/kachaka_smart_speaker 5 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_smart_speaker/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | package_name = "kachaka_smart_speaker" 4 | 5 | setup( 6 | name=package_name, 7 | version="0.0.0", 8 | packages=[package_name], 9 | data_files=[ 10 | ( 11 | "share/ament_index/resource_index/packages", 12 | ["resource/" + package_name], 13 | ), 14 | ("share/" + package_name, ["package.xml"]), 15 | ], 16 | install_requires=["setuptools"], 17 | zip_safe=True, 18 | maintainer="Kachaka Customer Support", 19 | maintainer_email="support@kachaka.life", 20 | description="kachaka ROS2 sample move_to_pose", 21 | license="TODO: License declaration", 22 | tests_require=["pytest"], 23 | entry_points={ 24 | "console_scripts": [ 25 | "smart_speaker = kachaka_smart_speaker.smart_speaker:main", 26 | ], 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_speak/README.md: -------------------------------------------------------------------------------- 1 | # speakサンプル 2 | 3 | ## ビルド方法 4 | 5 | ``` 6 | cd ~/ 7 | git clone https://github.com/pf-robotics/kachaka-api.git 8 | 9 | mkidr -p ~/ros2_ws/src 10 | cd ~/ros2_ws/src 11 | ln -s ~/kachaka-api/ros2/kachaka_interfaces . 12 | ln -s ~/kachaka-api/ros2/demos/kachaka_speak . 13 | 14 | cd ~/ros2_ws 15 | colcon build 16 | ``` 17 | 18 | ## 実行方法 19 | 20 | * ros2_bridgeを起動した状態で、以下のコマンドを実行します 21 | 22 | ``` 23 | cd ~/ros2_ws 24 | source install/setup.bash 25 | ros2 run kachaka_speak speak 26 | ``` 27 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_speak/kachaka_speak/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/demos/kachaka_speak/kachaka_speak/__init__.py -------------------------------------------------------------------------------- /ros2/demos/kachaka_speak/kachaka_speak/speak.py: -------------------------------------------------------------------------------- 1 | import rclpy 2 | from kachaka_interfaces.action import ExecKachakaCommand 3 | from kachaka_interfaces.msg import KachakaCommand 4 | from rclpy.action import ActionClient 5 | from rclpy.node import Node 6 | 7 | 8 | class Speak(Node): 9 | def __init__(self) -> None: 10 | super().__init__("speak") 11 | self._action_client = ActionClient( 12 | self, ExecKachakaCommand, "/kachaka/kachaka_command/execute" 13 | ) 14 | self._action_client.wait_for_server() 15 | 16 | def send_goal(self): 17 | command = KachakaCommand() 18 | command.command_type = KachakaCommand.SPEAK_COMMAND 19 | command.speak_command_text = "こんにちは、カチャカです" 20 | goal_msg = ExecKachakaCommand.Goal() 21 | goal_msg.kachaka_command = command 22 | return self._action_client.send_goal_async(goal_msg) 23 | 24 | 25 | def main(args=None): 26 | rclpy.init(args=args) 27 | 28 | speak = Speak() 29 | future = speak.send_goal() 30 | rclpy.spin_until_future_complete(speak, future) 31 | 32 | speak.destroy_node() 33 | rclpy.shutdown() 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_speak/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_speak 5 | 0.0.0 6 | ROS2 speak sample 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | ament_copyright 11 | ament_flake8 12 | ament_pep257 13 | python3-pytest 14 | 15 | 16 | ament_python 17 | 18 | 19 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_speak/resource/kachaka_speak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/demos/kachaka_speak/resource/kachaka_speak -------------------------------------------------------------------------------- /ros2/demos/kachaka_speak/setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script_dir=$base/lib/kachaka_speak 3 | [install] 4 | install_scripts=$base/lib/kachaka_speak 5 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_speak/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | package_name = "kachaka_speak" 4 | 5 | setup( 6 | name=package_name, 7 | version="0.0.0", 8 | packages=[package_name], 9 | data_files=[ 10 | ( 11 | "share/ament_index/resource_index/packages", 12 | ["resource/" + package_name], 13 | ), 14 | ("share/" + package_name, ["package.xml"]), 15 | ], 16 | install_requires=["setuptools"], 17 | zip_safe=True, 18 | maintainer="Kachaka Customer Support", 19 | maintainer_email="support@kachaka.life", 20 | description="kachaka ROS2 sample speak", 21 | license="TODO: License declaration", 22 | tests_require=["pytest"], 23 | entry_points={ 24 | "console_scripts": [ 25 | "speak = kachaka_speak.speak:main", 26 | ], 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_vision/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(kachaka_vision) 3 | 4 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 5 | add_compile_options(-Wall -Wextra -Wpedantic) 6 | endif() 7 | 8 | find_package(ament_cmake REQUIRED) 9 | find_package(cv_bridge REQUIRED) 10 | find_package(rclcpp REQUIRED) 11 | find_package(sensor_msgs REQUIRED) 12 | find_package(OpenCV REQUIRED) 13 | 14 | add_executable(hand_recognition_node src/hand_recognition_node.cpp) 15 | ament_target_dependencies( 16 | hand_recognition_node 17 | cv_bridge 18 | OpenCV 19 | rclcpp 20 | sensor_msgs) 21 | 22 | install(TARGETS hand_recognition_node DESTINATION lib/${PROJECT_NAME}) 23 | 24 | ament_package() 25 | 26 | install(DIRECTORY launch config DESTINATION share/${PROJECT_NAME}/) 27 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_vision/README.md: -------------------------------------------------------------------------------- 1 | # Vision Example 2 | 3 | # Overview 4 | 5 | * This is an example that uses front RGB camera to detect hand pose. 6 | 7 | # Building 8 | 9 | * Download model and config file from the internet. 10 | 11 | ```bash 12 | cd ~/kachaka-api/ros2/demos/kachaka_vision/config 13 | # sometimes the following link is not available, then please see and find the mirror link in https://github.com/CMU-Perceptual-Computing-Lab/openpose/issues/1567 14 | wget http://posefs1.perception.cs.cmu.edu/OpenPose/models/hand/pose_iter_102000.caffemodel 15 | wget https://raw.githubusercontent.com/CMU-Perceptual-Computing-Lab/openpose/master/models/hand/pose_deploy.prototxt 16 | ``` 17 | 18 | * Copy the necessary folders to workspace and build the project. 19 | 20 | ```bash 21 | mkidr -p ~/ros2_ws/src 22 | cd ~/ros2_ws/src 23 | cp -r ~/kachaka-api/ros2/demos/kachaka_vision . 24 | 25 | cd ~/ros2_ws 26 | colcon build 27 | ``` 28 | 29 | # Execution 30 | 31 | * Open another terminal and start the ROS2 bridge. 32 | 33 | ```bash 34 | cd ~/kachaka-api/ros2_bridge 35 | ./start_bridge.sh 36 | 37 | ``` 38 | 39 | * Next, run the following command. 40 | 41 | ```bash 42 | cd ~/ros2_ws 43 | source install/setup.bash 44 | ros2 launch kachaka_vision hand_recognition_launch.py 45 | ``` 46 | 47 | * Now, let's check the output image by the following command on another terminal, then put your hand on the front camera. 48 | 49 | ```bash 50 | ros2 run rqt_image_view rqt_image_view /hand_recognition_node/output_image 51 | ``` 52 | 53 | ![image_view](image_view.png "image_view") 54 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_vision/config/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_vision/image_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/demos/kachaka_vision/image_view.png -------------------------------------------------------------------------------- /ros2/demos/kachaka_vision/launch/hand_recognition_launch.py: -------------------------------------------------------------------------------- 1 | from launch import LaunchDescription 2 | from launch.substitutions import PathJoinSubstitution 3 | from launch_ros.actions import Node 4 | from launch_ros.substitutions import FindPackageShare 5 | 6 | 7 | def generate_launch_description(): 8 | model_path = PathJoinSubstitution( 9 | [ 10 | FindPackageShare("kachaka_vision"), 11 | "config", 12 | "pose_iter_102000.caffemodel", 13 | ] 14 | ) 15 | model_config_path = PathJoinSubstitution( 16 | [FindPackageShare("kachaka_vision"), "config", "pose_deploy.prototxt"] 17 | ) 18 | return LaunchDescription( 19 | [ 20 | Node( 21 | package="kachaka_vision", 22 | executable="hand_recognition_node", 23 | name="hand_recognition_node", 24 | namespace="hand_recognition_node", 25 | remappings=[ 26 | ("image", "/kachaka/front_camera/image_raw/compressed") 27 | ], 28 | parameters=[ 29 | { 30 | "model_path": model_path, 31 | "model_config_path": model_config_path, 32 | "confidence_threshold": 0.1, 33 | } 34 | ], 35 | ) 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /ros2/demos/kachaka_vision/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_vision 5 | 0.0.0 6 | ROS2 vision sample 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | ament_cmake 11 | cv_bridge 12 | rclcpp 13 | sensor_msgs 14 | 15 | ament_cmake 16 | 17 | 18 | -------------------------------------------------------------------------------- /ros2/kachaka_description/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(kachaka_description) 3 | 4 | if(NOT CMAKE_CXX_STANDARD) 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | endif() 8 | 9 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 10 | add_compile_options(-Wall -Wextra -Wpedantic) 11 | endif() 12 | 13 | find_package(ament_cmake REQUIRED) 14 | find_package(ament_cmake_auto REQUIRED) 15 | ament_auto_find_build_dependencies() 16 | 17 | install( 18 | DIRECTORY config 19 | launch 20 | robot 21 | meshes 22 | urdf DESTINATION share/${PROJECT_NAME}) 23 | 24 | if(BUILD_TESTING) 25 | find_package(ament_lint_auto REQUIRED) 26 | ament_lint_auto_find_test_dependencies() 27 | endif() 28 | 29 | ament_auto_package() 30 | -------------------------------------------------------------------------------- /ros2/kachaka_description/launch/robot_description.launch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from launch import LaunchDescription 3 | from launch.actions import DeclareLaunchArgument 4 | from launch.substitutions import ( 5 | Command, 6 | EnvironmentVariable, 7 | FindExecutable, 8 | LaunchConfiguration, 9 | PathJoinSubstitution, 10 | ) 11 | from launch_ros.actions import Node 12 | from launch_ros.substitutions import FindPackageShare 13 | 14 | 15 | def generate_launch_description(): 16 | namespace_arg = DeclareLaunchArgument( 17 | "namespace", 18 | default_value="kachaka", 19 | description="Namespace for the robot state publisher", 20 | ) 21 | 22 | frame_prefix_arg = DeclareLaunchArgument( 23 | "frame_prefix", 24 | default_value=EnvironmentVariable("FRAME_PREFIX"), 25 | description="Frame prefix for the robot state publisher", 26 | ) 27 | 28 | namespace = LaunchConfiguration("namespace") 29 | frame_prefix = LaunchConfiguration("frame_prefix") 30 | 31 | robot_description_content = Command( 32 | [ 33 | PathJoinSubstitution([FindExecutable(name="xacro")]), 34 | " ", 35 | PathJoinSubstitution( 36 | [ 37 | FindPackageShare("kachaka_description"), 38 | "robot", 39 | "kachaka.urdf.xacro", 40 | ] 41 | ), 42 | ] 43 | ) 44 | 45 | robot_state_publisher_node = Node( 46 | package="robot_state_publisher", 47 | executable="robot_state_publisher", 48 | name="robot_state_publisher", 49 | namespace=namespace, 50 | output="screen", 51 | parameters=[ 52 | { 53 | "robot_description": robot_description_content, 54 | "frame_prefix": frame_prefix, 55 | } 56 | ], 57 | ) 58 | 59 | return LaunchDescription( 60 | [namespace_arg, frame_prefix_arg, robot_state_publisher_node] 61 | ) 62 | -------------------------------------------------------------------------------- /ros2/kachaka_description/launch/robot_display.launch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 23 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /ros2/kachaka_description/meshes/kachaka/body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/kachaka_description/meshes/kachaka/body.stl -------------------------------------------------------------------------------- /ros2/kachaka_description/meshes/kachaka/left_tire.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/kachaka_description/meshes/kachaka/left_tire.stl -------------------------------------------------------------------------------- /ros2/kachaka_description/meshes/kachaka/right_tire.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/kachaka_description/meshes/kachaka/right_tire.stl -------------------------------------------------------------------------------- /ros2/kachaka_description/meshes/kachaka/solenoid.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pf-robotics/kachaka-api/fbca59553a41072e6472ccb11a82ca6fc5f714fb/ros2/kachaka_description/meshes/kachaka/solenoid.stl -------------------------------------------------------------------------------- /ros2/kachaka_description/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_description 5 | 0.0.0 6 | kachaka description 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | ament_cmake 11 | joint_state_publisher 12 | robot_state_publisher 13 | urdf 14 | xacro 15 | launch_ros 16 | launch_xml 17 | joint_state_publisher 18 | joint_state_publisher_gui 19 | robot_state_publisher 20 | rviz2 21 | urdf 22 | xacro 23 | ament_lint_auto 24 | ament_lint_common 25 | 26 | ament_cmake 27 | 28 | 29 | -------------------------------------------------------------------------------- /ros2/kachaka_description/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pysen] 2 | version = "0.9.1" 3 | 4 | [tool.pysen-cli] 5 | settings_dir = "." 6 | 7 | [tool.pysen.lint] 8 | enable_black = false 9 | enable_flake8 = false 10 | enable_isort = false 11 | enable_mypy = false 12 | 13 | [tool.pysen.plugin.cmake_format] 14 | function = "pysen_plugins::cmake_format" 15 | -------------------------------------------------------------------------------- /ros2/kachaka_description/robot/kachaka.urdf.xacro: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ros2/kachaka_description/urdf/_materials.urdf.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ros2/kachaka_description/urdf/_values.urdf.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/.gitignore: -------------------------------------------------------------------------------- 1 | gen-src 2 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_grpc_ros2_bridge 5 | 0.0.0 6 | gRPC ROS 2 bridge with Kachaka local API 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | ament_cmake 11 | rclcpp 12 | rclcpp_action 13 | rclcpp_components 14 | rcl 15 | rcl_interfaces 16 | rcutils 17 | rmw 18 | rmw_implementation_cmake 19 | tf2_ros 20 | tf2_msgs 21 | camera_info_manager 22 | cv_bridge 23 | libopencv-dev 24 | std_msgs 25 | std_srvs 26 | sensor_msgs 27 | geometry_msgs 28 | nav_msgs 29 | diagnostic_msgs 30 | kachaka_interfaces 31 | launch_ros 32 | launch_xml 33 | rclcpp 34 | rclcpp_action 35 | rclcpp_components 36 | rcl 37 | rcl_interfaces 38 | rcutils 39 | rmw 40 | tf2_ros 41 | tf2_msgs 42 | camera_info_manager 43 | cv_bridge 44 | libopencv-dev 45 | std_msgs 46 | std_srvs 47 | sensor_msgs 48 | geometry_msgs 49 | nav_msgs 50 | kachaka_interfaces 51 | kachaka_description 52 | cyclonedds 53 | rmw_cyclonedds_cpp 54 | ament_lint_auto 55 | ament_lint_common 56 | launch 57 | launch_testing 58 | launch_testing_ament_cmake 59 | launch_testing_ros 60 | 61 | ament_cmake 62 | 63 | 64 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pysen] 2 | version = "0.9.1" 3 | 4 | [tool.pysen-cli] 5 | settings_dir = "." 6 | 7 | [tool.pysen.lint] 8 | enable_black = false 9 | enable_flake8 = false 10 | enable_isort = false 11 | enable_mypy = false 12 | 13 | [tool.pysen.lint.source] 14 | includes = [ 15 | ".", 16 | ] 17 | 18 | [tool.pysen.plugin.clang_format] 19 | function = "pysen_plugins::clang_format" 20 | 21 | [tool.pysen.plugin.clang_format.config] 22 | extensions = [".cc", ".cpp", ".h", ".hpp"] 23 | 24 | [tool.pysen.plugin.cmake_format] 25 | function = "pysen_plugins::cmake_format" 26 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/component/back_camera_component.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "../camera_bridge.hpp" 20 | #include "kachaka-api.grpc.pb.h" 21 | 22 | namespace kachaka::grpc_ros2_bridge { 23 | 24 | class BackCameraComponent : public rclcpp::Node { 25 | public: 26 | explicit BackCameraComponent(const rclcpp::NodeOptions& options) 27 | : Node("back_camera", options) { 28 | this->declare_parameter("frame_prefix", ""); 29 | frame_prefix_ = this->get_parameter("frame_prefix").as_string(); 30 | stub_ = GetSharedStub(declare_parameter("server_uri", "")); 31 | using namespace std::placeholders; 32 | back_camera_bridge_ = std::make_unique< 33 | CameraBridge>( 36 | frame_prefix_, stub_, this, false, 37 | std::bind(&kachaka_api::KachakaApi::Stub::GetBackCameraRosCameraInfo, 38 | *stub_, _1, _2, _3), 39 | std::bind(&kachaka_api::KachakaApi::Stub::GetBackCameraRosImage, *stub_, 40 | _1, _2, _3), 41 | std::bind( 42 | &kachaka_api::KachakaApi::Stub::GetBackCameraRosCompressedImage, 43 | *stub_, _1, _2, _3)); 44 | } 45 | 46 | ~BackCameraComponent() = default; 47 | 48 | BackCameraComponent(const BackCameraComponent&) = delete; 49 | BackCameraComponent& operator=(const BackCameraComponent&) = delete; 50 | 51 | private: 52 | std::string frame_prefix_; 53 | std::shared_ptr stub_{nullptr}; 54 | std::unique_ptr< 55 | CameraBridge> 58 | back_camera_bridge_; 59 | }; 60 | 61 | } // namespace kachaka::grpc_ros2_bridge 62 | 63 | RCLCPP_COMPONENTS_REGISTER_NODE(kachaka::grpc_ros2_bridge::BackCameraComponent) 64 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/component/dynamic_tf_component.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "../converter/ros_header.hpp" 23 | #include "../dynamic_tf_bridge.hpp" 24 | #include "../stub_util.hpp" 25 | #include "kachaka-api.grpc.pb.h" 26 | 27 | namespace kachaka::grpc_ros2_bridge { 28 | 29 | class DynamicTfComponent : public rclcpp::Node { 30 | public: 31 | explicit DynamicTfComponent(const rclcpp::NodeOptions& options) 32 | : Node("dynamic_tf", options) { 33 | RCLCPP_INFO(this->get_logger(), "start dynamic tf"); 34 | 35 | this->declare_parameter("frame_prefix", ""); 36 | frame_prefix_ = this->get_parameter("frame_prefix").as_string(); 37 | stub_ = GetSharedStub(declare_parameter("server_uri", "")); 38 | 39 | RCLCPP_INFO(this->get_logger(), "get stub"); 40 | 41 | dynamic_tf_client_ = 42 | std::make_unique(frame_prefix_, stub_, this); 43 | 44 | dynamic_tf_client_->ReadStream(); 45 | RCLCPP_INFO(this->get_logger(), "start read dynamic tf"); 46 | } 47 | ~DynamicTfComponent() override {} 48 | 49 | DynamicTfComponent(const DynamicTfComponent&) = delete; 50 | DynamicTfComponent& operator=(const DynamicTfComponent&) = delete; 51 | 52 | private: 53 | std::string frame_prefix_; 54 | std::shared_ptr stub_{nullptr}; 55 | std::unique_ptr dynamic_tf_client_; 56 | }; 57 | 58 | } // namespace kachaka::grpc_ros2_bridge 59 | 60 | RCLCPP_COMPONENTS_REGISTER_NODE(kachaka::grpc_ros2_bridge::DynamicTfComponent) 61 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/component/front_camera_component.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "../camera_bridge.hpp" 20 | #include "kachaka-api.grpc.pb.h" 21 | 22 | namespace kachaka::grpc_ros2_bridge { 23 | 24 | class FrontCameraComponent : public rclcpp::Node { 25 | public: 26 | explicit FrontCameraComponent(const rclcpp::NodeOptions& options) 27 | : Node("front_camera", options) { 28 | this->declare_parameter("frame_prefix", ""); 29 | frame_prefix_ = this->get_parameter("frame_prefix").as_string(); 30 | stub_ = GetSharedStub(declare_parameter("server_uri", "")); 31 | using namespace std::placeholders; 32 | front_camera_bridge_ = std::make_unique< 33 | CameraBridge>( 36 | frame_prefix_, stub_, this, false, 37 | std::bind(&kachaka_api::KachakaApi::Stub::GetFrontCameraRosCameraInfo, 38 | *stub_, _1, _2, _3), 39 | std::bind(&kachaka_api::KachakaApi::Stub::GetFrontCameraRosImage, 40 | *stub_, _1, _2, _3), 41 | std::bind( 42 | &kachaka_api::KachakaApi::Stub::GetFrontCameraRosCompressedImage, 43 | *stub_, _1, _2, _3)); 44 | } 45 | 46 | ~FrontCameraComponent() = default; 47 | 48 | FrontCameraComponent(const FrontCameraComponent&) = delete; 49 | FrontCameraComponent& operator=(const FrontCameraComponent&) = delete; 50 | 51 | private: 52 | std::string frame_prefix_; 53 | std::shared_ptr stub_{nullptr}; 54 | std::unique_ptr< 55 | CameraBridge> 58 | front_camera_bridge_; 59 | }; 60 | 61 | } // namespace kachaka::grpc_ros2_bridge 62 | 63 | RCLCPP_COMPONENTS_REGISTER_NODE(kachaka::grpc_ros2_bridge::FrontCameraComponent) 64 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/component/goal_pose_component.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "../converter/kachaka_command.hpp" 22 | #include "../grpc_bridge.hpp" 23 | #include "../stub_util.hpp" 24 | #include "kachaka-api.grpc.pb.h" 25 | 26 | namespace kachaka::grpc_ros2_bridge { 27 | 28 | class GoalPoseComponent : public rclcpp::Node { 29 | public: 30 | explicit GoalPoseComponent(const rclcpp::NodeOptions& options) 31 | : Node("goal_pose", options) { 32 | exec_kachaka_command_client_ = rclcpp_action::create_client< 33 | kachaka_interfaces::action::ExecKachakaCommand>( 34 | this, "kachaka_command/execute"); 35 | goal_pose_subscriber_ = 36 | create_subscription( 37 | "~/goal_pose", 5, 38 | [this](const geometry_msgs::msg::PoseStamped& msg) { 39 | GoalPoseCallback(msg); 40 | }); 41 | } 42 | 43 | private: 44 | void GoalPoseCallback(const geometry_msgs::msg::PoseStamped& msg) { 45 | kachaka_interfaces::action::ExecKachakaCommand::Goal goal; 46 | goal.kachaka_command.command_type = 47 | kachaka_interfaces::msg::KachakaCommand::MOVE_TO_POSE_COMMAND; 48 | goal.kachaka_command.move_to_pose_command_x = msg.pose.position.x; 49 | goal.kachaka_command.move_to_pose_command_y = msg.pose.position.y; 50 | tf2::Quaternion q(msg.pose.orientation.x, msg.pose.orientation.y, 51 | msg.pose.orientation.z, msg.pose.orientation.w); 52 | tf2::Matrix3x3 m(q); 53 | double roll, pitch, yaw; 54 | m.getRPY(roll, pitch, yaw); 55 | goal.kachaka_command.move_to_pose_command_yaw = yaw; 56 | exec_kachaka_command_client_->async_send_goal(goal); 57 | RCLCPP_INFO(get_logger(), "sent goal_pose (%.3f, %.3f, %.3f)", 58 | goal.kachaka_command.move_to_pose_command_x, 59 | goal.kachaka_command.move_to_pose_command_y, 60 | goal.kachaka_command.move_to_pose_command_yaw); 61 | } 62 | 63 | rclcpp_action::Client:: 64 | SharedPtr exec_kachaka_command_client_{nullptr}; 65 | rclcpp::Subscription::SharedPtr 66 | goal_pose_subscriber_{nullptr}; 67 | }; 68 | 69 | } // namespace kachaka::grpc_ros2_bridge 70 | 71 | RCLCPP_COMPONENTS_REGISTER_NODE(kachaka::grpc_ros2_bridge::GoalPoseComponent) 72 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/component/tof_camera_component.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "../camera_bridge.hpp" 20 | #include "kachaka-api.grpc.pb.h" 21 | 22 | namespace kachaka::grpc_ros2_bridge { 23 | 24 | class TofCameraComponent : public rclcpp::Node { 25 | public: 26 | explicit TofCameraComponent(const rclcpp::NodeOptions& options) 27 | : Node("tof_camera", options) { 28 | this->declare_parameter("frame_prefix", ""); 29 | frame_prefix_ = this->get_parameter("frame_prefix").as_string(); 30 | stub_ = GetSharedStub(declare_parameter("server_uri", "")); 31 | using namespace std::placeholders; 32 | tof_camera_bridge_ = std::make_unique< 33 | CameraBridge>( 36 | frame_prefix_, stub_, this, true, 37 | std::bind(&kachaka_api::KachakaApi::Stub::GetTofCameraRosCameraInfo, 38 | *stub_, _1, _2, _3), 39 | std::bind(&kachaka_api::KachakaApi::Stub::GetTofCameraRosImage, *stub_, 40 | _1, _2, _3), 41 | std::bind( 42 | &kachaka_api::KachakaApi::Stub::GetTofCameraRosCompressedImage, 43 | *stub_, _1, _2, _3)); 44 | } 45 | 46 | ~TofCameraComponent() = default; 47 | 48 | TofCameraComponent(const TofCameraComponent&) = delete; 49 | TofCameraComponent& operator=(const TofCameraComponent&) = delete; 50 | 51 | private: 52 | std::string frame_prefix_; 53 | std::shared_ptr stub_{nullptr}; 54 | std::unique_ptr< 55 | CameraBridge> 58 | tof_camera_bridge_; 59 | }; 60 | 61 | } // namespace kachaka::grpc_ros2_bridge 62 | 63 | RCLCPP_COMPONENTS_REGISTER_NODE(kachaka::grpc_ros2_bridge::TofCameraComponent) 64 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/converter/kachaka_command.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #pragma once 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include "kachaka-api.pb.h" 20 | 21 | namespace kachaka::grpc_ros2_bridge::converter { 22 | 23 | std::optional ConvertToRos2( 24 | const kachaka_api::Command& in); 25 | 26 | std::optional ConvertToGrpc( 27 | const kachaka_interfaces::msg::KachakaCommand& in); 28 | 29 | } // namespace kachaka::grpc_ros2_bridge::converter 30 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/converter/ros_header.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include "ros_header.hpp" 14 | 15 | #include 16 | #include 17 | 18 | #include "kachaka-api.pb.h" 19 | 20 | namespace kachaka::grpc_ros2_bridge::converter { 21 | 22 | void ConvertGrpcHeaderToRos2Header(const kachaka_api::RosHeader& grpc_header, 23 | std_msgs::msg::Header* ros2_header, 24 | const std::string& frame_prefix) { 25 | ros2_header->stamp = rclcpp::Time(grpc_header.stamp_nsec()); 26 | ros2_header->frame_id = frame_prefix + grpc_header.frame_id(); 27 | } 28 | 29 | } // namespace kachaka::grpc_ros2_bridge::converter 30 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/converter/ros_header.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #pragma once 14 | 15 | #include 16 | 17 | #include "kachaka-api.pb.h" 18 | 19 | namespace kachaka::grpc_ros2_bridge::converter { 20 | 21 | void ConvertGrpcHeaderToRos2Header(const kachaka_api::RosHeader& grpc_header, 22 | std_msgs::msg::Header* ros2_header, 23 | const std::string& frame_prefix = ""); 24 | 25 | } // namespace kachaka::grpc_ros2_bridge::converter 26 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/dynamic_tf_bridge.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include "dynamic_tf_bridge.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "converter/ros_header.hpp" 20 | #include "kachaka-api.grpc.pb.h" 21 | 22 | namespace { 23 | bool ConvertGrpcTfToRosTf( 24 | const kachaka_api::GetDynamicTransformResponse& grpc_msg, 25 | tf2_msgs::msg::TFMessage* msg, const std::string& frame_prefix = "") { 26 | msg->transforms.clear(); 27 | msg->transforms.reserve(grpc_msg.transforms_size()); 28 | for (const auto& transform_grpc : grpc_msg.transforms()) { 29 | geometry_msgs::msg::TransformStamped transform_ros; 30 | kachaka::grpc_ros2_bridge::converter::ConvertGrpcHeaderToRos2Header( 31 | transform_grpc.header(), &(transform_ros.header), frame_prefix); 32 | 33 | transform_ros.child_frame_id = 34 | frame_prefix + transform_grpc.child_frame_id(); 35 | transform_ros.transform.translation.x = transform_grpc.translation().x(); 36 | transform_ros.transform.translation.y = transform_grpc.translation().y(); 37 | transform_ros.transform.translation.z = transform_grpc.translation().z(); 38 | transform_ros.transform.rotation.x = transform_grpc.rotation().x(); 39 | transform_ros.transform.rotation.y = transform_grpc.rotation().y(); 40 | transform_ros.transform.rotation.z = transform_grpc.rotation().z(); 41 | transform_ros.transform.rotation.w = transform_grpc.rotation().w(); 42 | 43 | msg->transforms.push_back(transform_ros); 44 | } 45 | return true; 46 | } 47 | 48 | } // namespace 49 | 50 | namespace kachaka::grpc_ros2_bridge { 51 | 52 | TfStreamClient::TfStreamClient( 53 | std::string frame_prefix, 54 | std::shared_ptr stub, rclcpp::Node* node) 55 | : frame_prefix_(std::move(frame_prefix)), 56 | stub_(stub), 57 | node_(node), 58 | publisher_(node->create_publisher( 59 | "/tf", tf2_ros::DynamicBroadcasterQoS(5))) {} 60 | 61 | void TfStreamClient::ReadStream() { 62 | grpc::ClientContext context; 63 | kachaka_api::EmptyRequest request; 64 | 65 | std::unique_ptr> 66 | reader(stub_->GetDynamicTransform(&context, request)); 67 | 68 | kachaka_api::GetDynamicTransformResponse response; 69 | 70 | while (reader->Read(&response)) { 71 | tf2_msgs::msg::TFMessage msg; 72 | ConvertGrpcTfToRosTf(response, &msg, frame_prefix_); 73 | publisher_->publish(msg); 74 | } 75 | RCLCPP_INFO(node_->get_logger(), "dynamic tf server is stopped."); 76 | } 77 | 78 | } // namespace kachaka::grpc_ros2_bridge 79 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/dynamic_tf_bridge.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #pragma once 14 | 15 | #include 16 | #include 17 | 18 | #include "kachaka-api.grpc.pb.h" 19 | 20 | namespace kachaka::grpc_ros2_bridge { 21 | 22 | class TfStreamClient { 23 | public: 24 | TfStreamClient(std::string frame_prefix, 25 | std::shared_ptr stub, 26 | rclcpp::Node* node); 27 | void ReadStream(); 28 | 29 | private: 30 | std::string frame_prefix_; 31 | std::shared_ptr stub_{nullptr}; 32 | rclcpp::Node* node_; 33 | typename rclcpp::Publisher::SharedPtr publisher_; 34 | }; 35 | 36 | } // namespace kachaka::grpc_ros2_bridge 37 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/error_code.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #pragma once 14 | 15 | namespace kachaka::grpc_ros2_bridge::error_code { 16 | 17 | // TODO(youtalk): Use value from ErrorCode.msg 18 | static constexpr int kErrorCodeApiGrpcSetRobotVelocityNotInTeleopMode = 13151; 19 | 20 | } // namespace kachaka::grpc_ros2_bridge::error_code 21 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/ros2_topic_bridge.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #pragma once 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "grpc_bridge.hpp" 23 | #include "kachaka-api.grpc.pb.h" 24 | 25 | namespace kachaka::grpc_ros2_bridge { 26 | 27 | template 28 | class Ros2TopicBridge 29 | : public GrpcBridge { 30 | public: 31 | using Converter = std::function; 32 | 33 | explicit Ros2TopicBridge( 34 | rclcpp::Node* node, 35 | typename GrpcBridge::GrpcService 36 | grpc_service, 37 | std::string ros2_topic, const rclcpp::QoS& qos) 38 | : GrpcBridge(node, grpc_service), 39 | ros2_topic_(std::move(ros2_topic)), 40 | publisher_(node->create_publisher(ros2_topic_, qos)) { 41 | this->SetGrpcRequestCreation([this]() { 42 | kachaka_api::GetRequest request; 43 | request.mutable_metadata()->set_cursor(last_cursor_); 44 | return request; 45 | }); 46 | this->SetPauseAsyncCallback([this]() { 47 | return publisher_->get_subscription_count() == 0 and not first_publish_; 48 | }); 49 | this->SetGrpcResponseCallback([this](const GrpcResponse& response) { 50 | Ros2Msg msg; 51 | if (converter_(response, &msg)) { 52 | publisher_->publish(msg); 53 | first_publish_ = false; 54 | } 55 | last_cursor_ = response.metadata().cursor(); 56 | }); 57 | } 58 | ~Ros2TopicBridge() = default; 59 | 60 | Ros2TopicBridge(const Ros2TopicBridge&) = delete; 61 | Ros2TopicBridge& operator=(const Ros2TopicBridge&) = delete; 62 | 63 | void SetConverter(Converter converter) { converter_ = std::move(converter); } 64 | 65 | private: 66 | std::string ros2_topic_; 67 | Converter converter_{[](const GrpcResponse&, Ros2Msg*) { 68 | return false; 69 | }}; 70 | typename rclcpp::Publisher::SharedPtr publisher_; 71 | bool first_publish_{true}; 72 | std::atomic_int64_t last_cursor_{0}; 73 | }; 74 | 75 | } // namespace kachaka::grpc_ros2_bridge 76 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/stub_util.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include "stub_util.hpp" 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | namespace { 22 | 23 | constexpr const char* kLogger = "grpc_ros2_bridge"; 24 | std::unordered_map> 25 | global_shared_stabs; 26 | 27 | } // namespace 28 | 29 | namespace kachaka::grpc_ros2_bridge { 30 | 31 | std::shared_ptr GetSharedStub( 32 | const std::string& server_uri) { 33 | if (server_uri.empty()) { 34 | RCLCPP_FATAL(rclcpp::get_logger(kLogger), "empty 'server_uri'"); 35 | std::abort(); 36 | } 37 | if (global_shared_stabs.count(server_uri) == 0) { 38 | auto channel = 39 | grpc::CreateChannel(server_uri, grpc::InsecureChannelCredentials()); 40 | global_shared_stabs.emplace(server_uri, 41 | kachaka_api::KachakaApi::NewStub(channel)); 42 | RCLCPP_INFO(rclcpp::get_logger(kLogger), "created stub %s", 43 | server_uri.c_str()); 44 | } 45 | 46 | return global_shared_stabs.at(server_uri); 47 | } 48 | 49 | } // namespace kachaka::grpc_ros2_bridge 50 | -------------------------------------------------------------------------------- /ros2/kachaka_grpc_ros2_bridge/src/stub_util.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Preferred Robotics, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #pragma once 14 | 15 | #include "kachaka-api.grpc.pb.h" 16 | 17 | namespace kachaka::grpc_ros2_bridge { 18 | 19 | std::shared_ptr GetSharedStub( 20 | const std::string& server_uri); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(kachaka_interfaces) 4 | 5 | if(NOT CMAKE_CXX_STANDARD) 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | endif() 9 | 10 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 11 | add_compile_options(-Wall -Wextra -Wpedantic) 12 | endif() 13 | 14 | find_package(ament_cmake REQUIRED) 15 | find_package(ament_cmake_auto REQUIRED) 16 | ament_auto_find_build_dependencies() 17 | 18 | rosidl_generate_interfaces( 19 | ${PROJECT_NAME} 20 | "action/ExecKachakaCommand.action" 21 | "msg/KachakaCommand.msg" 22 | "msg/Location.msg" 23 | "msg/LocationList.msg" 24 | "msg/ObjectDetection.msg" 25 | "msg/ObjectDetectionListStamped.msg" 26 | "msg/Shelf.msg" 27 | "msg/ShelfList.msg" 28 | "msg/ShelfSize.msg" 29 | DEPENDENCIES 30 | geometry_msgs 31 | sensor_msgs 32 | std_msgs) 33 | 34 | ament_auto_generate_code() 35 | 36 | if(BUILD_TESTING) 37 | find_package(ament_lint_auto REQUIRED) 38 | ament_lint_auto_find_test_dependencies() 39 | endif() 40 | 41 | ament_auto_package() 42 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/action/ExecKachakaCommand.action: -------------------------------------------------------------------------------- 1 | kachaka_interfaces/KachakaCommand kachaka_command 2 | bool cancel_all 3 | string tts_on_success 4 | --- 5 | bool success 6 | int32 error_code 7 | string message 8 | --- 9 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/KachakaCommand.msg: -------------------------------------------------------------------------------- 1 | uint8 COMMAND_TYPE_OTHER=0 2 | uint8 MOVE_SHELF_COMMAND=1 3 | uint8 RETURN_SHELF_COMMAND=2 4 | uint8 UNDOCK_SHELF_COMMAND=5 5 | uint8 MOVE_TO_LOCATION_COMMAND=7 6 | uint8 RETURN_HOME_COMMAND=8 7 | uint8 DOCK_SHELF_COMMAND=9 8 | uint8 SPEAK_COMMAND=12 9 | uint8 MOVE_TO_POSE_COMMAND=13 10 | 11 | uint8 command_type 12 | 13 | # NOTE: only used for MOVE_SHELF_COMMAND 14 | string move_shelf_command_target_shelf_id 15 | string move_shelf_command_destination_location_id 16 | bool move_shelf_command_undock_on_destination 17 | 18 | # NOTE: only used for RETURN_SHELF_COMMAND 19 | string return_shelf_command_target_shelf_id 20 | 21 | # NOTE: only used for UNDOCK_SHELF_COMMAND 22 | string undock_shelf_command_target_shelf_id 23 | 24 | # NOTE: only used for MOVE_TO_LOCATION_COMMAND 25 | string move_to_location_command_target_location_id 26 | 27 | # NOTE: only used for RETURN_HOME_COMMAND 28 | bool return_home_command_silent 29 | 30 | # NOTE: only used for SPEAK_COMMAND 31 | string speak_command_text 32 | 33 | # NOTE: only used for MOVE_TO_POSE_COMMAND 34 | float64 move_to_pose_command_x 35 | float64 move_to_pose_command_y 36 | float64 move_to_pose_command_yaw 37 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/Location.msg: -------------------------------------------------------------------------------- 1 | uint8 LOCATION_TYPE_OTHER=0 2 | uint8 LOCATION_TYPE_CHARGER=1 3 | uint8 LOCATION_TYPE_SHELF_HOME=2 4 | 5 | string id 6 | string name 7 | uint8 type 8 | geometry_msgs/Pose2D pose 9 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/LocationList.msg: -------------------------------------------------------------------------------- 1 | Location[] locations 2 | string default_location_id 3 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/ObjectDetection.msg: -------------------------------------------------------------------------------- 1 | # Object label definition 2 | uint8 PERSON=0 3 | uint8 SHELF=1 4 | uint8 CHARGER=2 5 | uint8 DOOR=3 6 | 7 | # Object label 8 | uint8 label 9 | 10 | # Region of the detected object 11 | sensor_msgs/RegionOfInterest roi 12 | 13 | # Confidence score for the detected object [0.0 - 1.0] 14 | float32 score 15 | 16 | # Distance to the detected object. Median value in the region. [m] 17 | # 0.0 is interpreted as invalid distance 18 | float64 distance_median 19 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/ObjectDetectionListStamped.msg: -------------------------------------------------------------------------------- 1 | std_msgs/Header header 2 | ObjectDetection[] detection 3 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/Shelf.msg: -------------------------------------------------------------------------------- 1 | uint8 DEFAULT_SPEED_MODE = 0 2 | uint8 LOW_SPEED_MODE = 1 3 | 4 | string id 5 | string name 6 | geometry_msgs/Pose2D pose 7 | ShelfSize size 8 | string home_location_id 9 | uint8 speed_mode 10 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/ShelfList.msg: -------------------------------------------------------------------------------- 1 | Shelf[] shelves 2 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/msg/ShelfSize.msg: -------------------------------------------------------------------------------- 1 | float64 width 2 | float64 depth 3 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kachaka_interfaces 5 | 0.0.0 6 | kachaka ROS 2 interfaces 7 | Kachaka Customer Support 8 | Apache License 2.0 9 | Kachaka Customer Support 10 | ament_cmake 11 | rosidl_default_generators 12 | std_msgs 13 | sensor_msgs 14 | geometry_msgs 15 | std_msgs 16 | sensor_msgs 17 | geometry_msgs 18 | ament_lint_auto 19 | ament_lint_common 20 | launch 21 | launch_testing 22 | launch_testing_ament_cmake 23 | launch_testing_ros 24 | rosidl_interface_packages 25 | 26 | ament_cmake 27 | 28 | 29 | -------------------------------------------------------------------------------- /ros2/kachaka_interfaces/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pysen] 2 | version = "0.9.1" 3 | 4 | [tool.pysen-cli] 5 | settings_dir = "." 6 | 7 | [tool.pysen.lint] 8 | enable_black = false 9 | enable_flake8 = false 10 | enable_isort = false 11 | enable_mypy = false 12 | 13 | [tool.pysen.plugin.clang_format] 14 | function = "pysen_plugins::clang_format" 15 | 16 | [tool.pysen.plugin.cmake_format] 17 | function = "pysen_plugins::cmake_format" 18 | -------------------------------------------------------------------------------- /tools/export_current_map.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # カチャカが現在使用中の地図をエクスポートする実行形式のスクリプトのサンプルです。 4 | # Usage: python3 export_map.py --ip_address <カチャカのIPアドレス> --output_dir <出力先ディレクトリ> 5 | 6 | from __future__ import annotations 7 | 8 | import argparse 9 | import asyncio 10 | import os 11 | import sys 12 | 13 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../") 14 | 15 | from kachaka_api.aio import KachakaApiClient # noqa: E402 16 | 17 | 18 | def parse_args() -> argparse.Namespace: 19 | parser = argparse.ArgumentParser(description="export map data to file") 20 | 21 | parser.add_argument( 22 | "--ip_address", 23 | type=str, 24 | default="100.94.1.1", 25 | help="IP Address of Kachaka", 26 | ) 27 | parser.add_argument( 28 | "--output_dir", 29 | type=str, 30 | default=os.getcwd(), 31 | help="output directory", 32 | ) 33 | return parser.parse_args() 34 | 35 | 36 | async def main() -> None: 37 | args = parse_args() 38 | 39 | client = ( 40 | KachakaApiClient() 41 | if args.ip_address is None 42 | else KachakaApiClient(f"{args.ip_address}:26400") 43 | ) 44 | 45 | serial_number = await client.get_robot_serial_number() 46 | current_map_id = await client.get_current_map_id() 47 | 48 | output_file_path = os.path.join( 49 | args.output_dir, f"kachaka_{serial_number}_map_{current_map_id}" 50 | ) 51 | await client.export_map(current_map_id, output_file_path) 52 | 53 | 54 | if __name__ == "__main__": 55 | asyncio.run(main()) 56 | -------------------------------------------------------------------------------- /tools/gen_proto/python_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | export UV_CACHE_DIR=/tmp/.cache/uv 6 | export UV_PYTHON_INSTALL_DIR=/tmp/.local/share/uv 7 | 8 | uv run \ 9 | --with "grpcio-tools==1.66.1" \ 10 | python -m grpc_tools.protoc \ 11 | -I/protos \ 12 | --python_out="/generated" \ 13 | --pyi_out="/generated" \ 14 | --grpc_python_out="/generated" \ 15 | /protos/kachaka-api.proto 16 | 17 | sed "/generated"/kachaka_api_pb2_grpc.py -i \ 18 | -e "s/import kachaka_api_pb2/from . import kachaka_api_pb2/" \ 19 | -e "s/self, channel/self, channel: grpc.Channel/" 20 | -------------------------------------------------------------------------------- /tools/gen_proto/ros2_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | protoc -I /protos \ 6 | --grpc_out=/generated \ 7 | --plugin=protoc-gen-grpc=/usr/bin/grpc_cpp_plugin \ 8 | --cpp_out=/generated \ 9 | /protos/kachaka-api.proto 10 | 11 | -------------------------------------------------------------------------------- /tools/generate_proto_for_ros2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 6 | REPO_TOP_DIR="${SCRIPT_DIR}/.." 7 | 8 | cd "${REPO_TOP_DIR}" || exit 1 9 | 10 | # build docker image specifying the target stage 11 | docker build \ 12 | --target kachaka-api-gen-proto-ros2 \ 13 | -t kachaka-api-gen-proto-ros2 \ 14 | . 15 | 16 | # run docker image 17 | mkdir -p "${REPO_TOP_DIR}/ros2/kachaka_grpc_ros2_bridge/gen-src" 18 | docker run --rm \ 19 | -u "$(id -u):$(id -g)" \ 20 | -v "${REPO_TOP_DIR}/protos:/protos" \ 21 | -v "${REPO_TOP_DIR}/ros2/kachaka_grpc_ros2_bridge/gen-src:/generated" \ 22 | kachaka-api-gen-proto-ros2 23 | 24 | -------------------------------------------------------------------------------- /tools/import_map.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # カチャカの地図をインポートする実行形式のスクリプトのサンプルです。 4 | # Usage: python3 import_map.py --ip_address <カチャカのIPアドレス> --input_file <インポートするマップファイル> 5 | 6 | from __future__ import annotations 7 | 8 | import argparse 9 | import asyncio 10 | import os 11 | import sys 12 | 13 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../") 14 | 15 | from kachaka_api.aio import KachakaApiClient # noqa: E402 16 | 17 | 18 | def parse_args() -> argparse.Namespace: 19 | parser = argparse.ArgumentParser(description="import map data from file") 20 | 21 | parser.add_argument( 22 | "--ip_address", 23 | type=str, 24 | default="100.94.1.1", 25 | help="IP Address of Kachaka", 26 | ) 27 | parser.add_argument( 28 | "--input_file", 29 | type=str, 30 | required=True, 31 | help="map data file path", 32 | ) 33 | return parser.parse_args() 34 | 35 | 36 | async def main() -> None: 37 | args = parse_args() 38 | 39 | client = ( 40 | KachakaApiClient() 41 | if args.ip_address is None 42 | else KachakaApiClient(f"{args.ip_address}:26400") 43 | ) 44 | 45 | result, map_id = await client.import_map(args.input_file) 46 | if result.success: 47 | print(f"map successfully imported. id: {map_id}") 48 | else: 49 | print(f"failed to import map. error code: {result.error_code}") 50 | 51 | 52 | if __name__ == "__main__": 53 | asyncio.run(main()) 54 | -------------------------------------------------------------------------------- /tools/lint/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt-get update && \ 4 | apt-get -y install --no-install-recommends \ 5 | git \ 6 | python3 \ 7 | python3-pip \ 8 | python3-setuptools \ 9 | python3-venv \ 10 | && \ 11 | apt-get clean && \ 12 | rm -rf /var/lib/apt/lists/* 13 | RUN pip3 install -U pip 14 | RUN --mount=source=tools/lint,target=/lint \ 15 | pip3 install -r /lint/requirements.txt 16 | -------------------------------------------------------------------------------- /tools/lint/local_run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | FORMAT=0 6 | while [[ "$#" -gt 0 ]]; do 7 | case "$1" in 8 | -i) 9 | FORMAT=1 10 | ;; 11 | -*) 12 | echo "Usage:" 13 | echo " - Perform lint:" 14 | echo " tools/lint/run.sh [-i]" 15 | exit 1 16 | ;; 17 | esac 18 | shift 19 | done 20 | 21 | TMP_DIR="$(mktemp -d /tmp/kachaka_api_lint_XXXXXX)" 22 | trap 'rm -rf "${TMP_DIR}"; exit 1' 1 2 3 15 23 | 24 | mapfile -t ipynb_files < <(git ls-files | grep ipynb) 25 | if [[ "$FORMAT" -eq 1 ]]; then 26 | ./tools/update_kachaka_api_base.py 27 | pysen run format lint 28 | mypy 29 | # TODO(nozaki) Use pysen plugins 30 | nbqa black "${ipynb_files[@]}" 31 | nbqa isort "${ipynb_files[@]}" 32 | else 33 | ./tools/update_kachaka_api_base.py "${TMP_DIR}"/base.py 34 | pysen run lint 35 | mypy 36 | # TODO(nozaki) Use pysen plugins 37 | nbqa black "${ipynb_files[@]}" --diff --check 38 | nbqa isort "${ipynb_files[@]}" --diff --check-only 39 | fi 40 | -------------------------------------------------------------------------------- /tools/lint/requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==6.0.1 2 | black==23.7.0 3 | clang-format==15.0.7 4 | cmake-format==0.6.13 5 | flake8==6.1.0 6 | grpcio==1.56.2 7 | isort==5.12.0 8 | mypy==1.5.1 9 | nbqa==1.7.0 10 | numpy==1.25.1 11 | protobuf==4.23.4 12 | pysen-plugins==2023.5.22 13 | pysen==0.10.4 14 | shellcheck-py==0.8.0.4 15 | -------------------------------------------------------------------------------- /tools/lint/run_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | OPTS=() 6 | while [[ $# -gt 0 ]]; do 7 | case $1 in 8 | -i) 9 | OPTS+=(-i) 10 | ;; 11 | -*) 12 | echo "Usage:" 13 | echo " - Perform lint:" 14 | echo " $0 [-i]" 15 | exit 1 16 | ;; 17 | esac 18 | shift 19 | done 20 | 21 | set -eu 22 | 23 | REPO_TOP_DIR="$(git rev-parse --show-toplevel)" 24 | docker run --rm -i \ 25 | -v "${REPO_TOP_DIR}:${REPO_TOP_DIR}" \ 26 | --workdir="$(pwd)" \ 27 | --user="$(id -u):$(id -g)" \ 28 | asia-northeast1-docker.pkg.dev/pfr-flexci/tmp/kachaka-api.lint:KEEP-20230828 \ 29 | bash << EOF 30 | python3 -m venv /tmp/env 31 | source /tmp/env/bin/activate 32 | pip3 install -r tools/lint/requirements.txt 33 | pip3 install -e . 34 | ./tools/lint/local_run.sh ${OPTS[*]} 35 | EOF 36 | -------------------------------------------------------------------------------- /tools/ros2_bridge/.env: -------------------------------------------------------------------------------- 1 | TAG=20250213 2 | -------------------------------------------------------------------------------- /tools/ros2_bridge/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | services: 4 | ros2_bridge: 5 | image: "asia-northeast1-docker.pkg.dev/kachaka-api/docker/kachaka-grpc-ros2-bridge:${TAG}" 6 | network_mode: "host" 7 | ipc: "host" 8 | pid: "host" 9 | environment: 10 | - NAMESPACE 11 | - FRAME_PREFIX 12 | - API_GRPC_BRIDGE_SERVER_URI 13 | - ROS_DOMAIN_ID 14 | - ROS_LOCALHOST_ONLY 15 | - ROS_LOG_DIR=/tmp 16 | - RMW_IMPLEMENTATION 17 | - USER_ID 18 | - GROUP_ID 19 | user: "${USER_ID}:${GROUP_ID}" 20 | command: > 21 | ros2 launch kachaka_grpc_ros2_bridge grpc_ros2_bridge.launch.xml server_uri:=${API_GRPC_BRIDGE_SERVER_URI} namespace:=${NAMESPACE} 22 | kachaka_follow: 23 | image: "asia-northeast1-docker.pkg.dev/kachaka-api/docker/kachaka-grpc-ros2-bridge:${TAG}" 24 | network_mode: "host" 25 | ipc: "host" 26 | pid: "host" 27 | environment: 28 | - ROS_DOMAIN_ID 29 | - ROS_LOCALHOST_ONLY 30 | - ROS_LOG_DIR=/tmp 31 | - RMW_IMPLEMENTATION 32 | - USER_ID 33 | - GROUP_ID 34 | user: "${USER_ID}:${GROUP_ID}" 35 | command: > 36 | ros2 run kachaka_follow follow 37 | kachaka_speak: 38 | image: "asia-northeast1-docker.pkg.dev/kachaka-api/docker/kachaka-grpc-ros2-bridge:${TAG}" 39 | network_mode: "host" 40 | ipc: "host" 41 | pid: "host" 42 | environment: 43 | - ROS_DOMAIN_ID 44 | - ROS_LOCALHOST_ONLY 45 | - ROS_LOG_DIR=/tmp 46 | - RMW_IMPLEMENTATION 47 | - USER_ID 48 | - GROUP_ID 49 | user: "${USER_ID}:${GROUP_ID}" 50 | command: > 51 | ros2 run kachaka_speak speak 52 | kachaka_smart_speaker: 53 | image: "asia-northeast1-docker.pkg.dev/kachaka-api/docker/kachaka-grpc-ros2-bridge:${TAG}" 54 | network_mode: "host" 55 | ipc: "host" 56 | pid: "host" 57 | environment: 58 | - ROS_DOMAIN_ID 59 | - ROS_LOCALHOST_ONLY 60 | - ROS_LOG_DIR=/tmp 61 | - RMW_IMPLEMENTATION 62 | - USER_ID 63 | - GROUP_ID 64 | user: "${USER_ID}:${GROUP_ID}" 65 | command: > 66 | ros2 run kachaka_smart_speaker smart_speaker 67 | kachaka_vision: 68 | image: "asia-northeast1-docker.pkg.dev/kachaka-api/docker/kachaka-grpc-ros2-bridge:${TAG}" 69 | network_mode: "host" 70 | ipc: "host" 71 | pid: "host" 72 | environment: 73 | - ROS_DOMAIN_ID 74 | - ROS_LOCALHOST_ONLY 75 | - ROS_LOG_DIR=/tmp 76 | - RMW_IMPLEMENTATION 77 | - USER_ID 78 | - GROUP_ID 79 | user: "${USER_ID}:${GROUP_ID}" 80 | command: > 81 | ros2 launch kachaka_vision hand_recognition_launch.py 82 | -------------------------------------------------------------------------------- /tools/ros2_bridge/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck disable=SC1091 4 | . /opt/kachaka/setup.sh 5 | exec "$@" 6 | -------------------------------------------------------------------------------- /tools/ros2_bridge/start_bridge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | DOCKER_COMPOSE_DIR="$(cd "$(dirname "$0")" && pwd)" 6 | cd "${DOCKER_COMPOSE_DIR}" 7 | 8 | usage() { 9 | echo "Usage: $0 KACHAKA_IP_ADRESS [KACHAKA_NAME] [Option]" 10 | echo " -d daemonize" 11 | exit 1 12 | } 13 | 14 | if [[ $# -lt 1 ]]; then 15 | usage 16 | fi 17 | 18 | if [[ $# -lt 2 ]]; then 19 | NAMESPACE="kachaka" 20 | FRAME_PREFIX='' 21 | else 22 | NAMESPACE=$2 23 | FRAME_PREFIX=$2"/" 24 | fi 25 | 26 | USER_ID="$(id -u)" 27 | GROUP_ID="$(id -g)" 28 | GRPC_PORT=26400 29 | 30 | export USER_ID 31 | export GROUP_ID 32 | 33 | KACHAKA_IP=$1 34 | 35 | if command -v docker-compose; then 36 | API_GRPC_BRIDGE_SERVER_URI="${KACHAKA_IP}:${GRPC_PORT}" NAMESPACE="${NAMESPACE}" FRAME_PREFIX="${FRAME_PREFIX}" docker-compose up "${@:3}" ros2_bridge 37 | else 38 | API_GRPC_BRIDGE_SERVER_URI="${KACHAKA_IP}:${GRPC_PORT}" NAMESPACE="${NAMESPACE}" FRAME_PREFIX="${FRAME_PREFIX}" docker compose up "${@:3}" ros2_bridge 39 | fi 40 | -------------------------------------------------------------------------------- /tools/sync_from_robot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -ne 1 ]]; then 4 | echo "Usage: $0 KACHAKA_IP_ADDRESS" 5 | exit 1 6 | fi 7 | 8 | KACHAKA_IP="$1" 9 | 10 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 11 | KACHAKA_API_DIR="${SCRIPT_DIR}/.." 12 | 13 | rsync -a --rsh "ssh -p 26500" kachaka@"${KACHAKA_IP}":/home/kachaka/kachaka-api/ "${KACHAKA_API_DIR}"/ 14 | -------------------------------------------------------------------------------- /tools/sync_to_robot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -ne 1 ]]; then 4 | echo "Usage: $0 KACHAKA_IP_ADDRESS" 5 | exit 1 6 | fi 7 | 8 | KACHAKA_IP="$1" 9 | 10 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 11 | KACHAKA_API_DIR="${SCRIPT_DIR}/.." 12 | 13 | rsync -a --rsh "ssh -p 26500" "${KACHAKA_API_DIR}"/ kachaka@"${KACHAKA_IP}":/home/kachaka/kachaka-api/ 14 | -------------------------------------------------------------------------------- /tools/update_docker_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | if [ $# != 1 ]; then 6 | echo "usage: ./tools/update_docker_images.sh " 7 | exit 1 8 | fi 9 | 10 | DATE="$(date +"%Y%m%d")" 11 | TAG="$1" 12 | 13 | function get_internal_image() { 14 | local PLATFORM=$1 15 | local digest 16 | digest=$( \ 17 | docker manifest inspect asia-northeast1-docker.pkg.dev/pfr-flexci/tmp/kachaka-grpc-ros2-bridge:"${TAG}-${PLATFORM}" \ 18 | | jq -r ".manifests[] | select(.platform.os == \"linux\" and .platform.architecture == \"${PLATFORM}\") | .digest" \ 19 | ) 20 | echo "asia-northeast1-docker.pkg.dev/pfr-flexci/tmp/kachaka-grpc-ros2-bridge@${digest}" 21 | } 22 | 23 | arm64_image=$(get_internal_image arm64) 24 | amd64_image=$(get_internal_image amd64) 25 | 26 | docker manifest create asia-northeast1-docker.pkg.dev/kachaka-api/docker/kachaka-grpc-ros2-bridge:"${DATE}" \ 27 | "${arm64_image}" \ 28 | "${amd64_image}" 29 | docker manifest push asia-northeast1-docker.pkg.dev/kachaka-api/docker/kachaka-grpc-ros2-bridge:"${DATE}" 30 | -------------------------------------------------------------------------------- /tools/update_kachaka_api_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | aio_base = open("python/kachaka_api/aio/base.py") 6 | output_path = "python/kachaka_api/base.py" 7 | if len(sys.argv) > 1: 8 | output_path = sys.argv[1] 9 | output = open(output_path, "w") 10 | 11 | output.write("# This file generated by tools/update_kachaka_api_base.py\n") 12 | output.write("# Don't edit directly\n\n") 13 | for line in aio_base: 14 | line = line.replace("await ", "") 15 | line = line.replace("async ", "") 16 | line = line.replace("AsyncIterator", "Iterator") 17 | line = line.replace("from ..", "from .") 18 | line = line.replace("grpc.aio.", "grpc.") 19 | output.write(line) 20 | -------------------------------------------------------------------------------- /tools/update_kachaka_api_generated.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 6 | REPO_TOP_DIR="${SCRIPT_DIR}/.." 7 | 8 | cd "${REPO_TOP_DIR}" || exit 1 9 | 10 | # build docker image specifying the target stage 11 | docker build \ 12 | --target kachaka-api-gen-proto-python \ 13 | -t kachaka-api-gen-proto-python \ 14 | . 15 | 16 | # run docker image 17 | docker run --rm \ 18 | -u "$(id -u):$(id -g)" \ 19 | -v "${REPO_TOP_DIR}/protos:/protos" \ 20 | -v "${REPO_TOP_DIR}/python/kachaka_api/generated:/generated" \ 21 | kachaka-api-gen-proto-python 22 | -------------------------------------------------------------------------------- /tools/web_proxy/.gitignore: -------------------------------------------------------------------------------- 1 | envoy.yaml 2 | -------------------------------------------------------------------------------- /tools/web_proxy/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | grpc_web_proxy: 4 | image: envoyproxy/envoy:v1.16-latest 5 | ports: 6 | - "${GRPC_WEB_PROXY_PORT}:${GRPC_WEB_PROXY_PORT}" 7 | volumes: 8 | - "./envoy.yaml:/etc/envoy/envoy.yaml" 9 | command: "/usr/local/bin/envoy -c /etc/envoy/envoy.yaml" 10 | -------------------------------------------------------------------------------- /tools/web_proxy/envoy.yaml.in: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: listener_0 4 | address: 5 | socket_address: { address: 0.0.0.0, port_value: GRPC_WEB_PROXY_PORT } 6 | filter_chains: 7 | - filters: 8 | - name: envoy.filters.network.http_connection_manager 9 | typed_config: 10 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 11 | codec_type: auto 12 | stat_prefix: ingress_http 13 | route_config: 14 | name: local_route 15 | virtual_hosts: 16 | - name: local_service 17 | domains: ["*"] 18 | routes: 19 | - match: { prefix: "/" } 20 | route: 21 | cluster: kachaka_api 22 | timeout: 0s 23 | max_stream_duration: 24 | grpc_timeout_header_max: 0s 25 | cors: 26 | allow_origin_string_match: 27 | - prefix: "*" 28 | allow_methods: GET, PUT, DELETE, POST, OPTIONS 29 | allow_headers: accept,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout 30 | max_age: "1728000" 31 | expose_headers: custom-header-1,grpc-status,grpc-message 32 | http_filters: 33 | - name: envoy.filters.http.grpc_web 34 | - name: envoy.filters.http.cors 35 | - name: envoy.filters.http.router 36 | clusters: 37 | - name: kachaka_api 38 | connect_timeout: 0.25s 39 | type: static 40 | http2_protocol_options: {} 41 | lb_policy: round_robin 42 | load_assignment: 43 | cluster_name: cluster_0 44 | endpoints: 45 | - lb_endpoints: 46 | - endpoint: 47 | address: 48 | socket_address: 49 | address: KACHAKA_IP_ADDRESS 50 | port_value: 26400 51 | -------------------------------------------------------------------------------- /tools/web_proxy/start_proxy_remote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | usage() { 6 | echo "Usage: $0 KACHAKA_IP_ADRESS [Option]" 7 | echo " -d daemonize" 8 | exit 1 9 | } 10 | 11 | if [[ $# -lt 1 ]]; then 12 | usage 13 | fi 14 | 15 | KACHAKA_IP_ADDRESS="$1" 16 | GRPC_WEB_PROXY_PORT=50000 17 | 18 | 19 | DOCKER_COMPOSE_DIR="$(cd "$(dirname "$0")" && pwd)" 20 | cd "${DOCKER_COMPOSE_DIR}" 21 | 22 | # render envoy.yaml from template 23 | sed "s/KACHAKA_IP_ADDRESS/${KACHAKA_IP_ADDRESS}/" ./envoy.yaml.in \ 24 | | sed "s/GRPC_WEB_PROXY_PORT/${GRPC_WEB_PROXY_PORT}/" \ 25 | > envoy.yaml 26 | 27 | export GRPC_WEB_PROXY_PORT 28 | 29 | if command -v docker-compose; then 30 | docker-compose up "${@:2}" grpc_web_proxy 31 | else 32 | docker compose up "${@:2}" grpc_web_proxy 33 | fi 34 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:@typescript-eslint/eslint-recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:import/recommended', 10 | 'plugin:import/typescript', 11 | 'plugin:prettier/recommended', 12 | 'plugin:react/recommended', 13 | 'plugin:react-hooks/recommended', 14 | ], 15 | ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts', 'src/protos/*'], 16 | parser: '@typescript-eslint/parser', 17 | plugins: ['react', 'react-refresh', 'react-hooks', '@typescript-eslint', 'import'], 18 | rules: { 19 | 'react-refresh/only-export-components': [ 20 | 'warn', 21 | { allowConstantExport: true }, 22 | ], 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-unused-vars': [ 25 | 'error', 26 | { varsIgnorePattern: '^_*' } 27 | ], 28 | 'semi': ['error', 'always'], 29 | 'semi-spacing': ['error', {'after': true, 'before': false}], 30 | 'semi-style': ['error', 'last'], 31 | 'no-extra-semi': 'error', 32 | 'no-unexpected-multiline': 'error', 33 | 'no-unreachable': 'error' 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "semi": true, 6 | "useTabs": false 7 | } 8 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/README.md: -------------------------------------------------------------------------------- 1 | # web sample 2 | 3 | ## usage 4 | 5 | - start proxy to bridge connection between gRPC server and client side scripts on browser. 6 | ``` 7 | $ cd kachaka-api 8 | $ ../../../tools/web_proxy/start_proxy_remote.sh [KACHAKA IP] 9 | ``` 10 | 11 | - run 12 | ``` 13 | $ npm run dev 14 | ``` 15 | 16 | - then open `localhost:5173` in your browser. 17 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Kachaka API web sample 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kachaka_api_web_sample", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "format": "prettier --write \"**/*.+(js|json|yml|ts|tsx)\"", 11 | "preview": "vite preview", 12 | "gen_proto": "npx protoc --ts_out ./src/protos --proto_path ../../../protos ../../../protos/kachaka-api.proto" 13 | }, 14 | "dependencies": { 15 | "@emotion/react": "^11.11.1", 16 | "@emotion/styled": "^11.11.0", 17 | "@mui/icons-material": "^5.14.7", 18 | "@mui/material": "^5.14.7", 19 | "@protobuf-ts/grpcweb-transport": "^2.9.1", 20 | "@protobuf-ts/plugin": "^2.9.1", 21 | "@types/uuid": "^9.0.3", 22 | "google-protobuf": "^3.21.2", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "uuid": "^9.0.0" 26 | }, 27 | "devDependencies": { 28 | "@types/react": "^18.2.15", 29 | "@types/react-dom": "^18.2.7", 30 | "@typescript-eslint/eslint-plugin": "^6.6.0", 31 | "@typescript-eslint/parser": "^6.0.0", 32 | "@vitejs/plugin-react-swc": "^3.3.2", 33 | "eslint": "^8.48.0", 34 | "eslint-config-plugin": "^1.0.11", 35 | "eslint-config-prettier": "^9.0.0", 36 | "eslint-config-standard-typescript": "^1.0.3", 37 | "eslint-config-standard-with-typescript": "^39.0.0", 38 | "eslint-plugin-import": "^2.28.1", 39 | "eslint-plugin-n": "^16.0.2", 40 | "eslint-plugin-promise": "^6.1.1", 41 | "eslint-plugin-react": "^7.33.2", 42 | "eslint-plugin-react-hooks": "^4.6.0", 43 | "eslint-plugin-react-refresh": "^0.4.3", 44 | "typescript": "^5.2.2", 45 | "vite": "^4.4.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | 3 | import { createTheme, ThemeProvider } from '@mui/material/styles'; 4 | import CssBaseline from '@mui/material/CssBaseline'; 5 | import Box from '@mui/material/Box'; 6 | import Container from '@mui/material/Container'; 7 | import Fab from '@mui/material/Fab'; 8 | import Grid from '@mui/material/Grid'; 9 | import MuiAppBar from '@mui/material/AppBar'; 10 | import Toolbar from '@mui/material/Toolbar'; 11 | import Typography from '@mui/material/Typography'; 12 | import AddIcon from '@mui/icons-material/Add'; 13 | 14 | import { v4 as uuidV4 } from 'uuid'; 15 | import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'; 16 | 17 | import { KachakaApiClient } from './protos/kachaka-api.client'; 18 | import { KachakaApiPanel } from './KachakaApiPanel'; 19 | import { useLocations, useShelves } from './kachakaApi'; 20 | 21 | const defaultTheme = createTheme(); 22 | 23 | function App(): JSX.Element { 24 | const [kachakaApiClient] = useState( 25 | new KachakaApiClient( 26 | new GrpcWebFetchTransport({ 27 | baseUrl: 'http://localhost:50000', 28 | }), 29 | ), 30 | ); 31 | const locations = useLocations(kachakaApiClient); 32 | const shelves = useShelves(kachakaApiClient); 33 | 34 | const [panelIds, setPanelIds] = useState(() => [uuidV4()]); 35 | 36 | const handleAddPanel = useCallback(() => { 37 | setPanelIds((prev) => [...prev, uuidV4()]); 38 | }, []); 39 | const handleClosePanel = useCallback((targetPanelId: string) => { 40 | setPanelIds((prev) => prev.filter((panelId) => panelId !== targetPanelId)); 41 | }, []); 42 | 43 | return ( 44 | 45 | 46 | 47 | 48 | 49 | 50 | Kachaka API grpc-web demo 51 | 52 | 53 | 54 | theme.palette.grey[100], 58 | flexGrow: 1, 59 | height: '100vh', 60 | overflow: 'auto', 61 | }} 62 | > 63 | 64 | 65 | 66 | {panelIds.map((panelId) => ( 67 | { 71 | handleClosePanel(panelId); 72 | }} 73 | locations={locations} 74 | shelves={shelves} 75 | /> 76 | ))} 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ); 88 | } 89 | 90 | export default App; 91 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/components/PngImage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | export function PngImage({ data }: { data: Uint8Array | undefined }) { 4 | const [url, setUrl] = useState(undefined); 5 | useEffect(() => { 6 | if (data) { 7 | const blob = new Blob([data], { type: 'image/png' }); 8 | const obj = URL.createObjectURL(blob); 9 | setUrl(obj); 10 | return () => { 11 | URL.revokeObjectURL(obj); 12 | }; 13 | } else { 14 | setUrl(undefined); 15 | } 16 | }, [data]); 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | 52 | button:hover { 53 | border-color: #646cff; 54 | } 55 | 56 | button:focus, 57 | button:focus-visible { 58 | outline: 4px auto -webkit-focus-ring-color; 59 | } 60 | 61 | @media (prefers-color-scheme: light) { 62 | :root { 63 | color: #213547; 64 | background-color: #ffffff; 65 | } 66 | 67 | a:hover { 68 | color: #747bff; 69 | } 70 | 71 | button { 72 | background-color: #f9f9f9; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/kachakaApiPanelType.ts: -------------------------------------------------------------------------------- 1 | export enum KachakaApiPanelType { 2 | Unset = '', 3 | AutoHoming = 'Auto Homing', 4 | Command = 'Command', 5 | CommandState = 'Command State', 6 | RobotPose = 'Robot Pose', 7 | RobotSerialNumber = 'Robot Serial Number', 8 | RobotVersion = 'Robot Version', 9 | Map = 'Map', 10 | ManualControl = 'Manual Control', 11 | } 12 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import './index.css'; 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ); 11 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/panels/AutoHomingPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Button from '@mui/material/Button'; 4 | import Typography from '@mui/material/Typography'; 5 | import Box from '@mui/material/Box'; 6 | 7 | import { type KachakaApiClient } from '../protos/kachaka-api.client'; 8 | import { useAutoHomingEnabled, useSetAutoHomingEnabled } from '../kachakaApi'; 9 | 10 | export function AutoHomingPanel({ 11 | kachakaApiClient, 12 | }: { 13 | kachakaApiClient: KachakaApiClient; 14 | }) { 15 | const autoHomingEnabled = useAutoHomingEnabled(kachakaApiClient); 16 | const setAutoHomingEnabled = useSetAutoHomingEnabled(kachakaApiClient); 17 | 18 | return ( 19 | 26 | 27 | 自動充電: {autoHomingEnabled ? 'ON' : 'OFF'} 28 | 29 | 39 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/panels/MapPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Card from '@mui/material/Card'; 4 | import Typography from '@mui/material/Typography'; 5 | 6 | import { type KachakaApiClient } from '../protos/kachaka-api.client'; 7 | import { useMap } from '../kachakaApi'; 8 | 9 | import { PngImage } from '../components/PngImage'; 10 | 11 | export function MapPanel({ 12 | kachakaApiClient, 13 | }: { 14 | kachakaApiClient: KachakaApiClient; 15 | }) { 16 | const map = useMap(kachakaApiClient); 17 | 18 | return ( 19 | 20 | {map && ( 21 | <> 22 | 23 | {map.name} 24 | 25 | 26 | 27 | )} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/panels/RobotPosePanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Box from '@mui/material/Box'; 4 | 5 | import { type KachakaApiClient } from '../protos/kachaka-api.client'; 6 | import { useRobotPose } from '../kachakaApi'; 7 | 8 | export function RobotPosePanel({ 9 | kachakaApiClient, 10 | }: { 11 | kachakaApiClient: KachakaApiClient; 12 | }) { 13 | const robotPose = useRobotPose(kachakaApiClient); 14 | 15 | return ( 16 | 17 | robot pose: ( 18 | {robotPose 19 | ? robotPose.map((v) => v.toFixed(3)).join(', ') 20 | : 'NaN, NaN, NaN'} 21 | ) 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/panels/RobotSerialNumberPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import Chip from '@mui/material/Chip'; 5 | 6 | import { type KachakaApiClient } from '../protos/kachaka-api.client'; 7 | import { useRobotSerialNumber } from '../kachakaApi'; 8 | 9 | export function RobotSerialNumberPanel({ 10 | kachakaApiClient, 11 | }: { 12 | kachakaApiClient: KachakaApiClient; 13 | }) { 14 | const robotSerialNumber = useRobotSerialNumber(kachakaApiClient); 15 | 16 | return ( 17 | 18 | robot serial number:{' '} 19 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/panels/RobotVersionPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import Chip from '@mui/material/Chip'; 5 | 6 | import { type KachakaApiClient } from '../protos/kachaka-api.client'; 7 | import { useRobotVersion } from '../kachakaApi'; 8 | 9 | export function RobotVersionPanel({ 10 | kachakaApiClient, 11 | }: { 12 | kachakaApiClient: KachakaApiClient; 13 | }) { 14 | const robotVersion = useRobotVersion(kachakaApiClient); 15 | 16 | return ( 17 | 18 | robot version:{' '} 19 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/panels/index.ts: -------------------------------------------------------------------------------- 1 | export { AutoHomingPanel } from './AutoHomingPanel'; 2 | export { CommandPanel } from './CommandPanel'; 3 | export { CommandStatePanel } from './CommandStatePanel'; 4 | export { RobotVersionPanel } from './RobotVersionPanel'; 5 | export { RobotSerialNumberPanel } from './RobotSerialNumberPanel'; 6 | export { RobotPosePanel } from './RobotPosePanel'; 7 | export { MapPanel } from './MapPanel'; 8 | export { ManualControlPanel } from './ManualControlPanel'; 9 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /web/demos/kachaka_api_web_sample/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react-swc'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | open: true, 9 | port: 5173, 10 | }, 11 | }); 12 | --------------------------------------------------------------------------------