├── .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 |
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 |
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 | 
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 | 
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 | 
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 | 
19 |
20 |
21 | ### return_shelf
22 | * 呼び出し: `return_shelf(shelf_id)`
23 | * 家具(ID: shelf_id)を家具のホームに片付けます。
24 |
25 | * 現在載せている家具を片付ける場合は、shelf_idを空にして `return_shelf("")` とします。
26 |
27 | 
28 |
29 | * 現在載せていない家具を指定すると、その家具の場所に行ってドッキングし、片付けます。
30 |
31 | 
32 |
33 | ### dock_shelf
34 | * 呼び出し: `dock_shelf()`
35 | * カチャカの正面にある家具をドッキングします。
36 |
37 | 
38 |
39 | ### undock_shelf
40 | * 呼び出し: `undock_shelf()`
41 | * 載せている家具をそこに置き、家具下から脱出します。前後どちらに抜けるかは、空き具合によって判断されます。
42 |
43 | 
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 | 
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 = \"code | error type | title (ja) | description (ja) | title (en) | description (en) |
\"\n",
44 | "for code, value in error_code.items():\n",
45 | " html += f\"{code} | {value.error_type} | {value.title} | {value.description} | {value.title_en} | {value.description_en} |
\"\n",
46 | "html += \"
\"\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 | 
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 |
--------------------------------------------------------------------------------